Преглед изворни кода

[core] Add unicodecsv 0.14.1, xlrd 2.0.1 and xlwt 1.3.0 for tablib 0.12.1

Ying Chen пре 4 година
родитељ
комит
e9240fe760
100 измењених фајлова са 17605 додато и 0 уклоњено
  1. 1 0
      desktop/core/ext-py/unicodecsv-0.14.1/MANIFEST.in
  2. 48 0
      desktop/core/ext-py/unicodecsv-0.14.1/PKG-INFO
  3. 27 0
      desktop/core/ext-py/unicodecsv-0.14.1/README.rst
  4. 5 0
      desktop/core/ext-py/unicodecsv-0.14.1/setup.cfg
  5. 31 0
      desktop/core/ext-py/unicodecsv-0.14.1/setup.py
  6. 11 0
      desktop/core/ext-py/unicodecsv-0.14.1/unicodecsv/__init__.py
  7. 223 0
      desktop/core/ext-py/unicodecsv-0.14.1/unicodecsv/py2.py
  8. 95 0
      desktop/core/ext-py/unicodecsv-0.14.1/unicodecsv/py3.py
  9. 919 0
      desktop/core/ext-py/unicodecsv-0.14.1/unicodecsv/test.py
  10. 583 0
      desktop/core/ext-py/xlrd-2.0.1/CHANGELOG.rst
  11. 76 0
      desktop/core/ext-py/xlrd-2.0.1/LICENSE
  12. 3 0
      desktop/core/ext-py/xlrd-2.0.1/MANIFEST.in
  13. 86 0
      desktop/core/ext-py/xlrd-2.0.1/PKG-INFO
  14. 56 0
      desktop/core/ext-py/xlrd-2.0.1/README.rst
  15. 410 0
      desktop/core/ext-py/xlrd-2.0.1/scripts/runxlrd.py
  16. 10 0
      desktop/core/ext-py/xlrd-2.0.1/setup.cfg
  17. 45 0
      desktop/core/ext-py/xlrd-2.0.1/setup.py
  18. 213 0
      desktop/core/ext-py/xlrd-2.0.1/xlrd/__init__.py
  19. 643 0
      desktop/core/ext-py/xlrd-2.0.1/xlrd/biffh.py
  20. 1474 0
      desktop/core/ext-py/xlrd-2.0.1/xlrd/book.py
  21. 485 0
      desktop/core/ext-py/xlrd-2.0.1/xlrd/compdoc.py
  22. 1324 0
      desktop/core/ext-py/xlrd-2.0.1/xlrd/formatting.py
  23. 2190 0
      desktop/core/ext-py/xlrd-2.0.1/xlrd/formula.py
  24. 1 0
      desktop/core/ext-py/xlrd-2.0.1/xlrd/info.py
  25. 2490 0
      desktop/core/ext-py/xlrd-2.0.1/xlrd/sheet.py
  26. 53 0
      desktop/core/ext-py/xlrd-2.0.1/xlrd/timemachine.py
  27. 248 0
      desktop/core/ext-py/xlrd-2.0.1/xlrd/xldate.py
  28. 11 0
      desktop/core/ext-py/xlwt-1.3.0/.gitignore
  29. 34 0
      desktop/core/ext-py/xlwt-1.3.0/.travis.yml
  30. 111 0
      desktop/core/ext-py/xlwt-1.3.0/PKG-INFO
  31. 83 0
      desktop/core/ext-py/xlwt-1.3.0/README.rst
  32. 153 0
      desktop/core/ext-py/xlwt-1.3.0/docs/Makefile
  33. 29 0
      desktop/core/ext-py/xlwt-1.3.0/docs/api.rst
  34. 173 0
      desktop/core/ext-py/xlwt-1.3.0/docs/changes.rst
  35. 22 0
      desktop/core/ext-py/xlwt-1.3.0/docs/conf.py
  36. 65 0
      desktop/core/ext-py/xlwt-1.3.0/docs/development.rst
  37. 35 0
      desktop/core/ext-py/xlwt-1.3.0/docs/index.rst
  38. 18 0
      desktop/core/ext-py/xlwt-1.3.0/docs/installation.rst
  39. 118 0
      desktop/core/ext-py/xlwt-1.3.0/docs/licenses.rst
  40. 190 0
      desktop/core/ext-py/xlwt-1.3.0/docs/make.bat
  41. 36 0
      desktop/core/ext-py/xlwt-1.3.0/examples/big-16Mb.py
  42. 31 0
      desktop/core/ext-py/xlwt-1.3.0/examples/big-35Mb.py
  43. 36 0
      desktop/core/ext-py/xlwt-1.3.0/examples/blanks.py
  44. 19 0
      desktop/core/ext-py/xlwt-1.3.0/examples/col_width.py
  45. 10 0
      desktop/core/ext-py/xlwt-1.3.0/examples/country.py
  46. 37 0
      desktop/core/ext-py/xlwt-1.3.0/examples/dates.py
  47. 38 0
      desktop/core/ext-py/xlwt-1.3.0/examples/format.py
  48. 35 0
      desktop/core/ext-py/xlwt-1.3.0/examples/formula_names.py
  49. 47 0
      desktop/core/ext-py/xlwt-1.3.0/examples/formulas.py
  50. 28 0
      desktop/core/ext-py/xlwt-1.3.0/examples/hyperlinks.py
  51. 16 0
      desktop/core/ext-py/xlwt-1.3.0/examples/image.py
  52. 27 0
      desktop/core/ext-py/xlwt-1.3.0/examples/image_chg_col_wid.py
  53. 39 0
      desktop/core/ext-py/xlwt-1.3.0/examples/merged.py
  54. 30 0
      desktop/core/ext-py/xlwt-1.3.0/examples/merged0.py
  55. 102 0
      desktop/core/ext-py/xlwt-1.3.0/examples/merged1.py
  56. 9 0
      desktop/core/ext-py/xlwt-1.3.0/examples/mini.py
  57. 60 0
      desktop/core/ext-py/xlwt-1.3.0/examples/num_formats.py
  58. 25 0
      desktop/core/ext-py/xlwt-1.3.0/examples/numbers_demo.py
  59. 113 0
      desktop/core/ext-py/xlwt-1.3.0/examples/outline.py
  60. 58 0
      desktop/core/ext-py/xlwt-1.3.0/examples/panes.py
  61. 60 0
      desktop/core/ext-py/xlwt-1.3.0/examples/panes2.py
  62. 42 0
      desktop/core/ext-py/xlwt-1.3.0/examples/panes3.py
  63. 12 0
      desktop/core/ext-py/xlwt-1.3.0/examples/parse-fmla.py
  64. BIN
      desktop/core/ext-py/xlwt-1.3.0/examples/pattern_examples.xls
  65. 122 0
      desktop/core/ext-py/xlwt-1.3.0/examples/protection.py
  66. BIN
      desktop/core/ext-py/xlwt-1.3.0/examples/python.bmp
  67. 17 0
      desktop/core/ext-py/xlwt-1.3.0/examples/row_styles.py
  68. 18 0
      desktop/core/ext-py/xlwt-1.3.0/examples/row_styles_empty.py
  69. 24 0
      desktop/core/ext-py/xlwt-1.3.0/examples/simple.py
  70. 52 0
      desktop/core/ext-py/xlwt-1.3.0/examples/sst.py
  71. 12 0
      desktop/core/ext-py/xlwt-1.3.0/examples/unicode0.py
  72. 28 0
      desktop/core/ext-py/xlwt-1.3.0/examples/unicode1.py
  73. 28 0
      desktop/core/ext-py/xlwt-1.3.0/examples/unicode2.py
  74. 156 0
      desktop/core/ext-py/xlwt-1.3.0/examples/wsprops.py
  75. 46 0
      desktop/core/ext-py/xlwt-1.3.0/examples/xlwt_easyxf_simple_demo.py
  76. 14 0
      desktop/core/ext-py/xlwt-1.3.0/examples/zoom_magnification.py
  77. 15 0
      desktop/core/ext-py/xlwt-1.3.0/requirements.txt
  78. 7 0
      desktop/core/ext-py/xlwt-1.3.0/setup.cfg
  79. 52 0
      desktop/core/ext-py/xlwt-1.3.0/setup.py
  80. BIN
      desktop/core/ext-py/xlwt-1.3.0/tests/bitmaps.xls
  81. BIN
      desktop/core/ext-py/xlwt-1.3.0/tests/less_simple.xls
  82. BIN
      desktop/core/ext-py/xlwt-1.3.0/tests/mini.xls
  83. BIN
      desktop/core/ext-py/xlwt-1.3.0/tests/python.bmp
  84. BIN
      desktop/core/ext-py/xlwt-1.3.0/tests/simple.xls
  85. 24 0
      desktop/core/ext-py/xlwt-1.3.0/tests/test_biff_records.py
  86. 41 0
      desktop/core/ext-py/xlwt-1.3.0/tests/test_bitmaps.py
  87. 30 0
      desktop/core/ext-py/xlwt-1.3.0/tests/test_by_name_functions.py
  88. 4 0
      desktop/core/ext-py/xlwt-1.3.0/tests/test_compound_doc.py
  89. 55 0
      desktop/core/ext-py/xlwt-1.3.0/tests/test_easyxf.py
  90. 21 0
      desktop/core/ext-py/xlwt-1.3.0/tests/test_mini.py
  91. 67 0
      desktop/core/ext-py/xlwt-1.3.0/tests/test_simple.py
  92. 40 0
      desktop/core/ext-py/xlwt-1.3.0/tests/test_unicode1.py
  93. 30 0
      desktop/core/ext-py/xlwt-1.3.0/tests/test_unicodeutils.py
  94. BIN
      desktop/core/ext-py/xlwt-1.3.0/tests/unicode1.xls
  95. 11 0
      desktop/core/ext-py/xlwt-1.3.0/tests/utils.py
  96. BIN
      desktop/core/ext-py/xlwt-1.3.0/tests/xlwt_easyxf_simple_demo.xls
  97. 13 0
      desktop/core/ext-py/xlwt-1.3.0/tox.ini
  98. 2457 0
      desktop/core/ext-py/xlwt-1.3.0/xlwt/BIFFRecords.py
  99. 275 0
      desktop/core/ext-py/xlwt-1.3.0/xlwt/Bitmap.py
  100. 244 0
      desktop/core/ext-py/xlwt-1.3.0/xlwt/Cell.py

+ 1 - 0
desktop/core/ext-py/unicodecsv-0.14.1/MANIFEST.in

@@ -0,0 +1 @@
+include README.rst

+ 48 - 0
desktop/core/ext-py/unicodecsv-0.14.1/PKG-INFO

@@ -0,0 +1,48 @@
+Metadata-Version: 1.1
+Name: unicodecsv
+Version: 0.14.1
+Summary: Python2's stdlib csv module is nice, but it doesn't support unicode. This module is a drop-in replacement which *does*.
+Home-page: https://github.com/jdunck/python-unicodecsv
+Author: Jeremy Dunck
+Author-email: jdunck@gmail.com
+License: BSD License
+Description: unicodecsv
+        ==========
+        
+        The unicodecsv is a drop-in replacement for Python 2.7's csv module which supports unicode strings without a hassle.  Supported versions are python 2.7, 3.3, 3.4, 3.5, and pypy 2.4.0.
+        
+        More fully
+        ----------
+        
+        Python 2's csv module doesn't easily deal with unicode strings, leading to the dreaded "'ascii' codec can't encode characters in position ..." exception.
+        
+        You can work around it by encoding everything just before calling write (or just after read), but why not add support to the serializer?
+        
+        .. code-block:: pycon
+        
+           >>> import unicodecsv as csv
+           >>> from io import BytesIO
+           >>> f = BytesIO()
+           >>> w = csv.writer(f, encoding='utf-8')
+           >>> _ = w.writerow((u'é', u'ñ'))
+           >>> _ = f.seek(0)
+           >>> r = csv.reader(f, encoding='utf-8')
+           >>> next(r) == [u'é', u'ñ']
+           True
+        
+        Note that unicodecsv expects a bytestream, not unicode -- so there's no need to use `codecs.open` or similar wrappers.  Plain `open(..., 'rb')` will do.
+        
+        (Version 0.14.0 dropped support for python 2.6, but 0.14.1 added it back.  See c0b7655248c4249 for the mistaken, breaking change.)
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Natural Language :: English
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Programming Language :: Python :: Implementation :: CPython

+ 27 - 0
desktop/core/ext-py/unicodecsv-0.14.1/README.rst

@@ -0,0 +1,27 @@
+unicodecsv
+==========
+
+The unicodecsv is a drop-in replacement for Python 2.7's csv module which supports unicode strings without a hassle.  Supported versions are python 2.7, 3.3, 3.4, 3.5, and pypy 2.4.0.
+
+More fully
+----------
+
+Python 2's csv module doesn't easily deal with unicode strings, leading to the dreaded "'ascii' codec can't encode characters in position ..." exception.
+
+You can work around it by encoding everything just before calling write (or just after read), but why not add support to the serializer?
+
+.. code-block:: pycon
+
+   >>> import unicodecsv as csv
+   >>> from io import BytesIO
+   >>> f = BytesIO()
+   >>> w = csv.writer(f, encoding='utf-8')
+   >>> _ = w.writerow((u'é', u'ñ'))
+   >>> _ = f.seek(0)
+   >>> r = csv.reader(f, encoding='utf-8')
+   >>> next(r) == [u'é', u'ñ']
+   True
+
+Note that unicodecsv expects a bytestream, not unicode -- so there's no need to use `codecs.open` or similar wrappers.  Plain `open(..., 'rb')` will do.
+
+(Version 0.14.0 dropped support for python 2.6, but 0.14.1 added it back.  See c0b7655248c4249 for the mistaken, breaking change.)

+ 5 - 0
desktop/core/ext-py/unicodecsv-0.14.1/setup.cfg

@@ -0,0 +1,5 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+

+ 31 - 0
desktop/core/ext-py/unicodecsv-0.14.1/setup.py

@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+import os
+from setuptools import setup, find_packages
+
+version = __import__('unicodecsv').__version__
+
+setup(
+    name='unicodecsv',
+    version=version,
+    description="Python2's stdlib csv module is nice, but it doesn't support unicode. This module is a drop-in replacement which *does*.",
+    long_description=open(os.path.join(os.path.dirname(__file__), 'README.rst'), 'rb').read().decode('utf-8'),
+    author='Jeremy Dunck',
+    author_email='jdunck@gmail.com',
+    url='https://github.com/jdunck/python-unicodecsv',
+    packages=find_packages(),
+    tests_require=['unittest2>=0.5.1'],
+    test_suite='runtests.get_suite',
+    license='BSD License',
+    classifiers=['Development Status :: 5 - Production/Stable',
+                'Intended Audience :: Developers',
+                'License :: OSI Approved :: BSD License',
+                'Natural Language :: English',
+                'Programming Language :: Python :: 2.6',
+                'Programming Language :: Python :: 2.7',
+                'Programming Language :: Python :: 3.3',
+                'Programming Language :: Python :: 3.4',
+                'Programming Language :: Python :: 3.5',
+                'Programming Language :: Python :: Implementation :: PyPy',
+                'Programming Language :: Python :: Implementation :: CPython',],
+)
+

+ 11 - 0
desktop/core/ext-py/unicodecsv-0.14.1/unicodecsv/__init__.py

@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# http://semver.org/
+import sys
+
+if sys.version_info >= (3, 0):
+    from unicodecsv.py3 import *
+else:
+    from unicodecsv.py2 import *
+
+VERSION = (0, 14, 1)
+__version__ = ".".join(map(str, VERSION))

+ 223 - 0
desktop/core/ext-py/unicodecsv-0.14.1/unicodecsv/py2.py

@@ -0,0 +1,223 @@
+# -*- coding: utf-8 -*-
+import csv
+import numbers
+
+from itertools import izip
+
+pass_throughs = [
+    'register_dialect',
+    'unregister_dialect',
+    'get_dialect',
+    'list_dialects',
+    'field_size_limit',
+    'Dialect',
+    'excel',
+    'excel_tab',
+    'Sniffer',
+    'QUOTE_ALL',
+    'QUOTE_MINIMAL',
+    'QUOTE_NONNUMERIC',
+    'QUOTE_NONE',
+    'Error'
+]
+__all__ = [
+    'reader',
+    'writer',
+    'DictReader',
+    'DictWriter',
+] + pass_throughs
+
+for prop in pass_throughs:
+    globals()[prop] = getattr(csv, prop)
+
+
+def _stringify(s, encoding, errors):
+    if s is None:
+        return ''
+    if isinstance(s, unicode):
+        return s.encode(encoding, errors)
+    elif isinstance(s, numbers.Number):
+        pass  # let csv.QUOTE_NONNUMERIC do its thing.
+    elif not isinstance(s, str):
+        s = str(s)
+    return s
+
+
+def _stringify_list(l, encoding, errors='strict'):
+    try:
+        return [_stringify(s, encoding, errors) for s in iter(l)]
+    except TypeError as e:
+        raise csv.Error(str(e))
+
+
+def _unicodify(s, encoding):
+    if s is None:
+        return None
+    if isinstance(s, (unicode, int, float)):
+        return s
+    elif isinstance(s, str):
+        return s.decode(encoding)
+    return s
+
+
+class UnicodeWriter(object):
+    """
+    >>> import unicodecsv
+    >>> from cStringIO import StringIO
+    >>> f = StringIO()
+    >>> w = unicodecsv.writer(f, encoding='utf-8')
+    >>> w.writerow((u'é', u'ñ'))
+    >>> f.seek(0)
+    >>> r = unicodecsv.reader(f, encoding='utf-8')
+    >>> row = r.next()
+    >>> row[0] == u'é'
+    True
+    >>> row[1] == u'ñ'
+    True
+    """
+    def __init__(self, f, dialect=csv.excel, encoding='utf-8', errors='strict',
+                 *args, **kwds):
+        self.encoding = encoding
+        self.writer = csv.writer(f, dialect, *args, **kwds)
+        self.encoding_errors = errors
+
+    def writerow(self, row):
+        return self.writer.writerow(
+                _stringify_list(row, self.encoding, self.encoding_errors))
+
+    def writerows(self, rows):
+        for row in rows:
+            self.writerow(row)
+
+    @property
+    def dialect(self):
+        return self.writer.dialect
+writer = UnicodeWriter
+
+
+class UnicodeReader(object):
+    def __init__(self, f, dialect=None, encoding='utf-8', errors='strict',
+                 **kwds):
+
+        format_params = ['delimiter', 'doublequote', 'escapechar',
+                         'lineterminator', 'quotechar', 'quoting',
+                         'skipinitialspace']
+
+        if dialect is None:
+            if not any([kwd_name in format_params
+                        for kwd_name in kwds.keys()]):
+                dialect = csv.excel
+        self.reader = csv.reader(f, dialect, **kwds)
+        self.encoding = encoding
+        self.encoding_errors = errors
+        self._parse_numerics = bool(
+            self.dialect.quoting & csv.QUOTE_NONNUMERIC)
+
+    def next(self):
+        row = self.reader.next()
+        encoding = self.encoding
+        encoding_errors = self.encoding_errors
+        unicode_ = unicode
+        if self._parse_numerics:
+            float_ = float
+            return [(value if isinstance(value, float_) else
+                    unicode_(value, encoding, encoding_errors))
+                    for value in row]
+        else:
+            return [unicode_(value, encoding, encoding_errors)
+                    for value in row]
+
+    def __iter__(self):
+        return self
+
+    @property
+    def dialect(self):
+        return self.reader.dialect
+
+    @property
+    def line_num(self):
+        return self.reader.line_num
+reader = UnicodeReader
+
+
+class DictWriter(csv.DictWriter):
+    """
+    >>> from cStringIO import StringIO
+    >>> f = StringIO()
+    >>> w = DictWriter(f, ['a', u'ñ', 'b'], restval=u'î')
+    >>> w.writerow({'a':'1', u'ñ':'2'})
+    >>> w.writerow({'a':'1', u'ñ':'2', 'b':u'ø'})
+    >>> w.writerow({'a':u'é', u'ñ':'2'})
+    >>> f.seek(0)
+    >>> r = DictReader(f, fieldnames=['a', u'ñ'], restkey='r')
+    >>> r.next() == {'a': u'1', u'ñ':'2', 'r': [u'î']}
+    True
+    >>> r.next() == {'a': u'1', u'ñ':'2', 'r': [u'\xc3\xb8']}
+    True
+    >>> r.next() == {'a': u'\xc3\xa9', u'ñ':'2', 'r': [u'\xc3\xae']}
+    True
+    """
+    def __init__(self, csvfile, fieldnames, restval='',
+                 extrasaction='raise', dialect='excel', encoding='utf-8',
+                 errors='strict', *args, **kwds):
+        self.encoding = encoding
+        csv.DictWriter.__init__(self, csvfile, fieldnames, restval,
+                                extrasaction, dialect, *args, **kwds)
+        self.writer = UnicodeWriter(csvfile, dialect, encoding=encoding,
+                                    errors=errors, *args, **kwds)
+        self.encoding_errors = errors
+
+    def writeheader(self):
+        header = dict(zip(self.fieldnames, self.fieldnames))
+        self.writerow(header)
+
+
+class DictReader(csv.DictReader):
+    """
+    >>> from cStringIO import StringIO
+    >>> f = StringIO()
+    >>> w = DictWriter(f, fieldnames=['name', 'place'])
+    >>> w.writerow({'name': 'Cary Grant', 'place': 'hollywood'})
+    >>> w.writerow({'name': 'Nathan Brillstone', 'place': u'øLand'})
+    >>> w.writerow({'name': u'Will ø. Unicoder', 'place': u'éSpandland'})
+    >>> f.seek(0)
+    >>> r = DictReader(f, fieldnames=['name', 'place'])
+    >>> print r.next() == {'name': 'Cary Grant', 'place': 'hollywood'}
+    True
+    >>> print r.next() == {'name': 'Nathan Brillstone', 'place': u'øLand'}
+    True
+    >>> print r.next() == {'name': u'Will ø. Unicoder', 'place': u'éSpandland'}
+    True
+    """
+    def __init__(self, csvfile, fieldnames=None, restkey=None, restval=None,
+                 dialect='excel', encoding='utf-8', errors='strict', *args,
+                 **kwds):
+        if fieldnames is not None:
+            fieldnames = _stringify_list(fieldnames, encoding)
+        csv.DictReader.__init__(self, csvfile, fieldnames, restkey, restval,
+                                dialect, *args, **kwds)
+        self.reader = UnicodeReader(csvfile, dialect, encoding=encoding,
+                                    errors=errors, *args, **kwds)
+        if fieldnames is None and not hasattr(csv.DictReader, 'fieldnames'):
+            # Python 2.5 fieldnames workaround.
+            # See http://bugs.python.org/issue3436
+            reader = UnicodeReader(csvfile, dialect, encoding=encoding,
+                                   *args, **kwds)
+            self.fieldnames = _stringify_list(reader.next(), reader.encoding)
+
+        if self.fieldnames is not None:
+            self.unicode_fieldnames = [_unicodify(f, encoding) for f in
+                                       self.fieldnames]
+        else:
+            self.unicode_fieldnames = []
+
+        self.unicode_restkey = _unicodify(restkey, encoding)
+
+    def next(self):
+        row = csv.DictReader.next(self)
+        result = dict((uni_key, row[str_key]) for (str_key, uni_key) in
+                      izip(self.fieldnames, self.unicode_fieldnames))
+        rest = row.get(self.restkey)
+        if rest:
+            result[self.unicode_restkey] = rest
+        return result

+ 95 - 0
desktop/core/ext-py/unicodecsv-0.14.1/unicodecsv/py3.py

@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+import csv
+from csv import *
+
+
+class _UnicodeWriteWrapper(object):
+    """Simple write() wrapper that converts unicode to bytes."""
+
+    def __init__(self, binary, encoding, errors):
+        self.binary = binary
+        self.encoding = encoding
+        self.errors = errors
+
+    def write(self, string):
+        return self.binary.write(string.encode(self.encoding, self.errors))
+
+
+class UnicodeWriter(object):
+    def __init__(self, f, dialect=csv.excel, encoding='utf-8', errors='strict',
+                 *args, **kwds):
+        if f is None:
+            raise TypeError
+
+        f = _UnicodeWriteWrapper(f, encoding=encoding, errors=errors)
+        self.writer = csv.writer(f, dialect, *args, **kwds)
+
+    def writerow(self, row):
+        return self.writer.writerow(row)
+
+    def writerows(self, rows):
+        return self.writer.writerows(rows)
+
+    @property
+    def dialect(self):
+        return self.writer.dialect
+
+
+class UnicodeReader(object):
+    def __init__(self, f, dialect=None, encoding='utf-8', errors='strict',
+                 **kwds):
+
+        format_params = ['delimiter', 'doublequote', 'escapechar',
+                     'lineterminator', 'quotechar', 'quoting',
+                     'skipinitialspace']
+
+        if dialect is None:
+            if not any([kwd_name in format_params
+                        for kwd_name in kwds.keys()]):
+                dialect = csv.excel
+
+        f = (bs.decode(encoding, errors=errors) for bs in f)
+        self.reader = csv.reader(f, dialect, **kwds)
+
+    def __next__(self):
+        return self.reader.__next__()
+
+    def __iter__(self):
+        return self
+
+    @property
+    def dialect(self):
+        return self.reader.dialect
+
+    @property
+    def line_num(self):
+        return self.reader.line_num
+
+
+writer = UnicodeWriter
+reader = UnicodeReader
+
+
+class DictWriter(csv.DictWriter):
+    def __init__(self, csvfile, fieldnames, restval='',
+                 extrasaction='raise', dialect='excel', encoding='utf-8',
+                 errors='strict', *args, **kwds):
+        super().__init__(csvfile, fieldnames, restval,
+                         extrasaction, dialect, *args, **kwds)
+        self.writer = UnicodeWriter(csvfile, dialect, encoding=encoding,
+                                    errors=errors, *args, **kwds)
+        self.encoding_errors = errors
+
+    def writeheader(self):
+        header = dict(zip(self.fieldnames, self.fieldnames))
+        self.writerow(header)
+
+
+class DictReader(csv.DictReader):
+    def __init__(self, csvfile, fieldnames=None, restkey=None, restval=None,
+                 dialect='excel', encoding='utf-8', errors='strict', *args,
+                 **kwds):
+        csv.DictReader.__init__(self, csvfile, fieldnames, restkey, restval,
+                                dialect, *args, **kwds)
+        self.reader = UnicodeReader(csvfile, dialect, encoding=encoding,
+                                    errors=errors, *args, **kwds)

+ 919 - 0
desktop/core/ext-py/unicodecsv-0.14.1/unicodecsv/test.py

@@ -0,0 +1,919 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2001,2002 Python Software Foundation
+# csv package unit tests
+
+import array
+import decimal
+import os
+import string
+import sys
+import tempfile
+import unittest2 as unittest
+from codecs import EncodedFile
+from io import BytesIO
+
+import unicodecsv as csv
+
+try:
+    # Python 2
+    chr = unichr
+except:
+    pass
+
+
+# pypy and cpython differ under which exception is raised under some
+# circumstances e.g. whether a module is written in C or not.
+py_compat_exc = (TypeError, AttributeError)
+
+
+class Test_Csv(unittest.TestCase):
+    """
+    Test the underlying C csv parser in ways that are not appropriate
+    from the high level interface. Further tests of this nature are done
+    in TestDialectRegistry.
+    """
+    def _test_arg_valid(self, ctor, arg):
+        self.assertRaises(py_compat_exc, ctor)
+        self.assertRaises(py_compat_exc, ctor, None)
+        self.assertRaises(py_compat_exc, ctor, arg, bad_attr=0)
+        self.assertRaises(py_compat_exc, ctor, arg, delimiter=0)
+        self.assertRaises(py_compat_exc, ctor, arg, delimiter='XX')
+        self.assertRaises(csv.Error, ctor, arg, 'foo')
+        self.assertRaises(py_compat_exc, ctor, arg, delimiter=None)
+        self.assertRaises(py_compat_exc, ctor, arg, delimiter=1)
+        self.assertRaises(py_compat_exc, ctor, arg, quotechar=1)
+        self.assertRaises(py_compat_exc, ctor, arg, lineterminator=None)
+        self.assertRaises(py_compat_exc, ctor, arg, lineterminator=1)
+        self.assertRaises(py_compat_exc, ctor, arg, quoting=None)
+        self.assertRaises(py_compat_exc, ctor, arg,
+                          quoting=csv.QUOTE_ALL, quotechar='')
+        self.assertRaises(py_compat_exc, ctor, arg,
+                          quoting=csv.QUOTE_ALL, quotechar=None)
+
+    def test_reader_arg_valid(self):
+        self._test_arg_valid(csv.reader, [])
+
+    def test_writer_arg_valid(self):
+        self._test_arg_valid(csv.writer, BytesIO())
+
+    def _test_default_attrs(self, ctor, *args):
+        obj = ctor(*args)
+        # Check defaults
+        self.assertEqual(obj.dialect.delimiter, ',')
+        self.assertEqual(obj.dialect.doublequote, True)
+        self.assertEqual(obj.dialect.escapechar, None)
+        self.assertEqual(obj.dialect.lineterminator, "\r\n")
+        self.assertEqual(obj.dialect.quotechar, '"')
+        self.assertEqual(obj.dialect.quoting, csv.QUOTE_MINIMAL)
+        self.assertEqual(obj.dialect.skipinitialspace, False)
+        self.assertEqual(obj.dialect.strict, False)
+        # Try deleting or changing attributes (they are read-only)
+        self.assertRaises(py_compat_exc, delattr,
+                          obj.dialect, 'delimiter')
+        self.assertRaises(py_compat_exc, setattr,
+                          obj.dialect, 'delimiter', ':')
+        self.assertRaises(py_compat_exc, delattr,
+                          obj.dialect, 'quoting')
+        self.assertRaises(py_compat_exc, setattr,
+                          obj.dialect, 'quoting', None)
+
+    def test_reader_attrs(self):
+        self._test_default_attrs(csv.reader, [])
+
+    def test_writer_attrs(self):
+        self._test_default_attrs(csv.writer, BytesIO())
+
+    def _test_kw_attrs(self, ctor, *args):
+        # Now try with alternate options
+        kwargs = dict(delimiter=':', doublequote=False, escapechar='\\',
+                      lineterminator='\r', quotechar='*',
+                      quoting=csv.QUOTE_NONE, skipinitialspace=True,
+                      strict=True)
+        obj = ctor(*args, **kwargs)
+        self.assertEqual(obj.dialect.delimiter, ':')
+        self.assertEqual(obj.dialect.doublequote, False)
+        self.assertEqual(obj.dialect.escapechar, '\\')
+        self.assertEqual(obj.dialect.lineterminator, "\r")
+        self.assertEqual(obj.dialect.quotechar, '*')
+        self.assertEqual(obj.dialect.quoting, csv.QUOTE_NONE)
+        self.assertEqual(obj.dialect.skipinitialspace, True)
+        self.assertEqual(obj.dialect.strict, True)
+
+    def test_reader_kw_attrs(self):
+        self._test_kw_attrs(csv.reader, [])
+
+    def test_writer_kw_attrs(self):
+        self._test_kw_attrs(csv.writer, BytesIO())
+
+    def _test_dialect_attrs(self, ctor, *args):
+        # Now try with dialect-derived options
+        class dialect:
+            delimiter = '-'
+            doublequote = False
+            escapechar = '^'
+            lineterminator = '$'
+            quotechar = '#'
+            quoting = csv.QUOTE_ALL
+            skipinitialspace = True
+            strict = False
+        args = args + (dialect,)
+        obj = ctor(*args)
+        self.assertEqual(obj.dialect.delimiter, '-')
+        self.assertEqual(obj.dialect.doublequote, False)
+        self.assertEqual(obj.dialect.escapechar, '^')
+        self.assertEqual(obj.dialect.lineterminator, "$")
+        self.assertEqual(obj.dialect.quotechar, '#')
+        self.assertEqual(obj.dialect.quoting, csv.QUOTE_ALL)
+        self.assertEqual(obj.dialect.skipinitialspace, True)
+        self.assertEqual(obj.dialect.strict, False)
+
+    def test_reader_dialect_attrs(self):
+        self._test_dialect_attrs(csv.reader, [])
+
+    def test_writer_dialect_attrs(self):
+        self._test_dialect_attrs(csv.writer, BytesIO())
+
+    def _write_test(self, fields, expect, **kwargs):
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            writer = csv.writer(fileobj, **kwargs)
+            writer.writerow(fields)
+            fileobj.seek(0)
+            self.assertEqual(fileobj.read(),
+                             expect + writer.dialect.lineterminator.encode('utf-8'))
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    def test_write_arg_valid(self):
+        import sys
+        pypy3 = hasattr(sys, 'pypy_version_info') and sys.version_info.major == 3
+
+        self.assertRaises(TypeError if pypy3 else csv.Error, self._write_test, None, '')
+        self._write_test((), b'')
+        self._write_test([None], b'""')
+        self.assertRaises(csv.Error, self._write_test,
+                          [None], None, quoting=csv.QUOTE_NONE)
+
+        # Check that exceptions are passed up the chain
+        class BadList:
+            def __len__(self):
+                return 10
+
+            def __getitem__(self, i):
+                if i > 2:
+                    raise IOError
+
+        self.assertRaises(IOError, self._write_test, BadList(), '')
+
+        class BadItem:
+            def __str__(self):
+                raise IOError
+
+        self.assertRaises(IOError, self._write_test, [BadItem()], '')
+
+    def test_write_bigfield(self):
+        # This exercises the buffer realloc functionality
+        bigstring = 'X' * 50000
+        self._write_test([bigstring, bigstring],
+                         b','.join([bigstring.encode('utf-8')] * 2))
+
+    def test_write_quoting(self):
+        self._write_test(['a', 1, 'p,q'], b'a,1,"p,q"')
+        self.assertRaises(csv.Error,
+                          self._write_test,
+                          ['a', 1, 'p,q'], b'a,1,p,q',
+                          quoting=csv.QUOTE_NONE)
+        self._write_test(['a', 1, 'p,q'], b'a,1,"p,q"',
+                         quoting=csv.QUOTE_MINIMAL)
+        self._write_test(['a', 1, 'p,q'], b'"a",1,"p,q"',
+                         quoting=csv.QUOTE_NONNUMERIC)
+        self._write_test(['a', 1, 'p,q'], b'"a","1","p,q"',
+                         quoting=csv.QUOTE_ALL)
+        self._write_test(['a\nb', 1], b'"a\nb","1"',
+                         quoting=csv.QUOTE_ALL)
+
+    def test_write_decimal(self):
+        self._write_test(['a', decimal.Decimal("1.1"), 'p,q'], b'"a",1.1,"p,q"',
+                         quoting=csv.QUOTE_NONNUMERIC)
+
+    def test_write_escape(self):
+        self._write_test(['a', 1, 'p,q'], b'a,1,"p,q"',
+                         escapechar='\\')
+        self.assertRaises(csv.Error,
+                          self._write_test,
+                          ['a', 1, 'p,"q"'], b'a,1,"p,\\"q\\""',
+                          escapechar=None, doublequote=False)
+        self._write_test(['a', 1, 'p,"q"'], b'a,1,"p,\\"q\\""',
+                         escapechar='\\', doublequote=False)
+        self._write_test(['"'], b'""""',
+                         escapechar='\\', quoting=csv.QUOTE_MINIMAL)
+        self._write_test(['"'], b'\\"',
+                         escapechar='\\', quoting=csv.QUOTE_MINIMAL,
+                         doublequote=False)
+        self._write_test(['"'], b'\\"',
+                         escapechar='\\', quoting=csv.QUOTE_NONE)
+        self._write_test(['a', 1, 'p,q'], b'a,1,p\\,q',
+                         escapechar='\\', quoting=csv.QUOTE_NONE)
+
+    def test_writerows(self):
+        class BrokenFile:
+            def write(self, buf):
+                raise IOError
+
+        writer = csv.writer(BrokenFile())
+        self.assertRaises(IOError, writer.writerows, [['a']])
+
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            writer = csv.writer(fileobj)
+            self.assertRaises(TypeError, writer.writerows, None)
+            writer.writerows([['a', 'b'], ['c', 'd']])
+            fileobj.seek(0)
+            self.assertEqual(fileobj.read(), b"a,b\r\nc,d\r\n")
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    def _read_test(self, input, expect, **kwargs):
+        reader = csv.reader(input, **kwargs)
+        result = list(reader)
+        self.assertEqual(result, expect)
+
+    def test_read_oddinputs(self):
+        self._read_test([], [])
+        self._read_test([b''], [[]])
+        self.assertRaises(csv.Error, self._read_test,
+                          [b'"ab"c'], None, strict=1)
+        # cannot handle null bytes for the moment
+        self.assertRaises(csv.Error, self._read_test,
+                          [b'ab\0c'], None, strict=1)
+        self._read_test([b'"ab"c'], [['abc']], doublequote=0)
+
+    def test_read_eol(self):
+        self._read_test([b'a,b'], [['a', 'b']])
+        self._read_test([b'a,b\n'], [['a', 'b']])
+        self._read_test([b'a,b\r\n'], [['a', 'b']])
+        self._read_test([b'a,b\r'], [['a', 'b']])
+        self.assertRaises(csv.Error, self._read_test, [b'a,b\rc,d'], [])
+        self.assertRaises(csv.Error, self._read_test, [b'a,b\nc,d'], [])
+        self.assertRaises(csv.Error, self._read_test, [b'a,b\r\nc,d'], [])
+
+    def test_read_escape(self):
+        self._read_test([b'a,\\b,c'], [['a', 'b', 'c']], escapechar='\\')
+        self._read_test([b'a,b\\,c'], [['a', 'b,c']], escapechar='\\')
+        self._read_test([b'a,"b\\,c"'], [['a', 'b,c']], escapechar='\\')
+        self._read_test([b'a,"b,\\c"'], [['a', 'b,c']], escapechar='\\')
+        self._read_test([b'a,"b,c\\""'], [['a', 'b,c"']], escapechar='\\')
+        self._read_test([b'a,"b,c"\\'], [['a', 'b,c\\']], escapechar='\\')
+
+    def test_read_quoting(self):
+        self._read_test([b'1,",3,",5'], [['1', ',3,', '5']])
+        self._read_test([b'1,",3,",5'], [['1', '"', '3', '"', '5']],
+                        quotechar=None, escapechar='\\')
+        self._read_test([b'1,",3,",5'], [['1', '"', '3', '"', '5']],
+                        quoting=csv.QUOTE_NONE, escapechar='\\')
+        # will this fail where locale uses comma for decimals?
+        self._read_test([b',3,"5",7.3, 9'], [['', 3, '5', 7.3, 9]],
+                        quoting=csv.QUOTE_NONNUMERIC)
+        self._read_test([b'"a\nb", 7'], [['a\nb', ' 7']])
+        self.assertRaises(ValueError, self._read_test,
+                          [b'abc,3'], [[]],
+                          quoting=csv.QUOTE_NONNUMERIC)
+
+    def test_read_linenum(self):
+        for r in (csv.reader([b'line,1', b'line,2', b'line,3']),
+                  csv.DictReader([b'line,1', b'line,2', b'line,3'],
+                                 fieldnames=['a', 'b', 'c'])):
+            self.assertEqual(r.line_num, 0)
+            next(r)
+            self.assertEqual(r.line_num, 1)
+            next(r)
+            self.assertEqual(r.line_num, 2)
+            next(r)
+            self.assertEqual(r.line_num, 3)
+            self.assertRaises(StopIteration, next, r)
+            self.assertEqual(r.line_num, 3)
+
+    def test_roundtrip_quoteed_newlines(self):
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            writer = csv.writer(fileobj)
+            self.assertRaises(TypeError, writer.writerows, None)
+            rows = [['a\nb', 'b'], ['c', 'x\r\nd']]
+            writer.writerows(rows)
+            fileobj.seek(0)
+            for i, row in enumerate(csv.reader(fileobj)):
+                self.assertEqual(row, rows[i])
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+
+class TestDialectRegistry(unittest.TestCase):
+    def test_registry_badargs(self):
+        self.assertRaises(TypeError, csv.list_dialects, None)
+        self.assertRaises(TypeError, csv.get_dialect)
+        self.assertRaises(csv.Error, csv.get_dialect, None)
+        self.assertRaises(csv.Error, csv.get_dialect, "nonesuch")
+        self.assertRaises(TypeError, csv.unregister_dialect)
+        self.assertRaises(csv.Error, csv.unregister_dialect, None)
+        self.assertRaises(csv.Error, csv.unregister_dialect, "nonesuch")
+        self.assertRaises(TypeError, csv.register_dialect, None)
+        self.assertRaises(TypeError, csv.register_dialect, None, None)
+        self.assertRaises(TypeError, csv.register_dialect, "nonesuch", 0, 0)
+        self.assertRaises(TypeError, csv.register_dialect, "nonesuch",
+                          badargument=None)
+        self.assertRaises(TypeError, csv.register_dialect, "nonesuch",
+                          quoting=None)
+        self.assertRaises(TypeError, csv.register_dialect, [])
+
+    def test_registry(self):
+        class myexceltsv(csv.excel):
+            delimiter = "\t"
+        name = "myexceltsv"
+        expected_dialects = csv.list_dialects() + [name]
+        expected_dialects.sort()
+        csv.register_dialect(name, myexceltsv)
+        try:
+            self.assertEqual(csv.get_dialect(name).delimiter, '\t')
+            got_dialects = csv.list_dialects()
+            got_dialects.sort()
+            self.assertEqual(expected_dialects, got_dialects)
+        finally:
+            csv.unregister_dialect(name)
+
+    def test_register_kwargs(self):
+        name = 'fedcba'
+        csv.register_dialect(name, delimiter=';')
+        try:
+            self.assertNotEqual(csv.get_dialect(name).delimiter, '\t')
+            self.assertEqual(list(csv.reader([b'X;Y;Z'], name)), [[u'X', u'Y', u'Z']])
+        finally:
+            csv.unregister_dialect(name)
+
+    def test_incomplete_dialect(self):
+        class myexceltsv(csv.Dialect):
+            delimiter = "\t"
+        self.assertRaises(csv.Error, myexceltsv)
+
+    def test_space_dialect(self):
+        class space(csv.excel):
+            delimiter = " "
+            quoting = csv.QUOTE_NONE
+            escapechar = "\\"
+
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            fileobj.write(b"abc def\nc1ccccc1 benzene\n")
+            fileobj.seek(0)
+            rdr = csv.reader(fileobj, dialect=space())
+            self.assertEqual(next(rdr), ["abc", "def"])
+            self.assertEqual(next(rdr), ["c1ccccc1", "benzene"])
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    def test_dialect_apply(self):
+        class testA(csv.excel):
+            delimiter = "\t"
+
+        class testB(csv.excel):
+            delimiter = ":"
+
+        class testC(csv.excel):
+            delimiter = "|"
+
+        csv.register_dialect('testC', testC)
+        try:
+            fd, name = tempfile.mkstemp()
+            fileobj = os.fdopen(fd, "w+b")
+            try:
+                writer = csv.writer(fileobj)
+                writer.writerow([1, 2, 3])
+                fileobj.seek(0)
+                self.assertEqual(fileobj.read(), b"1,2,3\r\n")
+            finally:
+                fileobj.close()
+                os.unlink(name)
+
+            fd, name = tempfile.mkstemp()
+            fileobj = os.fdopen(fd, "w+b")
+            try:
+                writer = csv.writer(fileobj, testA)
+                writer.writerow([1, 2, 3])
+                fileobj.seek(0)
+                self.assertEqual(fileobj.read(), b"1\t2\t3\r\n")
+            finally:
+                fileobj.close()
+                os.unlink(name)
+
+            fd, name = tempfile.mkstemp()
+            fileobj = os.fdopen(fd, "w+b")
+            try:
+                writer = csv.writer(fileobj, dialect=testB())
+                writer.writerow([1, 2, 3])
+                fileobj.seek(0)
+                self.assertEqual(fileobj.read(), b"1:2:3\r\n")
+            finally:
+                fileobj.close()
+                os.unlink(name)
+
+            fd, name = tempfile.mkstemp()
+            fileobj = os.fdopen(fd, "w+b")
+            try:
+                writer = csv.writer(fileobj, dialect='testC')
+                writer.writerow([1, 2, 3])
+                fileobj.seek(0)
+                self.assertEqual(fileobj.read(), b"1|2|3\r\n")
+            finally:
+                fileobj.close()
+                os.unlink(name)
+
+            fd, name = tempfile.mkstemp()
+            fileobj = os.fdopen(fd, "w+b")
+            try:
+                writer = csv.writer(fileobj, dialect=testA, delimiter=';')
+                writer.writerow([1, 2, 3])
+                fileobj.seek(0)
+                self.assertEqual(fileobj.read(), b"1;2;3\r\n")
+            finally:
+                fileobj.close()
+                os.unlink(name)
+
+        finally:
+            csv.unregister_dialect('testC')
+
+    def test_bad_dialect(self):
+        # Unknown parameter
+        self.assertRaises(TypeError, csv.reader, [], bad_attr=0)
+        # Bad values
+        self.assertRaises(TypeError, csv.reader, [], delimiter=None)
+        self.assertRaises(TypeError, csv.reader, [], quoting=-1)
+        self.assertRaises(TypeError, csv.reader, [], quoting=100)
+
+
+class TestCsvBase(unittest.TestCase):
+    def readerAssertEqual(self, input, expected_result):
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            fileobj.write(input)
+            fileobj.seek(0)
+            reader = csv.reader(fileobj, dialect=self.dialect)
+            fields = list(reader)
+            self.assertEqual(fields, expected_result)
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    def writerAssertEqual(self, input, expected_result):
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            writer = csv.writer(fileobj, dialect=self.dialect)
+            writer.writerows(input)
+            fileobj.seek(0)
+            self.assertEqual(fileobj.read(), expected_result)
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+
+class TestDialectExcel(TestCsvBase):
+    dialect = 'excel'
+
+    def test_single(self):
+        self.readerAssertEqual(b'abc', [['abc']])
+
+    def test_simple(self):
+        self.readerAssertEqual(b'1,2,3,4,5', [['1', '2', '3', '4', '5']])
+
+    def test_blankline(self):
+        self.readerAssertEqual(b'', [])
+
+    def test_empty_fields(self):
+        self.readerAssertEqual(b',', [['', '']])
+
+    def test_singlequoted(self):
+        self.readerAssertEqual(b'""', [['']])
+
+    def test_singlequoted_left_empty(self):
+        self.readerAssertEqual(b'"",', [['', '']])
+
+    def test_singlequoted_right_empty(self):
+        self.readerAssertEqual(b',""', [['', '']])
+
+    def test_single_quoted_quote(self):
+        self.readerAssertEqual(b'""""', [['"']])
+
+    def test_quoted_quotes(self):
+        self.readerAssertEqual(b'""""""', [['""']])
+
+    def test_inline_quote(self):
+        self.readerAssertEqual(b'a""b', [['a""b']])
+
+    def test_inline_quotes(self):
+        self.readerAssertEqual(b'a"b"c', [['a"b"c']])
+
+    def test_quotes_and_more(self):
+        # Excel would never write a field containing '"a"b', but when
+        # reading one, it will return 'ab'.
+        self.readerAssertEqual(b'"a"b', [['ab']])
+
+    def test_lone_quote(self):
+        self.readerAssertEqual(b'a"b', [['a"b']])
+
+    def test_quote_and_quote(self):
+        # Excel would never write a field containing '"a" "b"', but when
+        # reading one, it will return 'a "b"'.
+        self.readerAssertEqual(b'"a" "b"', [['a "b"']])
+
+    def test_space_and_quote(self):
+        self.readerAssertEqual(b' "a"', [[' "a"']])
+
+    def test_quoted(self):
+        self.readerAssertEqual(b'1,2,3,"I think, therefore I am",5,6',
+                               [['1', '2', '3',
+                                 'I think, therefore I am',
+                                 '5', '6']])
+
+    def test_quoted_quote(self):
+        value = b'1,2,3,"""I see,"" said the blind man","as he picked up his hammer and saw"'
+        self.readerAssertEqual(value,
+                               [['1', '2', '3',
+                                 '"I see," said the blind man',
+                                 'as he picked up his hammer and saw']])
+
+    def test_quoted_nl(self):
+        input = b'''\
+1,2,3,"""I see,""
+said the blind man","as he picked up his
+hammer and saw"
+9,8,7,6'''
+        self.readerAssertEqual(input,
+                               [['1', '2', '3',
+                                   '"I see,"\nsaid the blind man',
+                                   'as he picked up his\nhammer and saw'],
+                                ['9', '8', '7', '6']])
+
+    def test_dubious_quote(self):
+        self.readerAssertEqual(b'12,12,1",', [['12', '12', '1"', '']])
+
+    def test_null(self):
+        self.writerAssertEqual([], b'')
+
+    def test_single_writer(self):
+        self.writerAssertEqual([['abc']], b'abc\r\n')
+
+    def test_simple_writer(self):
+        self.writerAssertEqual([[1, 2, 'abc', 3, 4]],
+                               b'1,2,abc,3,4\r\n')
+
+    def test_quotes(self):
+        self.writerAssertEqual([[1, 2, 'a"bc"', 3, 4]],
+                               b'1,2,"a""bc""",3,4\r\n')
+
+    def test_quote_fieldsep(self):
+        self.writerAssertEqual([['abc,def']],
+                               b'"abc,def"\r\n')
+
+    def test_newlines(self):
+        self.writerAssertEqual([[1, 2, 'a\nbc', 3, 4]],
+                               b'1,2,"a\nbc",3,4\r\n')
+
+
+class EscapedExcel(csv.excel):
+    quoting = csv.QUOTE_NONE
+    escapechar = '\\'
+
+
+class TestEscapedExcel(TestCsvBase):
+    dialect = EscapedExcel()
+
+    def test_escape_fieldsep(self):
+        self.writerAssertEqual([['abc,def']], b'abc\\,def\r\n')
+
+    def test_read_escape_fieldsep(self):
+        self.readerAssertEqual(b'abc\\,def\r\n', [['abc,def']])
+
+
+class QuotedEscapedExcel(csv.excel):
+    quoting = csv.QUOTE_NONNUMERIC
+    escapechar = '\\'
+
+
+class TestQuotedEscapedExcel(TestCsvBase):
+    dialect = QuotedEscapedExcel()
+
+    def test_write_escape_fieldsep(self):
+        self.writerAssertEqual([['abc,def']], b'"abc,def"\r\n')
+
+    def test_read_escape_fieldsep(self):
+        self.readerAssertEqual(b'"abc\\,def"\r\n', [['abc,def']])
+
+
+class TestDictFields(unittest.TestCase):
+    # "long" means the row is longer than the number of fieldnames
+    # "short" means there are fewer elements in the row than fieldnames
+    def test_write_simple_dict(self):
+        fd, name = tempfile.mkstemp()
+        fileobj = open(name, 'w+b')
+        try:
+            writer = csv.DictWriter(fileobj, fieldnames=["f1", "f2", "f3"])
+            writer.writeheader()
+            fileobj.seek(0)
+            self.assertEqual(fileobj.readline(), b"f1,f2,f3\r\n")
+            writer.writerow({"f1": 10, "f3": "abc"})
+            fileobj.seek(0)
+            fileobj.readline()  # header
+            self.assertEqual(fileobj.read(), b"10,,abc\r\n")
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    def test_write_unicode_header_dict(self):
+        fd, name = tempfile.mkstemp()
+        fileobj = open(name, 'w+b')
+        try:
+            writer = csv.DictWriter(fileobj, fieldnames=[u"ñ", u"ö"])
+            writer.writeheader()
+            fileobj.seek(0)
+            self.assertEqual(fileobj.readline().decode('utf-8'), u"ñ,ö\r\n")
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    def test_write_no_fields(self):
+        fileobj = BytesIO()
+        self.assertRaises(TypeError, csv.DictWriter, fileobj)
+
+    def test_read_dict_fields(self):
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            fileobj.write(b"1,2,abc\r\n")
+            fileobj.seek(0)
+            reader = csv.DictReader(fileobj,
+                                    fieldnames=["f1", "f2", "f3"])
+            self.assertEqual(next(reader),
+                             {"f1": '1', "f2": '2', "f3": 'abc'})
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    def test_read_dict_no_fieldnames(self):
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            fileobj.write(b"f1,f2,f3\r\n1,2,abc\r\n")
+            fileobj.seek(0)
+            reader = csv.DictReader(fileobj)
+            self.assertEqual(reader.fieldnames,
+                             ["f1", "f2", "f3"])
+            self.assertEqual(next(reader),
+                             {"f1": '1', "f2": '2', "f3": 'abc'})
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    # Two test cases to make sure existing ways of implicitly setting
+    # fieldnames continue to work.  Both arise from discussion in issue3436.
+    def test_read_dict_fieldnames_from_file(self):
+        fd, name = tempfile.mkstemp()
+        f = os.fdopen(fd, "w+b")
+        try:
+            f.write(b"f1,f2,f3\r\n1,2,abc\r\n")
+            f.seek(0)
+            reader = csv.DictReader(f, fieldnames=next(csv.reader(f)))
+            self.assertEqual(reader.fieldnames,
+                             ["f1", "f2", "f3"])
+            self.assertEqual(next(reader),
+                             {"f1": '1', "f2": '2', "f3": 'abc'})
+        finally:
+            f.close()
+            os.unlink(name)
+
+    def test_read_dict_fieldnames_chain(self):
+        import itertools
+        fd, name = tempfile.mkstemp()
+        f = os.fdopen(fd, "w+b")
+        try:
+            f.write(b"f1,f2,f3\r\n1,2,abc\r\n")
+            f.seek(0)
+            reader = csv.DictReader(f)
+            first = next(reader)
+            for row in itertools.chain([first], reader):
+                self.assertEqual(reader.fieldnames, ["f1", "f2", "f3"])
+                self.assertEqual(row, {"f1": '1', "f2": '2', "f3": 'abc'})
+        finally:
+            f.close()
+            os.unlink(name)
+
+    def test_read_long(self):
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            fileobj.write(b"1,2,abc,4,5,6\r\n")
+            fileobj.seek(0)
+            reader = csv.DictReader(fileobj,
+                                    fieldnames=["f1", "f2"])
+            self.assertEqual(next(reader), {"f1": '1', "f2": '2',
+                                             None: ["abc", "4", "5", "6"]})
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    def test_read_long_with_rest(self):
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            fileobj.write(b"1,2,abc,4,5,6\r\n")
+            fileobj.seek(0)
+            reader = csv.DictReader(fileobj,
+                                    fieldnames=["f1", "f2"], restkey="_rest")
+            self.assertEqual(next(reader), {"f1": '1', "f2": '2',
+                                             "_rest": ["abc", "4", "5", "6"]})
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    def test_read_long_with_rest_no_fieldnames(self):
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            fileobj.write(b"f1,f2\r\n1,2,abc,4,5,6\r\n")
+            fileobj.seek(0)
+            reader = csv.DictReader(fileobj, restkey="_rest")
+            self.assertEqual(reader.fieldnames, ["f1", "f2"])
+            self.assertEqual(next(reader), {"f1": '1', "f2": '2',
+                                             "_rest": ["abc", "4", "5", "6"]})
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    def test_read_short(self):
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            fileobj.write(b"1,2,abc,4,5,6\r\n1,2,abc\r\n")
+            fileobj.seek(0)
+            reader = csv.DictReader(fileobj,
+                                    fieldnames="1 2 3 4 5 6".split(),
+                                    restval="DEFAULT")
+            self.assertEqual(next(reader), {"1": '1', "2": '2', "3": 'abc',
+                                             "4": '4', "5": '5', "6": '6'})
+            self.assertEqual(next(reader), {"1": '1', "2": '2', "3": 'abc',
+                                             "4": 'DEFAULT', "5": 'DEFAULT',
+                                             "6": 'DEFAULT'})
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    def test_read_multi(self):
+        sample = [
+            b'2147483648,43.0e12,17,abc,def\r\n',
+            b'147483648,43.0e2,17,abc,def\r\n',
+            b'47483648,43.0,170,abc,def\r\n'
+            ]
+
+        reader = csv.DictReader(sample,
+                                fieldnames="i1 float i2 s1 s2".split())
+        self.assertEqual(next(reader), {"i1": '2147483648',
+                                         "float": '43.0e12',
+                                         "i2": '17',
+                                         "s1": 'abc',
+                                         "s2": 'def'})
+
+    def test_read_with_blanks(self):
+        reader = csv.DictReader([b"1,2,abc,4,5,6\r\n", b"\r\n",
+                                 b"1,2,abc,4,5,6\r\n"],
+                                fieldnames="1 2 3 4 5 6".split())
+        self.assertEqual(next(reader), {"1": '1', "2": '2', "3": 'abc',
+                                         "4": '4', "5": '5', "6": '6'})
+        self.assertEqual(next(reader), {"1": '1', "2": '2', "3": 'abc',
+                                         "4": '4', "5": '5', "6": '6'})
+
+    def test_read_semi_sep(self):
+        reader = csv.DictReader([b"1;2;abc;4;5;6\r\n"],
+                                fieldnames="1 2 3 4 5 6".split(),
+                                delimiter=';')
+        self.assertEqual(next(reader), {"1": '1', "2": '2', "3": 'abc',
+                                         "4": '4', "5": '5', "6": '6'})
+
+    def test_empty_file(self):
+        csv.DictReader(BytesIO())
+
+class TestArrayWrites(unittest.TestCase):
+    def test_int_write(self):
+        contents = [(20-i) for i in range(20)]
+        a = array.array('i', contents)
+
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            writer = csv.writer(fileobj, dialect="excel")
+            writer.writerow(a)
+            expected = b",".join([str(i).encode('utf-8') for i in a])+b"\r\n"
+            fileobj.seek(0)
+            self.assertEqual(fileobj.read(), expected)
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    def test_double_write(self):
+        contents = [(20-i)*0.1 for i in range(20)]
+        a = array.array('d', contents)
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            writer = csv.writer(fileobj, dialect="excel")
+            writer.writerow(a)
+            float_repr = str
+            if sys.version_info >= (2, 7, 3):
+                float_repr = repr
+            expected = b",".join([float_repr(i).encode('utf-8') for i in a])+b"\r\n"
+            fileobj.seek(0)
+            self.assertEqual(fileobj.read(), expected)
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    def test_float_write(self):
+        contents = [(20-i)*0.1 for i in range(20)]
+        a = array.array('f', contents)
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            writer = csv.writer(fileobj, dialect="excel")
+            writer.writerow(a)
+            float_repr = str
+            if sys.version_info >= (2, 7, 3):
+                float_repr = repr
+            expected = b",".join([float_repr(i).encode('utf-8') for i in a])+b"\r\n"
+            fileobj.seek(0)
+            self.assertEqual(fileobj.read(), expected)
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+    def test_char_write(self):
+        a = string.ascii_letters
+        fd, name = tempfile.mkstemp()
+        fileobj = os.fdopen(fd, "w+b")
+        try:
+            writer = csv.writer(fileobj, dialect="excel")
+            writer.writerow(a)
+            expected = ",".join(a).encode('utf-8')+b"\r\n"
+            fileobj.seek(0)
+            self.assertEqual(fileobj.read(), expected)
+        finally:
+            fileobj.close()
+            os.unlink(name)
+
+
+class TestUnicode(unittest.TestCase):
+    def test_unicode_read(self):
+        f = EncodedFile(BytesIO((u"Martin von Löwis,"
+                                 u"Marc André Lemburg,"
+                                 u"Guido van Rossum,"
+                                 u"François Pinard\r\n").encode('iso-8859-1')),
+                        data_encoding='iso-8859-1')
+        reader = csv.reader(f, encoding='iso-8859-1')
+        self.assertEqual(list(reader), [[u"Martin von Löwis",
+                                         u"Marc André Lemburg",
+                                         u"Guido van Rossum",
+                                         u"François Pinard"]])
+
+
+class TestUnicodeErrors(unittest.TestCase):
+    def test_encode_error(self):
+        fd = BytesIO()
+        writer = csv.writer(fd, encoding='cp1252', errors='xmlcharrefreplace')
+        writer.writerow(['hello', chr(2603)])
+        self.assertEqual(fd.getvalue(), b'hello,ਫ\r\n')
+
+    def test_encode_error_dictwriter(self):
+        fd = BytesIO()
+        dw = csv.DictWriter(fd, ['col1'],
+                            encoding='cp1252', errors='xmlcharrefreplace')
+        dw.writerow({'col1': chr(2604)})
+        self.assertEqual(fd.getvalue(), b'ਬ\r\n')
+
+    def test_decode_error(self):
+        """Make sure the specified error-handling mode is obeyed on readers."""
+        file = EncodedFile(BytesIO(u'Löwis,2,3'.encode('iso-8859-1')),
+                           data_encoding='iso-8859-1')
+        reader = csv.reader(file, encoding='ascii', errors='ignore')
+        self.assertEqual(list(reader)[0][0], 'Lwis')
+
+    def test_decode_error_dictreader(self):
+        """Make sure the error-handling mode is obeyed on DictReaders."""
+        file = EncodedFile(BytesIO(u'name,height,weight\nLöwis,2,3'.encode('iso-8859-1')),
+                           data_encoding='iso-8859-1')
+        reader = csv.DictReader(file, encoding='ascii', errors='ignore')
+        self.assertEqual(list(reader)[0]['name'], 'Lwis')

+ 583 - 0
desktop/core/ext-py/xlrd-2.0.1/CHANGELOG.rst

@@ -0,0 +1,583 @@
+Changes
+=======
+
+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 ``<cell>`` ``inlineStr`` attribute without ``<si>`` 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

+ 76 - 0
desktop/core/ext-py/xlrd-2.0.1/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 <david@giffin.org>."
+     *
+     * 4. Redistributions of any form whatsoever must retain the following
+     *    acknowledgment:
+     *    "This product includes software developed by
+     *     David Giffin <david@giffin.org>."
+     *
+     * 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.
+     */

+ 3 - 0
desktop/core/ext-py/xlrd-2.0.1/MANIFEST.in

@@ -0,0 +1,3 @@
+include CHANGELOG.rst
+include LICENSE
+include README.md

+ 86 - 0
desktop/core/ext-py/xlrd-2.0.1/PKG-INFO

@@ -0,0 +1,86 @@
+Metadata-Version: 2.1
+Name: xlrd
+Version: 2.0.1
+Summary: Library for developers to extract data from Microsoft Excel (tm) .xls spreadsheet files
+Home-page: http://www.python-excel.org/
+Author: Chris Withers
+Author-email: chris@withers.org
+License: BSD
+Description: 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:: 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
+        
+Keywords: xls,excel,spreadsheet,workbook
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Operating System :: OS Independent
+Classifier: Topic :: Database
+Classifier: Topic :: Office/Business
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*
+Provides-Extra: test
+Provides-Extra: docs
+Provides-Extra: build

+ 56 - 0
desktop/core/ext-py/xlrd-2.0.1/README.rst

@@ -0,0 +1,56 @@
+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:: 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

+ 410 - 0
desktop/core/ext-py/xlrd-2.0.1/scripts/runxlrd.py

@@ -0,0 +1,410 @@
+#!/usr/bin/env python
+# Copyright (c) 2005-2012 Stephen John Machin, Lingfo Pty Ltd
+# This script is part of the xlrd package, which is released under a
+# BSD-style licence.
+
+from __future__ import print_function
+
+cmd_doc = """
+Commands:
+
+2rows           Print the contents of first and last row in each sheet
+3rows           Print the contents of first, second and last row in each sheet
+bench           Same as "show", but doesn't print -- for profiling
+biff_count[1]   Print a count of each type of BIFF record in the file
+biff_dump[1]    Print a dump (char and hex) of the BIFF records in the file
+fonts           hdr + print a dump of all font objects
+hdr             Mini-overview of file (no per-sheet information)
+hotshot         Do a hotshot profile run e.g. ... -f1 hotshot bench bigfile*.xls
+labels          Dump of sheet.col_label_ranges and ...row... for each sheet
+name_dump       Dump of each object in book.name_obj_list
+names           Print brief information for each NAME record
+ov              Overview of file
+profile         Like "hotshot", but uses cProfile
+show            Print the contents of all rows in each sheet
+version[0]      Print versions of xlrd and Python and exit
+xfc             Print "XF counts" and cell-type counts -- see code for details
+
+[0] means no file arg
+[1] means only one file arg i.e. no glob.glob pattern
+"""
+
+options = None
+if __name__ == "__main__":
+    import xlrd
+    import sys
+    import time
+    import glob
+    import traceback
+    import gc
+
+    from xlrd.timemachine import xrange, REPR
+
+
+    class LogHandler(object):
+
+        def __init__(self, logfileobj):
+            self.logfileobj = logfileobj
+            self.fileheading = None
+            self.shown = 0
+
+        def setfileheading(self, fileheading):
+            self.fileheading = fileheading
+            self.shown = 0
+
+        def write(self, text):
+            if self.fileheading and not self.shown:
+                self.logfileobj.write(self.fileheading)
+                self.shown = 1
+            self.logfileobj.write(text)
+
+    null_cell = xlrd.empty_cell
+
+    def show_row(bk, sh, rowx, colrange, printit):
+        if bk.ragged_rows:
+            colrange = range(sh.row_len(rowx))
+        if not colrange: return
+        if printit: print()
+        if bk.formatting_info:
+            for colx, ty, val, cxfx in get_row_data(bk, sh, rowx, colrange):
+                if printit:
+                    print("cell %s%d: type=%d, data: %r, xfx: %s"
+                        % (xlrd.colname(colx), rowx+1, ty, val, cxfx))
+        else:
+            for colx, ty, val, _unused in get_row_data(bk, sh, rowx, colrange):
+                if printit:
+                    print("cell %s%d: type=%d, data: %r" % (xlrd.colname(colx), rowx+1, ty, val))
+
+    def get_row_data(bk, sh, rowx, colrange):
+        result = []
+        dmode = bk.datemode
+        ctys = sh.row_types(rowx)
+        cvals = sh.row_values(rowx)
+        for colx in colrange:
+            cty = ctys[colx]
+            cval = cvals[colx]
+            if bk.formatting_info:
+                cxfx = str(sh.cell_xf_index(rowx, colx))
+            else:
+                cxfx = ''
+            if cty == xlrd.XL_CELL_DATE:
+                try:
+                    showval = xlrd.xldate_as_tuple(cval, dmode)
+                except xlrd.XLDateError as e:
+                    showval = "%s:%s" % (type(e).__name__, e)
+                    cty = xlrd.XL_CELL_ERROR
+            elif cty == xlrd.XL_CELL_ERROR:
+                showval = xlrd.error_text_from_code.get(cval, '<Unknown error code 0x%02x>' % cval)
+            else:
+                showval = cval
+            result.append((colx, cty, showval, cxfx))
+        return result
+
+    def bk_header(bk):
+        print()
+        print("BIFF version: %s; datemode: %s"
+            % (xlrd.biff_text_from_num[bk.biff_version], bk.datemode))
+        print("codepage: %r (encoding: %s); countries: %r"
+            % (bk.codepage, bk.encoding, bk.countries))
+        print("Last saved by: %r" % bk.user_name)
+        print("Number of data sheets: %d" % bk.nsheets)
+        print("Use mmap: %d; Formatting: %d; On demand: %d"
+            % (bk.use_mmap, bk.formatting_info, bk.on_demand))
+        print("Ragged rows: %d" % bk.ragged_rows)
+        if bk.formatting_info:
+            print("FORMATs: %d, FONTs: %d, XFs: %d"
+                % (len(bk.format_list), len(bk.font_list), len(bk.xf_list)))
+        if not options.suppress_timing:
+            print("Load time: %.2f seconds (stage 1) %.2f seconds (stage 2)"
+                % (bk.load_time_stage_1, bk.load_time_stage_2))
+        print()
+
+    def show_fonts(bk):
+        print("Fonts:")
+        for x in xrange(len(bk.font_list)):
+            font = bk.font_list[x]
+            font.dump(header='== Index %d ==' % x, indent=4)
+
+    def show_names(bk, dump=0):
+        bk_header(bk)
+        if bk.biff_version < 50:
+            print("Names not extracted in this BIFF version")
+            return
+        nlist = bk.name_obj_list
+        print("Name list: %d entries" % len(nlist))
+        for nobj in nlist:
+            if dump:
+                nobj.dump(sys.stdout,
+                    header="\n=== Dump of name_obj_list[%d] ===" % nobj.name_index)
+            else:
+                print("[%d]\tName:%r macro:%r scope:%d\n\tresult:%r\n"
+                    % (nobj.name_index, nobj.name, nobj.macro, nobj.scope, nobj.result))
+
+    def print_labels(sh, labs, title):
+        if not labs:return
+        for rlo, rhi, clo, chi in labs:
+            print("%s label range %s:%s contains:"
+                % (title, xlrd.cellname(rlo, clo), xlrd.cellname(rhi-1, chi-1)))
+            for rx in xrange(rlo, rhi):
+                for cx in xrange(clo, chi):
+                    print("    %s: %r" % (xlrd.cellname(rx, cx), sh.cell_value(rx, cx)))
+
+    def show_labels(bk):
+        # bk_header(bk)
+        hdr = 0
+        for shx in range(bk.nsheets):
+            sh = bk.sheet_by_index(shx)
+            clabs = sh.col_label_ranges
+            rlabs = sh.row_label_ranges
+            if clabs or rlabs:
+                if not hdr:
+                    bk_header(bk)
+                    hdr = 1
+                print("sheet %d: name = %r; nrows = %d; ncols = %d" %
+                    (shx, sh.name, sh.nrows, sh.ncols))
+                print_labels(sh, clabs, 'Col')
+                print_labels(sh, rlabs, 'Row')
+            if bk.on_demand: bk.unload_sheet(shx)
+
+    def show(bk, nshow=65535, printit=1):
+        bk_header(bk)
+        if 0:
+            rclist = xlrd.sheet.rc_stats.items()
+            rclist = sorted(rclist)
+            print("rc stats")
+            for k, v in rclist:
+                print("0x%04x %7d" % (k, v))
+        if options.onesheet:
+            try:
+                shx = int(options.onesheet)
+            except ValueError:
+                shx = bk.sheet_by_name(options.onesheet).number
+            shxrange = [shx]
+        else:
+            shxrange = range(bk.nsheets)
+        # print("shxrange", list(shxrange))
+        for shx in shxrange:
+            sh = bk.sheet_by_index(shx)
+            nrows, ncols = sh.nrows, sh.ncols
+            colrange = range(ncols)
+            anshow = min(nshow, nrows)
+            print("sheet %d: name = %s; nrows = %d; ncols = %d" %
+                (shx, REPR(sh.name), sh.nrows, sh.ncols))
+            if nrows and ncols:
+                # Beat the bounds
+                for rowx in xrange(nrows):
+                    nc = sh.row_len(rowx)
+                    if nc:
+                        sh.row_types(rowx)[nc-1]
+                        sh.row_values(rowx)[nc-1]
+                        sh.cell(rowx, nc-1)
+            for rowx in xrange(anshow-1):
+                if not printit and rowx % 10000 == 1 and rowx > 1:
+                    print("done %d rows" % (rowx-1,))
+                show_row(bk, sh, rowx, colrange, printit)
+            if anshow and nrows:
+                show_row(bk, sh, nrows-1, colrange, printit)
+            print()
+            if bk.on_demand: bk.unload_sheet(shx)
+
+    def count_xfs(bk):
+        bk_header(bk)
+        for shx in range(bk.nsheets):
+            sh = bk.sheet_by_index(shx)
+            nrows = sh.nrows
+            print("sheet %d: name = %r; nrows = %d; ncols = %d" %
+                (shx, sh.name, sh.nrows, sh.ncols))
+            # Access all xfindexes to force gathering stats
+            type_stats = [0, 0, 0, 0, 0, 0, 0]
+            for rowx in xrange(nrows):
+                for colx in xrange(sh.row_len(rowx)):
+                    xfx = sh.cell_xf_index(rowx, colx)
+                    assert xfx >= 0
+                    cty = sh.cell_type(rowx, colx)
+                    type_stats[cty] += 1
+            print("XF stats", sh._xf_index_stats)
+            print("type stats", type_stats)
+            print()
+            if bk.on_demand: bk.unload_sheet(shx)
+
+    def main(cmd_args):
+        import optparse
+        global options
+        usage = "\n%prog [options] command [input-file-patterns]\n" + cmd_doc
+        oparser = optparse.OptionParser(usage)
+        oparser.add_option(
+            "-l", "--logfilename",
+            default="",
+            help="contains error messages")
+        oparser.add_option(
+            "-v", "--verbosity",
+            type="int", default=0,
+            help="level of information and diagnostics provided")
+        oparser.add_option(
+            "-m", "--mmap",
+            type="int", default=-1,
+            help="1: use mmap; 0: don't use mmap; -1: accept heuristic")
+        oparser.add_option(
+            "-e", "--encoding",
+            default="",
+            help="encoding override")
+        oparser.add_option(
+            "-f", "--formatting",
+            type="int", default=0,
+            help="0 (default): no fmt info\n"
+                 "1: fmt info (all cells)\n",
+        )
+        oparser.add_option(
+            "-g", "--gc",
+            type="int", default=0,
+            help="0: auto gc enabled; 1: auto gc disabled, manual collect after each file; 2: no gc")
+        oparser.add_option(
+            "-s", "--onesheet",
+            default="",
+            help="restrict output to this sheet (name or index)")
+        oparser.add_option(
+            "-u", "--unnumbered",
+            action="store_true", default=0,
+            help="omit line numbers or offsets in biff_dump")
+        oparser.add_option(
+            "-d", "--on-demand",
+            action="store_true", default=0,
+            help="load sheets on demand instead of all at once")
+        oparser.add_option(
+            "-t", "--suppress-timing",
+            action="store_true", default=0,
+            help="don't print timings (diffs are less messy)")
+        oparser.add_option(
+            "-r", "--ragged-rows",
+            action="store_true", default=0,
+            help="open_workbook(..., ragged_rows=True)")
+        options, args = oparser.parse_args(cmd_args)
+        if len(args) == 1 and args[0] in ("version", ):
+            pass
+        elif len(args) < 2:
+            oparser.error("Expected at least 2 args, found %d" % len(args))
+        cmd = args[0]
+        xlrd_version = getattr(xlrd, "__VERSION__", "unknown; before 0.5")
+        if cmd == 'biff_dump':
+            xlrd.dump(args[1], unnumbered=options.unnumbered)
+            sys.exit(0)
+        if cmd == 'biff_count':
+            xlrd.count_records(args[1])
+            sys.exit(0)
+        if cmd == 'version':
+            print("xlrd: %s, from %s" % (xlrd_version, xlrd.__file__))
+            print("Python:", sys.version)
+            sys.exit(0)
+        if options.logfilename:
+            logfile = LogHandler(open(options.logfilename, 'w'))
+        else:
+            logfile = sys.stdout
+        mmap_opt = options.mmap
+        mmap_arg = xlrd.USE_MMAP
+        if mmap_opt in (1, 0):
+            mmap_arg = mmap_opt
+        elif mmap_opt != -1:
+            print('Unexpected value (%r) for mmap option -- assuming default' % mmap_opt)
+        fmt_opt = options.formatting | (cmd in ('xfc', ))
+        gc_mode = options.gc
+        if gc_mode:
+            gc.disable()
+        for pattern in args[1:]:
+            for fname in glob.glob(pattern):
+                print("\n=== File: %s ===" % fname)
+                if logfile != sys.stdout:
+                    logfile.setfileheading("\n=== File: %s ===\n" % fname)
+                if gc_mode == 1:
+                    n_unreachable = gc.collect()
+                    if n_unreachable:
+                        print("GC before open:", n_unreachable, "unreachable objects")
+                try:
+                    t0 = time.time()
+                    bk = xlrd.open_workbook(
+                        fname,
+                        verbosity=options.verbosity, logfile=logfile,
+                        use_mmap=mmap_arg,
+                        encoding_override=options.encoding,
+                        formatting_info=fmt_opt,
+                        on_demand=options.on_demand,
+                        ragged_rows=options.ragged_rows,
+                    )
+                    t1 = time.time()
+                    if not options.suppress_timing:
+                        print("Open took %.2f seconds" % (t1-t0,))
+                except xlrd.XLRDError as e:
+                    print("*** Open failed: %s: %s" % (type(e).__name__, e))
+                    continue
+                except KeyboardInterrupt:
+                    print("*** KeyboardInterrupt ***")
+                    traceback.print_exc(file=sys.stdout)
+                    sys.exit(1)
+                except BaseException as e:
+                    print("*** Open failed: %s: %s" % (type(e).__name__, e))
+                    traceback.print_exc(file=sys.stdout)
+                    continue
+                t0 = time.time()
+                if cmd == 'hdr':
+                    bk_header(bk)
+                elif cmd == 'ov': # OverView
+                    show(bk, 0)
+                elif cmd == 'show': # all rows
+                    show(bk)
+                elif cmd == '2rows': # first row and last row
+                    show(bk, 2)
+                elif cmd == '3rows': # first row, 2nd row and last row
+                    show(bk, 3)
+                elif cmd == 'bench':
+                    show(bk, printit=0)
+                elif cmd == 'fonts':
+                    bk_header(bk)
+                    show_fonts(bk)
+                elif cmd == 'names': # named reference list
+                    show_names(bk)
+                elif cmd == 'name_dump': # named reference list
+                    show_names(bk, dump=1)
+                elif cmd == 'labels':
+                    show_labels(bk)
+                elif cmd == 'xfc':
+                    count_xfs(bk)
+                else:
+                    print("*** Unknown command <%s>" % cmd)
+                    sys.exit(1)
+                del bk
+                if gc_mode == 1:
+                    n_unreachable = gc.collect()
+                    if n_unreachable:
+                        print("GC post cmd:", fname, "->", n_unreachable, "unreachable objects")
+                if not options.suppress_timing:
+                    t1 = time.time()
+                    print("\ncommand took %.2f seconds\n" % (t1-t0,))
+
+        return None
+
+    av = sys.argv[1:]
+    if not av:
+        main(av)
+    firstarg = av[0].lower()
+    if firstarg == "hotshot":
+        import hotshot
+        import hotshot.stats
+        av = av[1:]
+        prof_log_name = "XXXX.prof"
+        prof = hotshot.Profile(prof_log_name)
+        # benchtime, result = prof.runcall(main, *av)
+        result = prof.runcall(main, *(av, ))
+        print("result", repr(result))
+        prof.close()
+        stats = hotshot.stats.load(prof_log_name)
+        stats.strip_dirs()
+        stats.sort_stats('time', 'calls')
+        stats.print_stats(20)
+    elif firstarg == "profile":
+        import cProfile
+        av = av[1:]
+        cProfile.run('main(av)', 'YYYY.prof')
+        import pstats
+        p = pstats.Stats('YYYY.prof')
+        p.strip_dirs().sort_stats('cumulative').print_stats(30)
+    else:
+        main(av)

+ 10 - 0
desktop/core/ext-py/xlrd-2.0.1/setup.cfg

@@ -0,0 +1,10 @@
+[bdist_wheel]
+universal = 1
+
+[metadata]
+license_file = LICENSE
+
+[egg_info]
+tag_build = 
+tag_date = 0
+

+ 45 - 0
desktop/core/ext-py/xlrd-2.0.1/setup.py

@@ -0,0 +1,45 @@
+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) .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']
+    )
+)

+ 213 - 0
desktop/core/ext-py/xlrd-2.0.1/xlrd/__init__.py

@@ -0,0 +1,213 @@
+# Copyright (c) 2005-2012 Stephen John Machin, Lingfo Pty Ltd
+# This module is part of the xlrd package, which is released under a
+# BSD-style licence.
+import os
+import pprint
+import sys
+import zipfile
+
+from . import timemachine
+from .biffh import (
+    XL_CELL_BLANK, XL_CELL_BOOLEAN, XL_CELL_DATE, XL_CELL_EMPTY, XL_CELL_ERROR,
+    XL_CELL_NUMBER, XL_CELL_TEXT, XLRDError, biff_text_from_num,
+    error_text_from_code,
+)
+from .book import Book, colname, open_workbook_xls
+from .compdoc import SIGNATURE as XLS_SIGNATURE
+from .formula import *  # is constrained by __all__
+from .info import __VERSION__, __version__
+from .sheet import empty_cell
+from .xldate import XLDateError, xldate_as_datetime, xldate_as_tuple
+
+
+#: descriptions of the file types :mod:`xlrd` can :func:`inspect <inspect_format>`.
+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 <str>` path containing the content to inspect.
+      ``~`` will be expanded.
+
+    :param content:
+      The :class:`bytes` content to inspect.
+
+    :returns:
+       A :class:`str`, or ``None`` if the format cannot be determined.
+       The return value can always be looked up in :data:`FILE_FORMAT_DESCRIPTIONS`
+       to return a human-readable description of the format found.
+    """
+    if content:
+        peek = content[:PEEK_SIZE]
+    else:
+        path = os.path.expanduser(path)
+        with open(path, "rb") as f:
+            peek = f.read(PEEK_SIZE)
+
+    if peek.startswith(XLS_SIGNATURE):
+        return 'xls'
+
+    if peek.startswith(ZIP_SIGNATURE):
+        zf = zipfile.ZipFile(timemachine.BYTES_IO(content) if content else path)
+
+        # Workaround for some third party files that use forward slashes and
+        # lower case names. We map the expected name in lowercase to the
+        # actual filename in the zip container.
+        component_names = {name.replace('\\', '/').lower(): name
+                           for name in zf.namelist()}
+
+        if 'xl/workbook.xml' in component_names:
+            return 'xlsx'
+        if 'xl/workbook.bin' in component_names:
+            return 'xlsb'
+        if 'content.xml' in component_names:
+            return 'ods'
+        return 'zip'
+
+
+def open_workbook(filename=None,
+                  logfile=sys.stdout,
+                  verbosity=0,
+                  use_mmap=True,
+                  file_contents=None,
+                  encoding_override=None,
+                  formatting_info=False,
+                  on_demand=False,
+                  ragged_rows=False,
+                  ignore_workbook_corruption=False
+                  ):
+    """
+    Open a spreadsheet file for data extraction.
+
+    :param filename: The path to the spreadsheet file to be opened.
+
+    :param logfile: An open file to which messages and diagnostics are written.
+
+    :param verbosity: Increases the volume of trace material written to the
+                      logfile.
+
+    :param use_mmap:
+
+      Whether to use the mmap module is determined heuristically.
+      Use this arg to override the result.
+
+      Current heuristic: mmap is used if it exists.
+
+    :param file_contents:
+
+      A string or an :class:`mmap.mmap` object or some other behave-alike
+      object. If ``file_contents`` is supplied, ``filename`` will not be used,
+      except (possibly) in messages.
+
+    :param encoding_override:
+
+      Used to overcome missing or bad codepage information
+      in older-version files. See :doc:`unicode`.
+
+    :param formatting_info:
+
+      The default is ``False``, which saves memory.
+      In this case, "Blank" cells, which are those with their own formatting
+      information but no data, are treated as empty by ignoring the file's
+      ``BLANK`` and ``MULBLANK`` records.
+      This cuts off any bottom or right "margin" of rows of empty or blank
+      cells.
+      Only :meth:`~xlrd.sheet.Sheet.cell_value` and
+      :meth:`~xlrd.sheet.Sheet.cell_type` are available.
+
+      When ``True``, formatting information will be read from the spreadsheet
+      file. This provides all cells, including empty and blank cells.
+      Formatting information is available for each cell.
+
+      Note that this will raise a NotImplementedError when used with an
+      xlsx file.
+
+    :param on_demand:
+
+      Governs whether sheets are all loaded initially or when demanded
+      by the caller. See :doc:`on_demand`.
+
+    :param ragged_rows:
+
+      The default of ``False`` means all rows are padded out with empty cells so
+      that all rows have the same size as found in
+      :attr:`~xlrd.sheet.Sheet.ncols`.
+
+      ``True`` means that there are no empty cells at the ends of rows.
+      This can result in substantial memory savings if rows are of widely
+      varying sizes. See also the :meth:`~xlrd.sheet.Sheet.row_len` method.
+
+
+    :param ignore_workbook_corruption:
+
+      This option allows to read corrupted workbooks.
+      When ``False`` you may face CompDocError: Workbook corruption.
+      When ``True`` that exception will be ignored.
+
+    :returns: An instance of the :class:`~xlrd.book.Book` class.
+    """
+
+    file_format = inspect_format(filename, file_contents)
+    # We have to let unknown file formats pass through here, as some ancient
+    # files that xlrd can parse don't start with the expected signature.
+    if file_format and file_format != 'xls':
+        raise XLRDError(FILE_FORMAT_DESCRIPTIONS[file_format]+'; not supported')
+
+    bk = open_workbook_xls(
+        filename=filename,
+        logfile=logfile,
+        verbosity=verbosity,
+        use_mmap=use_mmap,
+        file_contents=file_contents,
+        encoding_override=encoding_override,
+        formatting_info=formatting_info,
+        on_demand=on_demand,
+        ragged_rows=ragged_rows,
+        ignore_workbook_corruption=ignore_workbook_corruption,
+    )
+
+    return bk
+
+
+def dump(filename, outfile=sys.stdout, unnumbered=False):
+    """
+    For debugging: dump an XLS file's BIFF records in char & hex.
+
+    :param filename: The path to the file to be dumped.
+    :param outfile: An open file, to which the dump is written.
+    :param unnumbered: If true, omit offsets (for meaningful diffs).
+    """
+    from .biffh import biff_dump
+    bk = Book()
+    bk.biff2_8_load(filename=filename, logfile=outfile, )
+    biff_dump(bk.mem, bk.base, bk.stream_len, 0, outfile, unnumbered)
+
+
+def count_records(filename, outfile=sys.stdout):
+    """
+    For debugging and analysis: summarise the file's BIFF records.
+    ie: produce a sorted file of ``(record_name, count)``.
+
+    :param filename: The path to the file to be summarised.
+    :param outfile: An open file, to which the summary is written.
+    """
+    from .biffh import biff_count_records
+    bk = Book()
+    bk.biff2_8_load(filename=filename, logfile=outfile, )
+    biff_count_records(bk.mem, bk.base, bk.stream_len, outfile)

+ 643 - 0
desktop/core/ext-py/xlrd-2.0.1/xlrd/biffh.py

@@ -0,0 +1,643 @@
+# -*- coding: utf-8 -*-
+# Portions copyright © 2005-2010 Stephen John Machin, Lingfo Pty Ltd
+# This module is part of the xlrd package, which is released under a
+# BSD-style licence.
+from __future__ import print_function
+
+import sys
+from struct import unpack
+
+from .timemachine import *
+
+DEBUG = 0
+
+
+
+class XLRDError(Exception):
+    """
+    An exception indicating problems reading data from an Excel file.
+    """
+
+
+class BaseObject(object):
+    """
+    Parent of almost all other classes in the package. Defines a common
+    :meth:`dump` method for debugging.
+    """
+
+    _repr_these = []
+
+
+    def dump(self, f=None, header=None, footer=None, indent=0):
+        """
+        :param f: open file object, to which the dump is written
+        :param header: text to write before the dump
+        :param footer: text to write after the dump
+        :param indent: number of leading spaces (for recursive calls)
+        """
+        if f is None:
+            f = sys.stderr
+        if hasattr(self, "__slots__"):
+            alist = []
+            for attr in self.__slots__:
+                alist.append((attr, getattr(self, attr)))
+        else:
+            alist = self.__dict__.items()
+        alist = sorted(alist)
+        pad = " " * indent
+        if header is not None: print(header, file=f)
+        list_type = type([])
+        dict_type = type({})
+        for attr, value in alist:
+            if getattr(value, 'dump', None) and attr != 'book':
+                value.dump(f,
+                    header="%s%s (%s object):" % (pad, attr, value.__class__.__name__),
+                    indent=indent+4)
+            elif (attr not in self._repr_these and
+                  (isinstance(value, list_type) or isinstance(value, dict_type))):
+                print("%s%s: %s, len = %d" % (pad, attr, type(value), len(value)), file=f)
+            else:
+                fprintf(f, "%s%s: %r\n", pad, attr, value)
+        if footer is not None: print(footer, file=f)
+
+FUN, FDT, FNU, FGE, FTX = range(5) # unknown, date, number, general, text
+DATEFORMAT = FDT
+NUMBERFORMAT = FNU
+
+(
+    XL_CELL_EMPTY,
+    XL_CELL_TEXT,
+    XL_CELL_NUMBER,
+    XL_CELL_DATE,
+    XL_CELL_BOOLEAN,
+    XL_CELL_ERROR,
+    XL_CELL_BLANK, # for use in debugging, gathering stats, etc
+) = range(7)
+
+biff_text_from_num = {
+    0:  "(not BIFF)",
+    20: "2.0",
+    21: "2.1",
+    30: "3",
+    40: "4S",
+    45: "4W",
+    50: "5",
+    70: "7",
+    80: "8",
+    85: "8X",
+}
+
+#: This dictionary can be used to produce a text version of the internal codes
+#: that Excel uses for error cells.
+error_text_from_code = {
+    0x00: '#NULL!',  # Intersection of two cell ranges is empty
+    0x07: '#DIV/0!', # Division by zero
+    0x0F: '#VALUE!', # Wrong type of operand
+    0x17: '#REF!',   # Illegal or deleted cell reference
+    0x1D: '#NAME?',  # Wrong function or range name
+    0x24: '#NUM!',   # Value range overflow
+    0x2A: '#N/A',    # Argument or function not available
+}
+
+BIFF_FIRST_UNICODE = 80
+
+XL_WORKBOOK_GLOBALS = WBKBLOBAL = 0x5
+XL_WORKBOOK_GLOBALS_4W = 0x100
+XL_WORKSHEET = WRKSHEET = 0x10
+
+XL_BOUNDSHEET_WORKSHEET = 0x00
+XL_BOUNDSHEET_CHART     = 0x02
+XL_BOUNDSHEET_VB_MODULE = 0x06
+
+# XL_RK2 = 0x7e
+XL_ARRAY  = 0x0221
+XL_ARRAY2 = 0x0021
+XL_BLANK = 0x0201
+XL_BLANK_B2 = 0x01
+XL_BOF = 0x809
+XL_BOOLERR = 0x205
+XL_BOOLERR_B2 = 0x5
+XL_BOUNDSHEET = 0x85
+XL_BUILTINFMTCOUNT = 0x56
+XL_CF = 0x01B1
+XL_CODEPAGE = 0x42
+XL_COLINFO = 0x7D
+XL_COLUMNDEFAULT = 0x20 # BIFF2 only
+XL_COLWIDTH = 0x24 # BIFF2 only
+XL_CONDFMT = 0x01B0
+XL_CONTINUE = 0x3c
+XL_COUNTRY = 0x8C
+XL_DATEMODE = 0x22
+XL_DEFAULTROWHEIGHT = 0x0225
+XL_DEFCOLWIDTH = 0x55
+XL_DIMENSION = 0x200
+XL_DIMENSION2 = 0x0
+XL_EFONT = 0x45
+XL_EOF = 0x0a
+XL_EXTERNNAME = 0x23
+XL_EXTERNSHEET = 0x17
+XL_EXTSST = 0xff
+XL_FEAT11 = 0x872
+XL_FILEPASS = 0x2f
+XL_FONT = 0x31
+XL_FONT_B3B4 = 0x231
+XL_FORMAT = 0x41e
+XL_FORMAT2 = 0x1E # BIFF2, BIFF3
+XL_FORMULA = 0x6
+XL_FORMULA3 = 0x206
+XL_FORMULA4 = 0x406
+XL_GCW = 0xab
+XL_HLINK = 0x01B8
+XL_QUICKTIP = 0x0800
+XL_HORIZONTALPAGEBREAKS = 0x1b
+XL_INDEX = 0x20b
+XL_INTEGER = 0x2 # BIFF2 only
+XL_IXFE = 0x44 # BIFF2 only
+XL_LABEL = 0x204
+XL_LABEL_B2 = 0x04
+XL_LABELRANGES = 0x15f
+XL_LABELSST = 0xfd
+XL_LEFTMARGIN = 0x26
+XL_TOPMARGIN = 0x28
+XL_RIGHTMARGIN = 0x27
+XL_BOTTOMMARGIN = 0x29
+XL_HEADER = 0x14
+XL_FOOTER = 0x15
+XL_HCENTER = 0x83
+XL_VCENTER = 0x84
+XL_MERGEDCELLS = 0xE5
+XL_MSO_DRAWING = 0x00EC
+XL_MSO_DRAWING_GROUP = 0x00EB
+XL_MSO_DRAWING_SELECTION = 0x00ED
+XL_MULRK = 0xbd
+XL_MULBLANK = 0xbe
+XL_NAME = 0x18
+XL_NOTE = 0x1c
+XL_NUMBER = 0x203
+XL_NUMBER_B2 = 0x3
+XL_OBJ = 0x5D
+XL_PAGESETUP = 0xA1
+XL_PALETTE = 0x92
+XL_PANE = 0x41
+XL_PRINTGRIDLINES = 0x2B
+XL_PRINTHEADERS = 0x2A
+XL_RK = 0x27e
+XL_ROW = 0x208
+XL_ROW_B2 = 0x08
+XL_RSTRING = 0xd6
+XL_SCL = 0x00A0
+XL_SHEETHDR = 0x8F # BIFF4W only
+XL_SHEETPR = 0x81
+XL_SHEETSOFFSET = 0x8E # BIFF4W only
+XL_SHRFMLA = 0x04bc
+XL_SST = 0xfc
+XL_STANDARDWIDTH = 0x99
+XL_STRING = 0x207
+XL_STRING_B2 = 0x7
+XL_STYLE = 0x293
+XL_SUPBOOK = 0x1AE # aka EXTERNALBOOK in OOo docs
+XL_TABLEOP = 0x236
+XL_TABLEOP2 = 0x37
+XL_TABLEOP_B2 = 0x36
+XL_TXO = 0x1b6
+XL_UNCALCED = 0x5e
+XL_UNKNOWN = 0xffff
+XL_VERTICALPAGEBREAKS = 0x1a
+XL_WINDOW2    = 0x023E
+XL_WINDOW2_B2 = 0x003E
+XL_WRITEACCESS = 0x5C
+XL_WSBOOL = XL_SHEETPR
+XL_XF = 0xe0
+XL_XF2 = 0x0043 # BIFF2 version of XF record
+XL_XF3 = 0x0243 # BIFF3 version of XF record
+XL_XF4 = 0x0443 # BIFF4 version of XF record
+
+boflen = {0x0809: 8, 0x0409: 6, 0x0209: 6, 0x0009: 4}
+bofcodes = (0x0809, 0x0409, 0x0209, 0x0009)
+
+XL_FORMULA_OPCODES = (0x0006, 0x0406, 0x0206)
+
+_cell_opcode_list = [
+    XL_BOOLERR,
+    XL_FORMULA,
+    XL_FORMULA3,
+    XL_FORMULA4,
+    XL_LABEL,
+    XL_LABELSST,
+    XL_MULRK,
+    XL_NUMBER,
+    XL_RK,
+    XL_RSTRING,
+]
+_cell_opcode_dict = {}
+for _cell_opcode in _cell_opcode_list:
+    _cell_opcode_dict[_cell_opcode] = 1
+
+def is_cell_opcode(c):
+    return c in  _cell_opcode_dict
+
+def upkbits(tgt_obj, src, manifest, local_setattr=setattr):
+    for n, mask, attr in manifest:
+        local_setattr(tgt_obj, attr, (src & mask) >> n)
+
+def upkbitsL(tgt_obj, src, manifest, local_setattr=setattr, local_int=int):
+    for n, mask, attr in manifest:
+        local_setattr(tgt_obj, attr, local_int((src & mask) >> n))
+
+def unpack_string(data, pos, encoding, lenlen=1):
+    nchars = unpack('<' + 'BH'[lenlen-1], data[pos:pos+lenlen])[0]
+    pos += lenlen
+    return unicode(data[pos:pos+nchars], encoding)
+
+def unpack_string_update_pos(data, pos, encoding, lenlen=1, known_len=None):
+    if known_len is not None:
+        # On a NAME record, the length byte is detached from the front of the string.
+        nchars = known_len
+    else:
+        nchars = unpack('<' + 'BH'[lenlen-1], data[pos:pos+lenlen])[0]
+        pos += lenlen
+    newpos = pos + nchars
+    return (unicode(data[pos:newpos], encoding), newpos)
+
+def unpack_unicode(data, pos, lenlen=2):
+    "Return unicode_strg"
+    nchars = unpack('<' + 'BH'[lenlen-1], data[pos:pos+lenlen])[0]
+    if not nchars:
+        # Ambiguous whether 0-length string should have an "options" byte.
+        # Avoid crash if missing.
+        return UNICODE_LITERAL("")
+    pos += lenlen
+    options = BYTES_ORD(data[pos])
+    pos += 1
+    # phonetic = options & 0x04
+    # richtext = options & 0x08
+    if options & 0x08:
+        # rt = unpack('<H', data[pos:pos+2])[0] # unused
+        pos += 2
+    if options & 0x04:
+        # sz = unpack('<i', data[pos:pos+4])[0] # unused
+        pos += 4
+    if options & 0x01:
+        # Uncompressed UTF-16-LE
+        rawstrg = data[pos:pos+2*nchars]
+        # if DEBUG: print "nchars=%d pos=%d rawstrg=%r" % (nchars, pos, rawstrg)
+        strg = unicode(rawstrg, 'utf_16_le')
+        # pos += 2*nchars
+    else:
+        # Note: this is COMPRESSED (not ASCII!) encoding!!!
+        # Merely returning the raw bytes would work OK 99.99% of the time
+        # if the local codepage was cp1252 -- however this would rapidly go pear-shaped
+        # for other codepages so we grit our Anglocentric teeth and return Unicode :-)
+
+        strg = unicode(data[pos:pos+nchars], "latin_1")
+        # pos += nchars
+    # if richtext:
+    #     pos += 4 * rt
+    # if phonetic:
+    #     pos += sz
+    # return (strg, pos)
+    return strg
+
+def unpack_unicode_update_pos(data, pos, lenlen=2, known_len=None):
+    "Return (unicode_strg, updated value of pos)"
+    if known_len is not None:
+        # On a NAME record, the length byte is detached from the front of the string.
+        nchars = known_len
+    else:
+        nchars = unpack('<' + 'BH'[lenlen-1], data[pos:pos+lenlen])[0]
+        pos += lenlen
+    if not nchars and not data[pos:]:
+        # Zero-length string with no options byte
+        return (UNICODE_LITERAL(""), pos)
+    options = BYTES_ORD(data[pos])
+    pos += 1
+    phonetic = options & 0x04
+    richtext = options & 0x08
+    if richtext:
+        rt = unpack('<H', data[pos:pos+2])[0]
+        pos += 2
+    if phonetic:
+        sz = unpack('<i', data[pos:pos+4])[0]
+        pos += 4
+    if options & 0x01:
+        # Uncompressed UTF-16-LE
+        strg = unicode(data[pos:pos+2*nchars], 'utf_16_le')
+        pos += 2*nchars
+    else:
+        # Note: this is COMPRESSED (not ASCII!) encoding!!!
+        strg = unicode(data[pos:pos+nchars], "latin_1")
+        pos += nchars
+    if richtext:
+        pos += 4 * rt
+    if phonetic:
+        pos += sz
+    return (strg, pos)
+
+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
+    # BIFF8-only MERGEDCELLS records in a BIFF5 file!
+    n, = unpack("<H", data[pos:pos+2])
+    pos += 2
+    if n:
+        if addr_size == 6:
+            fmt = "<HHBB"
+        else:
+            fmt = "<HHHH"
+        for _unused in xrange(n):
+            ra, rb, ca, cb = unpack(fmt, data[pos:pos+addr_size])
+            output_list.append((ra, rb+1, ca, cb+1))
+            pos += addr_size
+    return pos
+
+_brecstrg = """\
+0000 DIMENSIONS_B2
+0001 BLANK_B2
+0002 INTEGER_B2_ONLY
+0003 NUMBER_B2
+0004 LABEL_B2
+0005 BOOLERR_B2
+0006 FORMULA
+0007 STRING_B2
+0008 ROW_B2
+0009 BOF_B2
+000A EOF
+000B INDEX_B2_ONLY
+000C CALCCOUNT
+000D CALCMODE
+000E PRECISION
+000F REFMODE
+0010 DELTA
+0011 ITERATION
+0012 PROTECT
+0013 PASSWORD
+0014 HEADER
+0015 FOOTER
+0016 EXTERNCOUNT
+0017 EXTERNSHEET
+0018 NAME_B2,5+
+0019 WINDOWPROTECT
+001A VERTICALPAGEBREAKS
+001B HORIZONTALPAGEBREAKS
+001C NOTE
+001D SELECTION
+001E FORMAT_B2-3
+001F BUILTINFMTCOUNT_B2
+0020 COLUMNDEFAULT_B2_ONLY
+0021 ARRAY_B2_ONLY
+0022 DATEMODE
+0023 EXTERNNAME
+0024 COLWIDTH_B2_ONLY
+0025 DEFAULTROWHEIGHT_B2_ONLY
+0026 LEFTMARGIN
+0027 RIGHTMARGIN
+0028 TOPMARGIN
+0029 BOTTOMMARGIN
+002A PRINTHEADERS
+002B PRINTGRIDLINES
+002F FILEPASS
+0031 FONT
+0032 FONT2_B2_ONLY
+0036 TABLEOP_B2
+0037 TABLEOP2_B2
+003C CONTINUE
+003D WINDOW1
+003E WINDOW2_B2
+0040 BACKUP
+0041 PANE
+0042 CODEPAGE
+0043 XF_B2
+0044 IXFE_B2_ONLY
+0045 EFONT_B2_ONLY
+004D PLS
+0051 DCONREF
+0055 DEFCOLWIDTH
+0056 BUILTINFMTCOUNT_B3-4
+0059 XCT
+005A CRN
+005B FILESHARING
+005C WRITEACCESS
+005D OBJECT
+005E UNCALCED
+005F SAVERECALC
+0063 OBJECTPROTECT
+007D COLINFO
+007E RK2_mythical_?
+0080 GUTS
+0081 WSBOOL
+0082 GRIDSET
+0083 HCENTER
+0084 VCENTER
+0085 BOUNDSHEET
+0086 WRITEPROT
+008C COUNTRY
+008D HIDEOBJ
+008E SHEETSOFFSET
+008F SHEETHDR
+0090 SORT
+0092 PALETTE
+0099 STANDARDWIDTH
+009B FILTERMODE
+009C FNGROUPCOUNT
+009D AUTOFILTERINFO
+009E AUTOFILTER
+00A0 SCL
+00A1 SETUP
+00AB GCW
+00BD MULRK
+00BE MULBLANK
+00C1 MMS
+00D6 RSTRING
+00D7 DBCELL
+00DA BOOKBOOL
+00DD SCENPROTECT
+00E0 XF
+00E1 INTERFACEHDR
+00E2 INTERFACEEND
+00E5 MERGEDCELLS
+00E9 BITMAP
+00EB MSO_DRAWING_GROUP
+00EC MSO_DRAWING
+00ED MSO_DRAWING_SELECTION
+00EF PHONETIC
+00FC SST
+00FD LABELSST
+00FF EXTSST
+013D TABID
+015F LABELRANGES
+0160 USESELFS
+0161 DSF
+01AE SUPBOOK
+01AF PROTECTIONREV4
+01B0 CONDFMT
+01B1 CF
+01B2 DVAL
+01B6 TXO
+01B7 REFRESHALL
+01B8 HLINK
+01BC PASSWORDREV4
+01BE DV
+01C0 XL9FILE
+01C1 RECALCID
+0200 DIMENSIONS
+0201 BLANK
+0203 NUMBER
+0204 LABEL
+0205 BOOLERR
+0206 FORMULA_B3
+0207 STRING
+0208 ROW
+0209 BOF
+020B INDEX_B3+
+0218 NAME
+0221 ARRAY
+0223 EXTERNNAME_B3-4
+0225 DEFAULTROWHEIGHT
+0231 FONT_B3B4
+0236 TABLEOP
+023E WINDOW2
+0243 XF_B3
+027E RK
+0293 STYLE
+0406 FORMULA_B4
+0409 BOF
+041E FORMAT
+0443 XF_B4
+04BC SHRFMLA
+0800 QUICKTIP
+0809 BOF
+0862 SHEETLAYOUT
+0867 SHEETPROTECTION
+0868 RANGEPROTECTION
+"""
+
+biff_rec_name_dict = {}
+for _buff in _brecstrg.splitlines():
+    _numh, _name = _buff.split()
+    biff_rec_name_dict[int(_numh, 16)] = _name
+del _buff, _name, _brecstrg
+
+def hex_char_dump(strg, ofs, dlen, base=0, fout=sys.stdout, unnumbered=False):
+    endpos = min(ofs + dlen, len(strg))
+    pos = ofs
+    numbered = not unnumbered
+    num_prefix = ''
+    while pos < endpos:
+        endsub = min(pos + 16, endpos)
+        substrg = strg[pos:endsub]
+        lensub = endsub - pos
+        if lensub <= 0 or lensub != len(substrg):
+            fprintf(
+                sys.stdout,
+                '??? 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 " % BYTES_ORD(c) for c in substrg)
+
+        chard = ''
+        for c in substrg:
+            c = chr(BYTES_ORD(c))
+            if c == '\0':
+                c = '~'
+            elif not (' ' <= c <= '~'):
+                c = '?'
+            chard += c
+        if numbered:
+            num_prefix = "%5d: " %  (base+pos-ofs)
+
+        fprintf(fout, "%s     %-48s %s\n", num_prefix, hexd, chard)
+        pos = endsub
+
+def biff_dump(mem, stream_offset, stream_len, base=0, fout=sys.stdout, unnumbered=False):
+    pos = stream_offset
+    stream_end = stream_offset + stream_len
+    adj = base - stream_offset
+    dummies = 0
+    numbered = not unnumbered
+    num_prefix = ''
+    while stream_end - pos >= 4:
+        rc, length = unpack('<HH', mem[pos:pos+4])
+        if rc == 0 and length == 0:
+            if mem[pos:] == b'\0' * (stream_end - pos):
+                dummies = stream_end - pos
+                savpos = pos
+                pos = stream_end
+                break
+            if dummies:
+                dummies += 4
+            else:
+                savpos = pos
+                dummies = 4
+            pos += 4
+        else:
+            if dummies:
+                if numbered:
+                    num_prefix =  "%5d: " % (adj + savpos)
+                fprintf(fout, "%s---- %d zero bytes skipped ----\n", num_prefix, dummies)
+                dummies = 0
+            recname = biff_rec_name_dict.get(rc, '<UNKNOWN>')
+            if numbered:
+                num_prefix = "%5d: " % (adj + pos)
+            fprintf(fout, "%s%04x %s len = %04x (%d)\n", num_prefix, rc, recname, length, length)
+            pos += 4
+            hex_char_dump(mem, pos, length, adj+pos, fout, unnumbered)
+            pos += length
+    if dummies:
+        if numbered:
+            num_prefix =  "%5d: " % (adj + savpos)
+        fprintf(fout, "%s---- %d zero bytes skipped ----\n", num_prefix, dummies)
+    if pos < stream_end:
+        if numbered:
+            num_prefix = "%5d: " % (adj + pos)
+        fprintf(fout, "%s---- Misc bytes at end ----\n", num_prefix)
+        hex_char_dump(mem, pos, stream_end-pos, adj + pos, fout, unnumbered)
+    elif pos > stream_end:
+        fprintf(fout, "Last dumped record has length (%d) that is too large\n", length)
+
+def biff_count_records(mem, stream_offset, stream_len, fout=sys.stdout):
+    pos = stream_offset
+    stream_end = stream_offset + stream_len
+    tally = {}
+    while stream_end - pos >= 4:
+        rc, length = unpack('<HH', mem[pos:pos+4])
+        if rc == 0 and length == 0:
+            if mem[pos:] == b'\0' * (stream_end - pos):
+                break
+            recname = "<Dummy (zero)>"
+        else:
+            recname = biff_rec_name_dict.get(rc, None)
+            if recname is None:
+                recname = "Unknown_0x%04X" % rc
+        if recname in tally:
+            tally[recname] += 1
+        else:
+            tally[recname] = 1
+        pos += length + 4
+    slist = sorted(tally.items())
+    for recname, count in slist:
+        print("%8d %s" % (count, recname), file=fout)
+
+encoding_from_codepage = {
+    1200 : 'utf_16_le',
+    10000: 'mac_roman',
+    10006: 'mac_greek', # guess
+    10007: 'mac_cyrillic', # guess
+    10029: 'mac_latin2', # guess
+    10079: 'mac_iceland', # guess
+    10081: 'mac_turkish', # guess
+    32768: 'mac_roman',
+    32769: 'cp1252',
+}
+# some more guessing, for Indic scripts
+# codepage 57000 range:
+# 2 Devanagari [0]
+# 3 Bengali [1]
+# 4 Tamil [5]
+# 5 Telegu [6]
+# 6 Assamese [1] c.f. Bengali
+# 7 Oriya [4]
+# 8 Kannada [7]
+# 9 Malayalam [8]
+# 10 Gujarati [3]
+# 11 Gurmukhi [2]

+ 1474 - 0
desktop/core/ext-py/xlrd-2.0.1/xlrd/book.py

@@ -0,0 +1,1474 @@
+# 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 __future__ import print_function
+
+import struct
+
+from . import compdoc, formatting, sheet
+from .biffh import *
+from .formula import *
+from .timemachine import *
+
+try:
+    from time import perf_counter
+except ImportError:
+    # 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
+
+SUPBOOK_UNK, SUPBOOK_INTERNAL, SUPBOOK_EXTERNAL, SUPBOOK_ADDIN, SUPBOOK_DDEOLE = range(5)
+
+SUPPORTED_VERSIONS = (80, 70, 50, 45, 40, 30, 21, 20)
+
+_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 = {}
+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, _code_from_builtin_name
+
+def open_workbook_xls(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):
+    t0 = perf_counter()
+    bk = Book()
+    try:
+        bk.biff2_8_load(
+            filename=filename, file_contents=file_contents,
+            logfile=logfile, verbosity=verbosity, use_mmap=use_mmap,
+            encoding_override=encoding_override,
+            formatting_info=formatting_info,
+            on_demand=on_demand,
+            ragged_rows=ragged_rows,
+            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:
+            raise XLRDError("Can't determine file's BIFF version")
+        if biff_version not in SUPPORTED_VERSIONS:
+            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
+            if on_demand:
+                fprintf(bk.logfile,
+                    "*** WARNING: on_demand is not supported for this Excel version.\n"
+                    "*** Setting on_demand to False.\n")
+                bk.on_demand = on_demand = False
+            bk.fake_globals_get_sheet()
+        elif biff_version == 45:
+            # worksheet(s) embedded in global stream
+            bk.parse_globals()
+            if on_demand:
+                fprintf(bk.logfile, "*** WARNING: on_demand is not supported for this Excel version.\n"
+                                    "*** Setting on_demand to False.\n")
+                bk.on_demand = on_demand = False
+        else:
+            bk.parse_globals()
+            bk._sheet_list = [None for sh in bk._sheet_names]
+            if not on_demand:
+                bk.get_sheets()
+        bk.nsheets = len(bk._sheet_list)
+        if biff_version == 45 and bk.nsheets > 1:
+            fprintf(
+                bk.logfile,
+                "*** WARNING: Excel 4.0 workbook (.XLW) file contains %d worksheets.\n"
+                "*** Book-level data will be that of the last worksheet.\n",
+                bk.nsheets
+            )
+        t2 = perf_counter()
+        bk.load_time_stage_2 = t2 - t1
+    except:
+        bk.release_resources()
+        raise
+    # normal exit
+    if not on_demand:
+        bk.release_resources()
+    return bk
+
+
+class Name(BaseObject):
+    """
+    Information relating to a named reference, formula, macro, etc.
+
+    .. note::
+
+      Name information is **not** extracted from files older than
+      Excel 5.0 (``Book.biff_version < 50``)
+    """
+    _repr_these = ['stack']
+    book = None # parent
+
+    #: 0 = Visible; 1 = Hidden
+    hidden = 0
+
+    #: 0 = Command macro; 1 = Function macro. Relevant only if macro == 1
+    func = 0
+
+    #: 0 = Sheet macro; 1 = VisualBasic macro. Relevant only if macro == 1
+    vbasic = 0
+
+    #: 0 = Standard name; 1 = Macro name
+    macro = 0
+
+    #: 0 = Simple formula; 1 = Complex formula (array formula or user defined).
+    #:
+    #: .. note:: No examples have been sighted.
+    complex = 0
+
+    #: 0 = User-defined name; 1 = Built-in name
+    #:
+    #: Common examples: ``Print_Area``, ``Print_Titles``; see OOo docs for
+    #: full list
+    builtin = 0
+
+    #: Function group. Relevant only if macro == 1; see OOo docs for values.
+    funcgroup = 0
+
+    #: 0 = Formula definition; 1 = Binary data
+    #:
+    #: .. note:: No examples have been sighted.
+    binary = 0
+
+    #: The index of this object in book.name_obj_list
+    name_index = 0
+
+    # A Unicode string. If builtin, decoded as per OOo docs.
+    name = UNICODE_LITERAL("")
+
+    #: An 8-bit string.
+    raw_formula = b''
+
+    #: ``-1``:
+    #:    The name is global (visible in all calculation sheets).
+    #: ``-2``:
+    #:    The name belongs to a macro sheet or VBA sheet.
+    #: ``-3``:
+    #:    The name is invalid.
+    #: ``0 <= scope < book.nsheets``:
+    #:    The name is local to the sheet whose index is scope.
+    scope = -1
+
+    #: The result of evaluating the formula, if any.
+    #: If no formula, or evaluation of the formula encountered problems,
+    #: the result is ``None``. Otherwise the result is a single instance of the
+    #: :class:`~xlrd.formula.Operand` class.
+    #
+    result = None
+
+    def cell(self):
+        """
+        This is a convenience method for the frequent use case where the name
+        refers to a single cell.
+
+        :returns: An instance of the :class:`~xlrd.sheet.Cell` class.
+
+        :raises xlrd.biffh.XLRDError:
+          The name is not a constant absolute reference
+          to a single cell.
+        """
+        res = self.result
+        if res:
+            # result should be an instance of the Operand class
+            kind = res.kind
+            value = res.value
+            if kind == oREF and len(value) == 1:
+                ref3d = value[0]
+                if (0 <= ref3d.shtxlo == ref3d.shtxhi - 1 and
+                        ref3d.rowxlo == ref3d.rowxhi - 1 and
+                        ref3d.colxlo == ref3d.colxhi - 1):
+                    sh = self.book.sheet_by_index(ref3d.shtxlo)
+                    return sh.cell(ref3d.rowxlo, ref3d.colxlo)
+        self.dump(
+            self.book.logfile,
+            header="=== Dump of Name object ===",
+            footer="======= End of dump =======",
+        )
+        raise XLRDError("Not a constant absolute reference to a single cell")
+
+    def area2d(self, clipped=True):
+        """
+        This is a convenience method for the use case where the name
+        refers to one rectangular area in one worksheet.
+
+        :param clipped:
+          If ``True``, the default, the returned rectangle is clipped
+          to fit in ``(0, sheet.nrows, 0, sheet.ncols)``.
+          it is guaranteed that ``0 <= rowxlo <= rowxhi <= sheet.nrows`` and
+          that the number of usable rows in the area (which may be zero) is
+          ``rowxhi - rowxlo``; likewise for columns.
+
+        :returns: a tuple ``(sheet_object, rowxlo, rowxhi, colxlo, colxhi)``.
+
+        :raises xlrd.biffh.XLRDError:
+           The name is not a constant absolute reference
+           to a single area in a single sheet.
+        """
+        res = self.result
+        if res:
+            # result should be an instance of the Operand class
+            kind = res.kind
+            value = res.value
+            if kind == oREF and len(value) == 1: # only 1 reference
+                ref3d = value[0]
+                if 0 <= ref3d.shtxlo == ref3d.shtxhi - 1: # only 1 usable sheet
+                    sh = self.book.sheet_by_index(ref3d.shtxlo)
+                    if not clipped:
+                        return sh, ref3d.rowxlo, ref3d.rowxhi, ref3d.colxlo, ref3d.colxhi
+                    rowxlo = min(ref3d.rowxlo, sh.nrows)
+                    rowxhi = max(rowxlo, min(ref3d.rowxhi, sh.nrows))
+                    colxlo = min(ref3d.colxlo, sh.ncols)
+                    colxhi = max(colxlo, min(ref3d.colxhi, sh.ncols))
+                    assert 0 <= rowxlo <= rowxhi <= sh.nrows
+                    assert 0 <= colxlo <= colxhi <= sh.ncols
+                    return sh, rowxlo, rowxhi, colxlo, colxhi
+        self.dump(
+            self.book.logfile,
+            header="=== Dump of Name object ===",
+            footer="======= End of dump =======",
+        )
+        raise XLRDError("Not a constant absolute reference to a single area in a single sheet")
+
+
+class Book(BaseObject):
+    """
+    Contents of a "workbook".
+
+    .. warning::
+
+      You should not instantiate this class yourself. You use the :class:`Book`
+      object that was returned when you called :func:`~xlrd.open_workbook`.
+    """
+
+    #: The number of worksheets present in the workbook file.
+    #: This information is available even when no sheets have yet been loaded.
+    nsheets = 0
+
+    #: Which date system was in force when this file was last saved.
+    #:
+    #: 0:
+    #:   1900 system (the Excel for Windows default).
+    #:
+    #: 1:
+    #:   1904 system (the Excel for Macintosh default).
+    #:
+    #: Defaults to 0 in case it's not specified in the file.
+    datemode = 0
+
+    #: Version of BIFF (Binary Interchange File Format) used to create the file.
+    #: Latest is 8.0 (represented here as 80), introduced with Excel 97.
+    #: Earliest supported by this module: 2.0 (represented as 20).
+    biff_version = 0
+
+    #: List containing a :class:`Name` object for each ``NAME`` record in the
+    #: workbook.
+    #:
+    #: .. versionadded:: 0.6.0
+    name_obj_list = []
+
+    #: An integer denoting the character set used for strings in this file.
+    #: For BIFF 8 and later, this will be 1200, meaning Unicode;
+    #: more precisely, UTF_16_LE.
+    #: For earlier versions, this is used to derive the appropriate Python
+    #: encoding to be used to convert to Unicode.
+    #: Examples: ``1252 -> 'cp1252'``, ``10000 -> 'mac_roman'``
+    codepage = None
+
+    #: The encoding that was derived from the codepage.
+    encoding = None
+
+    #: A tuple containing the telephone country code for:
+    #:
+    #: ``[0]``:
+    #:   the user-interface setting when the file was created.
+    #:
+    #: ``[1]``:
+    #:    the regional settings.
+    #:
+    #: Example: ``(1, 61)`` meaning ``(USA, Australia)``.
+    #:
+    #: This information may give a clue to the correct encoding for an
+    #: unknown codepage. For a long list of observed values, refer to the
+    #: OpenOffice.org documentation for the ``COUNTRY`` record.
+    countries = (0, 0)
+
+    #: What (if anything) is recorded as the name of the last user to
+    #: save the file.
+    user_name = UNICODE_LITERAL('')
+
+    #: A list of :class:`~xlrd.formatting.Font` class instances,
+    #: each corresponding to a FONT record.
+    #:
+    #: .. versionadded:: 0.6.1
+    font_list = []
+
+    #: A list of :class:`~xlrd.formatting.XF` class instances,
+    #: each corresponding to an ``XF`` record.
+    #:
+    #: .. versionadded:: 0.6.1
+    xf_list = []
+
+    #: A list of :class:`~xlrd.formatting.Format` objects, each corresponding to
+    #: a ``FORMAT`` record, in the order that they appear in the input file.
+    #: It does *not* contain builtin formats.
+    #:
+    #: If you are creating an output file using (for example) :mod:`xlwt`,
+    #: use this list.
+    #:
+    #: The collection to be used for all visual rendering purposes is
+    #: :attr:`format_map`.
+    #:
+    #: .. versionadded:: 0.6.1
+    format_list = []
+
+    ##
+    #: The mapping from :attr:`~xlrd.formatting.XF.format_key` to
+    #: :class:`~xlrd.formatting.Format` object.
+    #:
+    #: .. versionadded:: 0.6.1
+    format_map = {}
+
+    #: This provides access via name to the extended format information for
+    #: both built-in styles and user-defined styles.
+    #:
+    #: It maps ``name`` to ``(built_in, xf_index)``, where
+    #: ``name`` is either the name of a user-defined style,
+    #: or the name of one of the built-in styles. Known built-in names are
+    #: Normal, RowLevel_1 to RowLevel_7,
+    #: ColLevel_1 to ColLevel_7, Comma, Currency, Percent, "Comma [0]",
+    #: "Currency [0]", Hyperlink, and "Followed Hyperlink".
+    #:
+    #: ``built_in`` has the following meanings
+    #:
+    #: 1:
+    #:     built-in style
+    #:
+    #: 0:
+    #:     user-defined
+    #:
+    #: ``xf_index`` is an index into :attr:`Book.xf_list`.
+    #:
+    #: References: OOo docs s6.99 (``STYLE`` record); Excel UI Format/Style
+    #:
+    #: .. versionadded:: 0.6.1
+    #:
+    #: Extracted only if ``open_workbook(..., formatting_info=True)``
+    #:
+    #: .. versionadded:: 0.7.4
+    style_name_map = {}
+
+    #: This provides definitions for colour indexes. Please refer to
+    #: :ref:`palette` for an explanation
+    #: of how colours are represented in Excel.
+    #:
+    #: Colour indexes into the palette map into ``(red, green, blue)`` tuples.
+    #: "Magic" indexes e.g. ``0x7FFF`` map to ``None``.
+    #:
+    #: :attr:`colour_map` is what you need if you want to render cells on screen
+    #: or in a PDF file. If you are writing an output XLS file, use
+    #: :attr:`palette_record`.
+    #:
+    #: .. note:: Extracted only if ``open_workbook(..., formatting_info=True)``
+    #:
+    #: .. versionadded:: 0.6.1
+    colour_map = {}
+
+    #: If the user has changed any of the colours in the standard palette, the
+    #: XLS file will contain a ``PALETTE`` record with 56 (16 for Excel 4.0 and
+    #: earlier) RGB values in it, and this list will be e.g.
+    #: ``[(r0, b0, g0), ..., (r55, b55, g55)]``.
+    #: Otherwise this list will be empty. This is what you need if you are
+    #: writing an output XLS file. If you want to render cells on screen or in a
+    #: PDF file, use :attr:`colour_map`.
+    #:
+    #: .. note:: Extracted only if ``open_workbook(..., formatting_info=True)``
+    #:
+    #: .. versionadded:: 0.6.1
+    palette_record = []
+
+    #: Time in seconds to extract the XLS image as a contiguous string
+    #: (or mmap equivalent).
+    load_time_stage_1 = -1.0
+
+    #: Time in seconds to parse the data from the contiguous string
+    #: (or mmap equivalent).
+    load_time_stage_2 = -1.0
+
+    def sheets(self):
+        """
+        :returns: A list of all sheets in the book.
+
+        All sheets not already loaded will be loaded.
+        """
+        for sheetx in xrange(self.nsheets):
+            if not self._sheet_list[sheetx]:
+                self.get_sheet(sheetx)
+        return self._sheet_list[:]
+
+    def sheet_by_index(self, sheetx):
+        """
+        :param sheetx: Sheet index in ``range(nsheets)``
+        :returns: A :class:`~xlrd.sheet.Sheet`.
+        """
+        return self._sheet_list[sheetx] or self.get_sheet(sheetx)
+
+    def __iter__(self):
+        """
+        Makes iteration through sheets of a book a little more straightforward.
+        Don't free resources after use since it can be called like `list(book)`
+        """
+        for i in range(self.nsheets):
+            yield self.sheet_by_index(i)
+
+    def sheet_by_name(self, sheet_name):
+        """
+        :param sheet_name: Name of the sheet required.
+        :returns: A :class:`~xlrd.sheet.Sheet`.
+        """
+        try:
+            sheetx = self._sheet_names.index(sheet_name)
+        except ValueError:
+            raise XLRDError('No sheet named <%r>' % sheet_name)
+        return self.sheet_by_index(sheetx)
+
+    def __getitem__(self, item):
+        """
+        Allow indexing with sheet name or index.
+        :param item: Name or index of sheet enquired upon
+        :return: :class:`~xlrd.sheet.Sheet`.
+        """
+        if isinstance(item, int):
+            return self.sheet_by_index(item)
+        else:
+            return self.sheet_by_name(item)
+
+    def sheet_names(self):
+        """
+        :returns:
+          A list of the names of all the worksheets in the workbook file.
+          This information is available even when no sheets have yet been
+          loaded.
+        """
+        return self._sheet_names[:]
+
+    def sheet_loaded(self, sheet_name_or_index):
+        """
+        :param sheet_name_or_index: Name or index of sheet enquired upon
+        :returns: ``True`` if sheet is loaded, ``False`` otherwise.
+
+        .. versionadded:: 0.7.1
+        """
+        if isinstance(sheet_name_or_index, int):
+            sheetx = sheet_name_or_index
+        else:
+            try:
+                sheetx = self._sheet_names.index(sheet_name_or_index)
+            except ValueError:
+                raise XLRDError('No sheet named <%r>' % sheet_name_or_index)
+        return bool(self._sheet_list[sheetx])
+
+    def unload_sheet(self, sheet_name_or_index):
+        """
+        :param sheet_name_or_index: Name or index of sheet to be unloaded.
+
+        .. versionadded:: 0.7.1
+        """
+        if isinstance(sheet_name_or_index, int):
+            sheetx = sheet_name_or_index
+        else:
+            try:
+                sheetx = self._sheet_names.index(sheet_name_or_index)
+            except ValueError:
+                raise XLRDError('No sheet named <%r>' % sheet_name_or_index)
+        self._sheet_list[sheetx] = None
+
+    def release_resources(self):
+        """
+        This method has a dual purpose. You can call it to release
+        memory-consuming objects and (possibly) a memory-mapped file
+        (:class:`mmap.mmap` object) when you have finished loading sheets in
+        ``on_demand`` mode, but still require the :class:`Book` object to
+        examine the loaded sheets. It is also called automatically (a) when
+        :func:`~xlrd.open_workbook`
+        raises an exception and (b) if you are using a ``with`` statement, when
+        the ``with`` block is exited. Calling this method multiple times on the
+        same object has no ill effect.
+        """
+        self._resources_released = 1
+        if hasattr(self.mem, "close"):
+            # must be a mmap.mmap object
+            self.mem.close()
+        self.mem = None
+        if hasattr(self.filestr, "close"):
+            self.filestr.close()
+        self.filestr = None
+        self._sharedstrings = None
+        self._rich_text_runlist_map = None
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_value, exc_tb):
+        self.release_resources()
+        # return false
+
+    #: A mapping from ``(lower_case_name, scope)`` to a single :class:`Name`
+    #:  object.
+    #:
+    #: .. versionadded:: 0.6.0
+    name_and_scope_map = {}
+
+    #: A mapping from `lower_case_name` to a list of :class:`Name` objects.
+    #: The list is sorted in scope order. Typically there will be one item
+    #: (of global scope) in the list.
+    #:
+    #: .. versionadded:: 0.6.0
+    name_map = {}
+
+    def __init__(self):
+        self._sheet_list = []
+        self._sheet_names = []
+        self._sheet_visibility = [] # from BOUNDSHEET record
+        self.nsheets = 0
+        self._sh_abs_posn = [] # sheet's absolute position in the stream
+        self._sharedstrings = []
+        self._rich_text_runlist_map = {}
+        self.raw_user_name = False
+        self._sheethdr_count = 0 # BIFF 4W only
+        self.builtinfmtcount = -1 # unknown as yet. BIFF 3, 4S, 4W
+        self.initialise_format_info()
+        self._all_sheets_count = 0 # includes macro & VBA sheets
+        self._supbook_count = 0
+        self._supbook_locals_inx = None
+        self._supbook_addins_inx = None
+        self._all_sheets_map = [] # maps an all_sheets index to a calc-sheets index (or -1)
+        self._externsheet_info = []
+        self._externsheet_type_b57 = []
+        self._extnsht_name_from_num = {}
+        self._sheet_num_from_name = {}
+        self._extnsht_count = 0
+        self._supbook_types = []
+        self._resources_released = 0
+        self.addin_func_names = []
+        self.name_obj_list = []
+        self.colour_map = {}
+        self.palette_record = []
+        self.xf_list = []
+        self.style_name_map = {}
+        self.mem = b''
+        self.filestr = b''
+
+    def biff2_8_load(self, filename=None, file_contents=None,
+                     logfile=sys.stdout, verbosity=0, use_mmap=True,
+                     encoding_override=None,
+                     formatting_info=False,
+                     on_demand=False,
+                     ragged_rows=False,
+                     ignore_workbook_corruption=False
+                     ):
+        # DEBUG = 0
+        self.logfile = logfile
+        self.verbosity = verbosity
+        self.use_mmap = use_mmap
+        self.encoding_override = encoding_override
+        self.formatting_info = formatting_info
+        self.on_demand = on_demand
+        self.ragged_rows = ragged_rows
+
+        if not file_contents:
+            with open(filename, "rb") as f:
+                f.seek(0, 2) # EOF
+                size = f.tell()
+                f.seek(0, 0) # BOF
+                if size == 0:
+                    raise XLRDError("File size is 0 bytes")
+                if self.use_mmap:
+                    self.filestr = mmap.mmap(f.fileno(), size, access=mmap.ACCESS_READ)
+                    self.stream_len = size
+                else:
+                    self.filestr = f.read()
+                    self.stream_len = len(self.filestr)
+        else:
+            self.filestr = file_contents
+            self.stream_len = len(file_contents)
+
+        self.base = 0
+        if self.filestr[:8] != compdoc.SIGNATURE:
+            # got this one at the antique store
+            self.mem = self.filestr
+        else:
+            cd = compdoc.CompDoc(self.filestr, logfile=self.logfile,
+                                 ignore_workbook_corruption=ignore_workbook_corruption)
+            for qname in ['Workbook', 'Book']:
+                self.mem, self.base, self.stream_len = \
+                            cd.locate_named_stream(UNICODE_LITERAL(qname))
+                if self.mem:
+                    break
+            else:
+                raise XLRDError("Can't find workbook in OLE2 compound document")
+            del cd
+            if self.mem is not self.filestr:
+                if hasattr(self.filestr, "close"):
+                    self.filestr.close()
+                self.filestr = b''
+        self._position = self.base
+        if DEBUG:
+            print("mem: %s, base: %d, len: %d" % (type(self.mem), self.base, self.stream_len), file=self.logfile)
+
+    def initialise_format_info(self):
+        # needs to be done once per sheet for BIFF 4W :-(
+        self.format_map = {}
+        self.format_list = []
+        self.xfcount = 0
+        self.actualfmtcount = 0 # number of FORMAT records seen so far
+        self._xf_index_to_xl_type_map = {0: XL_CELL_NUMBER}
+        self._xf_epilogue_done = 0
+        self.xf_list = []
+        self.font_list = []
+
+    def get2bytes(self):
+        pos = self._position
+        buff_two = self.mem[pos:pos+2]
+        lenbuff = len(buff_two)
+        self._position += lenbuff
+        if lenbuff < 2:
+            return MY_EOF
+        lo, hi = buff_two
+        return (BYTES_ORD(hi) << 8) | BYTES_ORD(lo)
+
+    def get_record_parts(self):
+        pos = self._position
+        mem = self.mem
+        code, length = unpack('<HH', mem[pos:pos+4])
+        pos += 4
+        data = mem[pos:pos+length]
+        self._position = pos + length
+        return (code, length, data)
+
+    def get_record_parts_conditional(self, reqd_record):
+        pos = self._position
+        mem = self.mem
+        code, length = unpack('<HH', mem[pos:pos+4])
+        if code != reqd_record:
+            return (None, 0, b'')
+        pos += 4
+        data = mem[pos:pos+length]
+        self._position = pos + length
+        return (code, length, data)
+
+    def get_sheet(self, sh_number, update_pos=True):
+        if self._resources_released:
+            raise XLRDError("Can't load sheets after releasing resources.")
+        if update_pos:
+            self._position = self._sh_abs_posn[sh_number]
+        self.getbof(XL_WORKSHEET)
+        # assert biff_version == self.biff_version ### FAILS
+        # Have an example where book is v7 but sheet reports v8!!!
+        # It appears to work OK if the sheet version is ignored.
+        # Confirmed by Daniel Rentz: happens when Excel does "save as"
+        # creating an old version file; ignore version details on sheet BOF.
+        sh = sheet.Sheet(
+            self,
+            self._position,
+            self._sheet_names[sh_number],
+            sh_number,
+        )
+        sh.read(self)
+        self._sheet_list[sh_number] = sh
+        return sh
+
+    def get_sheets(self):
+        # DEBUG = 0
+        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("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 = UNICODE_LITERAL('Sheet 1')
+        self._sheet_names = [fake_sheet_name]
+        self._sh_abs_posn = [0]
+        self._sheet_visibility = [0] # one sheet, visible
+        self._sheet_list.append(None) # get_sheet updates _sheet_list but needs a None beforehand
+        self.get_sheets()
+
+    def handle_boundsheet(self, data):
+        # DEBUG = 1
+        bv = self.biff_version
+        self.derive_encoding()
+        if DEBUG:
+            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.
+            sheet_name = unpack_string(data, 0, self.encoding, lenlen=1)
+            visibility = 0
+            sheet_type = XL_BOUNDSHEET_WORKSHEET # guess, patch later
+            if len(self._sh_abs_posn) == 0:
+                abs_posn = self._sheetsoffset + self.base
+                # Note (a) this won't be used
+                # (b) it's the position of the SHEETHDR record
+                # (c) add 11 to get to the worksheet BOF record
+            else:
+                abs_posn = -1 # unknown
+        else:
+            offset, visibility, sheet_type = unpack('<iBB', data[0:6])
+            abs_posn = offset + self.base # because global BOF is always at posn 0 in the stream
+            if bv < BIFF_FIRST_UNICODE:
+                sheet_name = unpack_string(data, 6, self.encoding, lenlen=1)
+            else:
+                sheet_name = unpack_unicode(data, 6, lenlen=1)
+
+        if DEBUG or self.verbosity >= 2:
+            fprintf(self.logfile,
+                "BOUNDSHEET: inx=%d vis=%r sheet_name=%r abs_posn=%d sheet_type=0x%02x\n",
+                self._all_sheets_count, visibility, sheet_name, abs_posn, sheet_type)
+        self._all_sheets_count += 1
+        if sheet_type != XL_BOUNDSHEET_WORKSHEET:
+            self._all_sheets_map.append(-1)
+            descr = {
+                1: 'Macro sheet',
+                2: 'Chart',
+                6: 'Visual Basic module',
+            }.get(sheet_type, 'UNKNOWN')
+
+            if DEBUG or self.verbosity >= 1:
+                fprintf(self.logfile,
+                    "NOTE *** Ignoring non-worksheet data named %r (type 0x%02x = %s)\n",
+                    sheet_name, sheet_type, descr)
+        else:
+            snum = len(self._sheet_names)
+            self._all_sheets_map.append(snum)
+            self._sheet_names.append(sheet_name)
+            self._sh_abs_posn.append(abs_posn)
+            self._sheet_visibility.append(visibility)
+            self._sheet_num_from_name[sheet_name] = snum
+
+    def handle_builtinfmtcount(self, data):
+        ### N.B. This count appears to be utterly useless.
+        # DEBUG = 1
+        builtinfmtcount = unpack('<H', data[0:2])[0]
+        if DEBUG: fprintf(self.logfile, "BUILTINFMTCOUNT: %r\n", builtinfmtcount)
+        self.builtinfmtcount = builtinfmtcount
+
+    def derive_encoding(self):
+        if self.encoding_override:
+            self.encoding = self.encoding_override
+        elif self.codepage is None:
+            if self.biff_version < 80:
+                fprintf(self.logfile,
+                    "*** 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 codepage in encoding_from_codepage:
+                encoding = encoding_from_codepage[codepage]
+            elif 300 <= codepage <= 1999:
+                encoding = 'cp' + str(codepage)
+            elif self.biff_version >= 80:
+                self.codepage = 1200
+                encoding = 'utf_16_le'
+            else:
+                encoding = 'unknown_codepage_' + str(codepage)
+            if DEBUG or (self.verbosity and encoding != self.encoding) :
+                fprintf(self.logfile, "CODEPAGE: codepage %r -> encoding %r\n", codepage, encoding)
+            self.encoding = encoding
+        if self.codepage != 1200: # utf_16_le
+            # If we don't have a codec that can decode ASCII into Unicode,
+            # we're well & truly stuffed -- let the punter know ASAP.
+            try:
+                unicode(b'trial', self.encoding)
+            except BaseException as e:
+                fprintf(self.logfile,
+                    "ERROR *** codepage %r -> encoding %r -> %s: %s\n",
+                    self.codepage, self.encoding, type(e).__name__.split(".")[-1], e)
+                raise
+        if self.raw_user_name:
+            strg = unpack_string(self.user_name, 0, self.encoding, lenlen=1)
+            strg = strg.rstrip()
+            # if DEBUG:
+            #     print "CODEPAGE: user name decoded from %r to %r" % (self.user_name, strg)
+            self.user_name = strg
+            self.raw_user_name = False
+        return self.encoding
+
+    def handle_codepage(self, data):
+        # DEBUG = 0
+        codepage = unpack('<H', data[0:2])[0]
+        self.codepage = codepage
+        self.derive_encoding()
+
+    def handle_country(self, data):
+        countries = unpack('<HH', data[0:4])
+        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
+
+    def handle_datemode(self, data):
+        datemode = unpack('<H', data[0:2])[0]
+        if DEBUG or self.verbosity:
+            fprintf(self.logfile, "DATEMODE: datemode %r\n", datemode)
+        assert datemode in (0, 1)
+        self.datemode = datemode
+
+    def handle_externname(self, data):
+        blah = DEBUG or self.verbosity >= 2
+        if self.biff_version >= 80:
+            option_flags, other_info =unpack("<HI", data[:6])
+            pos = 6
+            name, pos = unpack_unicode_update_pos(data, pos, lenlen=1)
+            extra = data[pos:]
+            if self._supbook_types[-1] == SUPBOOK_ADDIN:
+                self.addin_func_names.append(name)
+            if blah:
+                fprintf(self.logfile,
+                    "EXTERNNAME: sbktype=%d oflags=0x%04x oinfo=0x%08x name=%r extra=%r\n",
+                    self._supbook_types[-1], option_flags, other_info, name, extra)
+
+    def handle_externsheet(self, data):
+        self.derive_encoding() # in case CODEPAGE record missing/out of order/wrong
+        self._extnsht_count += 1 # for use as a 1-based index
+        blah1 = DEBUG or self.verbosity >= 1
+        blah2 = DEBUG or self.verbosity >= 2
+        if self.biff_version >= 80:
+            num_refs = unpack("<H", data[0:2])[0]
+            bytes_reqd = num_refs * 6 + 2
+            while len(data) < bytes_reqd:
+                if blah1:
+                    fprintf(
+                        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")
+                data += data2
+            pos = 2
+            for k in xrange(num_refs):
+                info = unpack("<HHH", data[pos:pos+6])
+                ref_recordx, ref_first_sheetx, ref_last_sheetx = info
+                self._externsheet_info.append(info)
+                pos += 6
+                if blah2:
+                    fprintf(
+                        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("<BB", data[:2])
+            if blah2:
+                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("   %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._extnsht_name_from_num, file=self.logfile)
+            if not (1 <= ty <= 4):
+                ty = 0
+            self._externsheet_type_b57.append(ty)
+
+    def handle_filepass(self, data):
+        if self.verbosity >= 2:
+            logf = self.logfile
+            fprintf(logf, "FILEPASS:\n")
+            hex_char_dump(data, 0, len(data), base=0, fout=logf)
+            if self.biff_version >= 80:
+                kind1, = unpack('<H', data[:2])
+                if kind1 == 0: # weak XOR encryption
+                    key, hash_value = unpack('<HH', data[2:])
+                    fprintf(logf,
+                        'weak XOR: key=0x%04x hash=0x%04x\n',
+                        key, hash_value)
+                elif kind1 == 1:
+                    kind2, = unpack('<H', data[4:6])
+                    if kind2 == 1: # BIFF8 standard encryption
+                        caption = "BIFF8 std"
+                    elif kind2 == 2:
+                        caption = "BIFF8 strong"
+                    else:
+                        caption = "** UNKNOWN ENCRYPTION METHOD **"
+                    fprintf(logf, "%s\n", caption)
+        raise XLRDError("Workbook is encrypted")
+
+    def handle_name(self, data):
+        blah = DEBUG or self.verbosity >= 2
+        bv = self.biff_version
+        if bv < 50:
+            return
+        self.derive_encoding()
+        # print
+        # hex_char_dump(data, 0, len(data), fout=self.logfile)
+        (
+            option_flags, kb_shortcut, name_len, fmla_len, extsht_index, sheet_index,
+            menu_text_len, description_text_len, help_topic_text_len, status_bar_text_len,
+        ) = unpack("<HBBHHH4B", data[0:14])
+        nobj = Name()
+        nobj.book = self ### CIRCULAR ###
+        name_index = len(self.name_obj_list)
+        nobj.name_index = name_index
+        self.name_obj_list.append(nobj)
+        nobj.option_flags = option_flags
+        attrs = [
+            ('hidden', 1, 0),
+            ('func', 2, 1),
+            ('vbasic', 4, 2),
+            ('macro', 8, 3),
+            ('complex', 0x10, 4),
+            ('builtin', 0x20, 5),
+            ('funcgroup', 0xFC0, 6),
+            ('binary', 0x1000, 12),
+        ]
+        for attr, mask, nshift in attrs:
+            setattr(nobj, attr, (option_flags & mask) >> nshift)
+
+        macro_flag = " M"[nobj.macro]
+        if bv < 80:
+            internal_name, pos = unpack_string_update_pos(data, 14, self.encoding, known_len=name_len)
+        else:
+            internal_name, pos = unpack_unicode_update_pos(data, 14, known_len=name_len)
+        nobj.extn_sheet_num = extsht_index
+        nobj.excel_sheet_index = sheet_index
+        nobj.scope = None # patched up in the names_epilogue() method
+        if blah:
+            fprintf(
+                self.logfile,
+                "NAME[%d]:%s oflags=%d, name_len=%d, fmla_len=%d, extsht_index=%d, sheet_index=%d, name=%r\n",
+                name_index, macro_flag, option_flags, name_len,
+                fmla_len, extsht_index, sheet_index, internal_name)
+        name = internal_name
+        if nobj.builtin:
+            name = builtin_name_from_code.get(name, "??Unknown??")
+            if blah: print("    builtin: %s" % name, file=self.logfile)
+        nobj.name = name
+        nobj.raw_formula = data[pos:]
+        nobj.basic_formula_len = fmla_len
+        nobj.evaluated = 0
+        if blah:
+            nobj.dump(
+                self.logfile,
+                header="--- handle_name: name[%d] ---" % name_index,
+                footer="-------------------",
+            )
+
+    def names_epilogue(self):
+        blah = self.verbosity >= 2
+        f = self.logfile
+        if blah:
+            print("+++++ names_epilogue +++++", file=f)
+            print("_all_sheets_map", REPR(self._all_sheets_map), file=f)
+            print("_extnsht_name_from_num", REPR(self._extnsht_name_from_num), file=f)
+            print("_sheet_num_from_name", REPR(self._sheet_num_from_name), file=f)
+        num_names = len(self.name_obj_list)
+        for namex in range(num_names):
+            nobj = self.name_obj_list[namex]
+            # Convert from excel_sheet_index to scope.
+            # This is done here because in BIFF7 and earlier, the
+            # BOUNDSHEET records (from which _all_sheets_map is derived)
+            # come after the NAME records.
+            if self.biff_version >= 80:
+                sheet_index = nobj.excel_sheet_index
+                if sheet_index == 0:
+                    intl_sheet_index = -1 # global
+                elif 1 <= sheet_index <= len(self._all_sheets_map):
+                    intl_sheet_index = self._all_sheets_map[sheet_index-1]
+                    if intl_sheet_index == -1: # maps to a macro or VBA sheet
+                        intl_sheet_index = -2 # valid sheet reference but not useful
+                else:
+                    # huh?
+                    intl_sheet_index = -3 # invalid
+            elif 50 <= self.biff_version <= 70:
+                sheet_index = nobj.extn_sheet_num
+                if sheet_index == 0:
+                    intl_sheet_index = -1 # global
+                else:
+                    sheet_name = self._extnsht_name_from_num[sheet_index]
+                    intl_sheet_index = self._sheet_num_from_name.get(sheet_name, -2)
+            nobj.scope = intl_sheet_index
+
+        for namex in range(num_names):
+            nobj = self.name_obj_list[namex]
+            # Parse the formula ...
+            if nobj.macro or nobj.binary: continue
+            if nobj.evaluated: continue
+            evaluate_name_formula(self, nobj, namex, blah=blah)
+
+        if self.verbosity >= 2:
+            print("---------- name object dump ----------", file=f)
+            for namex in range(num_names):
+                nobj = self.name_obj_list[namex]
+                nobj.dump(f, header="--- name[%d] ---" % namex)
+            print("--------------------------------------", file=f)
+        #
+        # Build some dicts for access to the name objects
+        #
+        name_and_scope_map = {} # (name.lower(), scope): Name_object
+        name_map = {}           # name.lower() : list of Name_objects (sorted in scope order)
+        for namex in range(num_names):
+            nobj = self.name_obj_list[namex]
+            name_lcase = nobj.name.lower()
+            key = (name_lcase, nobj.scope)
+            if key in name_and_scope_map and self.verbosity:
+                fprintf(f, 'Duplicate entry %r in name_and_scope_map\n', key)
+            name_and_scope_map[key] = nobj
+            sort_data = (nobj.scope, namex, nobj)
+            # namex (a temp unique ID) ensures the Name objects will not
+            # be compared (fatal in py3)
+            if name_lcase in name_map:
+                name_map[name_lcase].append(sort_data)
+            else:
+                name_map[name_lcase] = [sort_data]
+        for key in name_map.keys():
+            alist = name_map[key]
+            alist.sort()
+            name_map[key] = [x[2] for x in alist]
+        self.name_and_scope_map = name_and_scope_map
+        self.name_map = name_map
+
+    def handle_obj(self, data):
+        # Not doing much handling at all.
+        # Worrying about embedded (BOF ... EOF) substreams is done elsewhere.
+        # DEBUG = 1
+        obj_type, obj_id = unpack('<HI', data[4:10])
+        # if DEBUG: print "---> handle_obj type=%d id=0x%08x" % (obj_type, obj_id)
+
+    def handle_supbook(self, data):
+        # aka EXTERNALBOOK in OOo docs
+        self._supbook_types.append(None)
+        blah = DEBUG or self.verbosity >= 2
+        if blah:
+            print("SUPBOOK:", file=self.logfile)
+            hex_char_dump(data, 0, len(data), fout=self.logfile)
+        num_sheets = unpack("<H", data[0:2])[0]
+        if blah: print("num_sheets = %d" % num_sheets, file=self.logfile)
+        sbn = self._supbook_count
+        self._supbook_count += 1
+        if data[2:4] == b"\x01\x04":
+            self._supbook_types[-1] = SUPBOOK_INTERNAL
+            self._supbook_locals_inx = self._supbook_count - 1
+            if blah:
+                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] == b"\x01\x00\x01\x3A":
+            self._supbook_types[-1] = SUPBOOK_ADDIN
+            self._supbook_addins_inx = self._supbook_count - 1
+            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: fprintf(self.logfile, "SUPBOOK[%d]: DDE/OLE document = %r\n", sbn, url)
+            return
+        self._supbook_types[-1] = SUPBOOK_EXTERNAL
+        if blah: fprintf(self.logfile, "SUPBOOK[%d]: url = %r\n", sbn, url)
+        sheet_names = []
+        for x in range(num_sheets):
+            try:
+                shname, pos = unpack_unicode_update_pos(data, pos, lenlen=2)
+            except struct.error:
+                # #### FIX ME ####
+                # Should implement handling of CONTINUE record(s) ...
+                if self.verbosity:
+                    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: 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.
+        # The SHEETHDR record is followed by a (BOF ... EOF) substream containing
+        # a worksheet.
+        # DEBUG = 1
+        self.derive_encoding()
+        sheet_len = unpack('<i', data[:4])[0]
+        sheet_name = unpack_string(data, 4, self.encoding, lenlen=1)
+        sheetno = self._sheethdr_count
+        assert sheet_name == self._sheet_names[sheetno]
+        self._sheethdr_count += 1
+        BOF_posn = self._position
+        posn = BOF_posn - 4 - len(data)
+        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('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('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('<i', data)[0]
+        if DEBUG: print('SHEETSOFFSET:', posn, file=self.logfile)
+        self._sheetsoffset = posn
+
+    def handle_sst(self, data):
+        # DEBUG = 1
+        if DEBUG:
+            print("SST Processing", file=self.logfile)
+            t0 = perf_counter()
+        nbt = len(data)
+        strlist = [data]
+        uniquestrings = unpack('<i', data[4:8])[0]
+        if DEBUG  or self.verbosity >= 2:
+            fprintf(self.logfile, "SST: unique strings: %d\n", uniquestrings)
+        while 1:
+            code, nb, data = self.get_record_parts_conditional(XL_CONTINUE)
+            if code is None:
+                break
+            nbt += nb
+            if DEBUG >= 2:
+                fprintf(self.logfile, "CONTINUE: adding %d bytes to SST -> %d\n", nb, nbt)
+            strlist.append(data)
+        self._sharedstrings, rt_runlist = unpack_SST_table(strlist, uniquestrings)
+        if self.formatting_info:
+            self._rich_text_runlist_map = rt_runlist
+        if DEBUG:
+            t1 = perf_counter()
+            print("SST processing took %.2f seconds" % (t1 - t0, ), file=self.logfile)
+
+    def handle_writeaccess(self, data):
+        DEBUG = 0
+        if self.biff_version < 80:
+            if not self.encoding:
+                self.raw_user_name = True
+                self.user_name = data
+                return
+            strg = unpack_string(data, 0, self.encoding, lenlen=1)
+        else:
+            try:
+                strg = unpack_unicode(data, 0, lenlen=2)
+            except UnicodeDecodeError:
+                # may have invalid trailing characters
+                strg = unpack_unicode(data.strip(), 0, lenlen=2)
+        if DEBUG: fprintf(self.logfile, "WRITEACCESS: %d bytes; raw=%s %r\n", len(data), self.raw_user_name, strg)
+        strg = strg.rstrip()
+        self.user_name = strg
+
+    def parse_globals(self):
+        # DEBUG = 0
+        # no need to position, just start reading (after the BOF)
+        formatting.initialise_book(self)
+        while 1:
+            rc, length, data = self.get_record_parts()
+            if DEBUG: print("parse_globals: record code is 0x%04x" % rc, file=self.logfile)
+            if rc == XL_SST:
+                self.handle_sst(data)
+            elif rc == XL_FONT or rc == XL_FONT_B3B4:
+                self.handle_font(data)
+            elif rc == XL_FORMAT: # XL_FORMAT2 is BIFF <= 3.0, can't appear in globals
+                self.handle_format(data)
+            elif rc == XL_XF:
+                self.handle_xf(data)
+            elif rc ==  XL_BOUNDSHEET:
+                self.handle_boundsheet(data)
+            elif rc == XL_DATEMODE:
+                self.handle_datemode(data)
+            elif rc == XL_CODEPAGE:
+                self.handle_codepage(data)
+            elif rc == XL_COUNTRY:
+                self.handle_country(data)
+            elif rc == XL_EXTERNNAME:
+                self.handle_externname(data)
+            elif rc == XL_EXTERNSHEET:
+                self.handle_externsheet(data)
+            elif rc == XL_FILEPASS:
+                self.handle_filepass(data)
+            elif rc == XL_WRITEACCESS:
+                self.handle_writeaccess(data)
+            elif rc == XL_SHEETSOFFSET:
+                self.handle_sheetsoffset(data)
+            elif rc == XL_SHEETHDR:
+                self.handle_sheethdr(data)
+            elif rc == XL_SUPBOOK:
+                self.handle_supbook(data)
+            elif rc == XL_NAME:
+                self.handle_name(data)
+            elif rc == XL_PALETTE:
+                self.handle_palette(data)
+            elif rc == XL_STYLE:
+                self.handle_style(data)
+            elif rc & 0xff == 9 and self.verbosity:
+                fprintf(self.logfile, "*** Unexpected BOF at posn %d: 0x%04x len=%d data=%r\n",
+                    self._position - length - 4, rc, length, data)
+            elif rc ==  XL_EOF:
+                self.xf_epilogue()
+                self.names_epilogue()
+                self.palette_epilogue()
+                if not self.encoding:
+                    self.derive_encoding()
+                if self.biff_version == 45:
+                    # DEBUG = 0
+                    if DEBUG: print("global EOF: position", self._position, file=self.logfile)
+                    # if DEBUG:
+                    #     pos = self._position - 4
+                    #     print repr(self.mem[pos:pos+40])
+                return
+            else:
+                # if DEBUG:
+                #     print >> self.logfile, "parse_globals: ignoring record code 0x%04x" % rc
+                pass
+
+    def read(self, pos, length):
+        data = self.mem[pos:pos+length]
+        self._position = pos + len(data)
+        return data
+
+    def getbof(self, rqd_stream):
+        # DEBUG = 1
+        # if DEBUG: print >> self.logfile, "getbof(): position", self._position
+        if DEBUG: print("reqd: 0x%04x" % rqd_stream, file=self.logfile)
+
+        def bof_error(msg):
+            raise XLRDError('Unsupported format, or corrupt file: ' + msg)
+        savpos = self._position
+        opcode = self.get2bytes()
+        if opcode == MY_EOF:
+            bof_error('Expected BOF record; met end of file')
+        if opcode not in bofcodes:
+            bof_error('Expected BOF record; found %r' % self.mem[savpos:savpos+8])
+        length = self.get2bytes()
+        if length == MY_EOF:
+            bof_error('Incomplete BOF record[1]; met end of file')
+        if not (4 <= length <= 20):
+            bof_error(
+                'Invalid length (%d) for BOF record type 0x%04x'
+                % (length, opcode))
+        padding = b'\0' * max(0, boflen[opcode] - length)
+        data = self.read(self._position, length)
+        if DEBUG: fprintf(self.logfile, "\ngetbof(): data=%r\n", data)
+        if len(data) < length:
+            bof_error('Incomplete BOF record[2]; met end of file')
+        data += padding
+        version1 = opcode >> 8
+        version2, streamtype = unpack('<HH', data[0:4])
+        if DEBUG:
+            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("getbof(): BOF found at offset %d; savpos=%d"
+                % (bof_offset, savpos), file=self.logfile)
+        version = build = year = 0
+        if version1 == 0x08:
+            build, year = unpack('<HH', data[4:8])
+            if version2 == 0x0600:
+                version = 80
+            elif version2 == 0x0500:
+                if year < 1994 or build in (2412, 3218, 3321):
+                    version = 50
+                else:
+                    version = 70
+            else:
+                # dodgy one, created by a 3rd-party tool
+                version = {
+                    0x0000: 21,
+                    0x0007: 21,
+                    0x0200: 21,
+                    0x0300: 30,
+                    0x0400: 40,
+                }.get(version2, 0)
+        elif version1 in (0x04, 0x02, 0x00):
+            version = {0x04: 40, 0x02: 30, 0x00: 21}[version1]
+
+        if version == 40 and streamtype == XL_WORKBOOK_GLOBALS_4W:
+            version = 45 # i.e. 4W
+
+        if DEBUG or self.verbosity >= 2:
+            print("BOF: op=0x%04x vers=0x%04x stream=0x%04x buildid=%d buildyr=%d -> BIFF%d"
+                % (opcode, version2, streamtype, build, year, version), file=self.logfile)
+        got_globals = streamtype == XL_WORKBOOK_GLOBALS or (
+            version == 45 and streamtype == XL_WORKBOOK_GLOBALS_4W)
+        if (rqd_stream == XL_WORKBOOK_GLOBALS and got_globals) or streamtype == rqd_stream:
+            return version
+        if version < 50 and streamtype == XL_WORKSHEET:
+            return version
+        if version >= 50 and streamtype == 0x0100:
+            bof_error("Workspace file -- no spreadsheet data")
+        bof_error(
+            'BOF not workbook/worksheet: op=0x%04x vers=0x%04x strm=0x%04x build=%d year=%d -> BIFF%d'
+            % (opcode, version2, streamtype, build, year, version)
+        )
+
+# === helper functions
+
+def expand_cell_address(inrow, incol):
+    # Ref : OOo docs, "4.3.4 Cell Addresses in BIFF8"
+    outrow = inrow
+    if incol & 0x8000:
+        if outrow >= 32768:
+            outrow -= 65536
+        relrow = 1
+    else:
+        relrow = 0
+    outcol = incol & 0xFF
+    if incol & 0x4000:
+        if outcol >= 128:
+            outcol -= 256
+        relcol = 1
+    else:
+        relcol = 0
+    return outrow, outcol, relrow, relcol
+
+def colname(colx, _A2Z="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
+    assert colx >= 0
+    name = UNICODE_LITERAL('')
+    while 1:
+        quot, rem = divmod(colx, 26)
+        name = _A2Z[rem] + name
+        if not quot:
+            return name
+        colx = quot - 1
+
+def display_cell_address(rowx, colx, relrow, relcol):
+    if relrow:
+        rowpart = "(*%s%d)" % ("+-"[rowx < 0], abs(rowx))
+    else:
+        rowpart = "$%d" % (rowx+1,)
+    if relcol:
+        colpart = "(*%s%d)" % ("+-"[colx < 0], abs(colx))
+    else:
+        colpart = "$" + colname(colx)
+    return colpart + rowpart
+
+def unpack_SST_table(datatab, nstrings):
+    "Return list of strings"
+    datainx = 0
+    ndatas = len(datatab)
+    data = datatab[0]
+    datalen = len(data)
+    pos = 8
+    strings = []
+    strappend = strings.append
+    richtext_runs = {}
+    local_unpack = unpack
+    local_min = min
+    local_BYTES_ORD = BYTES_ORD
+    latin_1 = "latin_1"
+    for _unused_i in xrange(nstrings):
+        nchars = local_unpack('<H', data[pos:pos+2])[0]
+        pos += 2
+        options = local_BYTES_ORD(data[pos])
+        pos += 1
+        rtcount = 0
+        phosz = 0
+        if options & 0x08: # richtext
+            rtcount = local_unpack('<H', data[pos:pos+2])[0]
+            pos += 2
+        if options & 0x04: # phonetic
+            phosz = local_unpack('<i', data[pos:pos+4])[0]
+            pos += 4
+        accstrg = UNICODE_LITERAL('')
+        charsgot = 0
+        while 1:
+            charsneed = nchars - charsgot
+            if options & 0x01:
+                # Uncompressed UTF-16
+                charsavail = local_min((datalen - pos) >> 1, charsneed)
+                rawstrg = data[pos:pos+2*charsavail]
+                # if DEBUG: print "SST U16: nchars=%d pos=%d rawstrg=%r" % (nchars, pos, rawstrg)
+                try:
+                    accstrg += unicode(rawstrg, "utf_16_le")
+                except:
+                    # print "SST U16: nchars=%d pos=%d rawstrg=%r" % (nchars, pos, rawstrg)
+                    # Probable cause: dodgy data e.g. unfinished surrogate pair.
+                    # E.g. file unicode2.xls in pyExcelerator's examples has cells containing
+                    # unichr(i) for i in range(0x100000)
+                    # so this will include 0xD800 etc
+                    raise
+                pos += 2*charsavail
+            else:
+                # Note: this is COMPRESSED (not ASCII!) encoding!!!
+                charsavail = local_min(datalen - pos, charsneed)
+                rawstrg = data[pos:pos+charsavail]
+                # if DEBUG: print "SST CMPRSD: nchars=%d pos=%d rawstrg=%r" % (nchars, pos, rawstrg)
+                accstrg += unicode(rawstrg, latin_1)
+                pos += charsavail
+            charsgot += charsavail
+            if charsgot == nchars:
+                break
+            datainx += 1
+            data = datatab[datainx]
+            datalen = len(data)
+            options = local_BYTES_ORD(data[0])
+            pos = 1
+
+        if rtcount:
+            runs = []
+            for runindex in xrange(rtcount):
+                if pos == datalen:
+                    pos = 0
+                    datainx += 1
+                    data = datatab[datainx]
+                    datalen = len(data)
+                runs.append(local_unpack("<HH", data[pos:pos+4]))
+                pos += 4
+            richtext_runs[len(strings)] = runs
+
+        pos += phosz # size of the phonetic stuff to skip
+        if pos >= datalen:
+            # adjust to correct position in next record
+            pos = pos - datalen
+            datainx += 1
+            if datainx < ndatas:
+                data = datatab[datainx]
+                datalen = len(data)
+            else:
+                assert _unused_i == nstrings - 1
+        strappend(accstrg)
+    return strings, richtext_runs

+ 485 - 0
desktop/core/ext-py/xlrd-2.0.1/xlrd/compdoc.py

@@ -0,0 +1,485 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2005-2012 Stephen John Machin, Lingfo Pty Ltd
+# This module is part of the xlrd package, which is released under a
+# BSD-style licence.
+# No part of the content of this file was derived from the works of
+# David Giffin.
+"""
+Implements the minimal functionality required
+to extract a "Workbook" or "Book" stream (as one big string)
+from an OLE2 Compound Document file.
+"""
+from __future__ import print_function
+
+import array
+import sys
+from struct import unpack
+
+from .timemachine import *
+
+#: Magic cookie that should appear in the first 8 bytes of the file.
+SIGNATURE = b"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"
+
+EOCSID = -2
+FREESID = -1
+SATSID = -3
+MSATSID = -4
+EVILSID = -5
+
+class CompDocError(Exception):
+    pass
+
+class DirNode(object):
+
+    def __init__(self, DID, dent, DEBUG=0, logfile=sys.stdout):
+        # dent is the 128-byte directory entry
+        self.DID = DID
+        self.logfile = logfile
+        (cbufsize, self.etype, self.colour, self.left_DID, self.right_DID,
+        self.root_DID) = \
+            unpack('<HBBiii', dent[64:80])
+        (self.first_SID, self.tot_size) = \
+            unpack('<ii', dent[116:124])
+        if cbufsize == 0:
+            self.name = UNICODE_LITERAL('')
+        else:
+            self.name = unicode(dent[0:cbufsize-2], 'utf_16_le') # omit the trailing U+0000
+        self.children = [] # filled in later
+        self.parent = -1 # indicates orphan; fixed up later
+        self.tsinfo = unpack('<IIII', dent[100:116])
+        if DEBUG:
+            self.dump(DEBUG)
+
+    def dump(self, DEBUG=1):
+        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("timestamp info", self.tsinfo, file=self.logfile)
+
+def _build_family_tree(dirlist, parent_DID, child_DID):
+    if child_DID < 0: return
+    _build_family_tree(dirlist, parent_DID, dirlist[child_DID].left_DID)
+    dirlist[parent_DID].children.append(child_DID)
+    dirlist[child_DID].parent = parent_DID
+    _build_family_tree(dirlist, parent_DID, dirlist[child_DID].right_DID)
+    if dirlist[child_DID].etype == 1: # storage
+        _build_family_tree(dirlist, child_DID, dirlist[child_DID].root_DID)
+
+
+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, 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] != b'\xFE\xFF':
+            raise CompDocError('Expected "little-endian" marker, found %r' % mem[28:30])
+        revision, version = unpack('<HH', mem[24:28])
+        if DEBUG:
+            print("\nCompDoc format: version=0x%04x revision=0x%04x" % (version, revision), file=logfile)
+        self.mem = mem
+        ssz, sssz = unpack('<HH', mem[30:34])
+        if ssz > 20: # allows for 2**20 bytes i.e. 1MB
+            print("WARNING: sector size (2**%d) is preposterous; assuming 512 and continuing ..."
+                % ssz, file=logfile)
+            ssz = 9
+        if sssz > ssz:
+            print("WARNING: short stream sector size (2**%d) is preposterous; assuming 64 and continuing ..."
+                % sssz, file=logfile)
+            sssz = 6
+        self.sec_size = sec_size = 1 << ssz
+        self.short_sec_size = 1 << sssz
+        if self.sec_size != 512 or self.short_sec_size != 64:
+            print("@@@@ sec_size=%d short_sec_size=%d" % (self.sec_size, self.short_sec_size), file=logfile)
+        (
+            SAT_tot_secs, self.dir_first_sec_sid, _unused, self.min_size_std_stream,
+            SSAT_first_sec_sid, SSAT_tot_secs,
+            MSATX_first_sec_sid, MSATX_tot_secs,
+        ) = unpack('<iiiiiiii', mem[44:76])
+        mem_data_len = len(mem) - 512
+        mem_data_secs, left_over = divmod(mem_data_len, sec_size)
+        if left_over:
+            #### raise CompDocError("Not a whole number of sectors")
+            mem_data_secs += 1
+            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('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 = (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
+            # should be set to EOCSID ... FREESID and 0 have been met in the wild.
+            pass # Presuming no extension
+        else:
+            sid = MSATX_first_sec_sid
+            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('MSATX: sid=%d (0x%08X)' % (sid, sid), file=logfile)
+                if sid >= mem_data_secs:
+                    msg = "MSAT extension: accessing sector %d but only %d in file" % (sid, mem_data_secs)
+                    if DEBUG > 1:
+                        print(msg, file=logfile)
+                        break
+                    raise CompDocError(msg)
+                elif sid < 0:
+                    raise CompDocError("MSAT extension: invalid sector id: %d" % sid)
+                if seen[sid]:
+                    raise CompDocError("MSAT corruption: seen[%d] == %d" % (sid, seen[sid]))
+                seen[sid] = 1
+                actual_MSATX_sectors += 1
+                if DEBUG and actual_MSATX_sectors > expected_MSATX_sectors:
+                    print("[1]===>>>", mem_data_secs, nent, SAT_sectors_reqd, expected_MSATX_sectors, actual_MSATX_sectors, file=logfile)
+                offset = 512 + sec_size * sid
+                MSAT.extend(unpack(fmt, mem[offset:offset+sec_size]))
+                sid = MSAT.pop() # last sector id is sid of next sector in the chain
+
+        if DEBUG and actual_MSATX_sectors != expected_MSATX_sectors:
+            print("[2]===>>>", mem_data_secs, nent, SAT_sectors_reqd, expected_MSATX_sectors, actual_MSATX_sectors, file=logfile)
+        if DEBUG:
+            print("MSAT: len =", len(MSAT), file=logfile)
+            dump_list(MSAT, 10, logfile)
+        #
+        # === build the SAT ===
+        #
+        self.SAT = []
+        actual_SAT_sectors = 0
+        dump_again = 0
+        for msidx in xrange(len(MSAT)):
+            msid = MSAT[msidx]
+            if msid in (FREESID, EOCSID):
+                # Specification: the MSAT array may be padded with trailing FREESID entries.
+                # Toleration: a FREESID or EOCSID entry anywhere in the MSAT array will be ignored.
+                continue
+            if msid >= mem_data_secs:
+                if not trunc_warned:
+                    print("WARNING *** File is truncated, or OLE2 MSAT is corrupt!!", file=logfile)
+                    print("INFO: Trying to access sector %d but only %d available"
+                        % (msid, mem_data_secs), file=logfile)
+                    trunc_warned = 1
+                MSAT[msidx] = EVILSID
+                dump_again = 1
+                continue
+            elif msid < -2:
+                raise CompDocError("MSAT: invalid sector id: %d" % msid)
+            if seen[msid]:
+                raise CompDocError("MSAT extension corruption: seen[%d] == %d" % (msid, seen[msid]))
+            seen[msid] = 2
+            actual_SAT_sectors += 1
+            if DEBUG and actual_SAT_sectors > SAT_sectors_reqd:
+                print("[3]===>>>", mem_data_secs, nent, SAT_sectors_reqd, expected_MSATX_sectors, actual_MSATX_sectors, actual_SAT_sectors, msid, file=logfile)
+            offset = 512 + sec_size * msid
+            self.SAT.extend(unpack(fmt, mem[offset:offset+sec_size]))
+
+        if DEBUG:
+            print("SAT: len =", len(self.SAT), file=logfile)
+            dump_list(self.SAT, 10, logfile)
+            # print >> logfile, "SAT ",
+            # for i, s in enumerate(self.SAT):
+            #     print >> logfile, "entry: %4d offset: %6d, next entry: %4d" % (i, 512 + sec_size * i, s)
+            #     print >> logfile, "%d:%d " % (i, s),
+            print(file=logfile)
+        if DEBUG and dump_again:
+            print("MSAT: len =", len(MSAT), file=logfile)
+            dump_list(MSAT, 10, logfile)
+            for satx in xrange(mem_data_secs, len(self.SAT)):
+                self.SAT[satx] = EVILSID
+            print("SAT: len =", len(self.SAT), file=logfile)
+            dump_list(self.SAT, 10, logfile)
+        #
+        # === build the directory ===
+        #
+        dbytes = self._get_stream(
+            self.mem, 512, self.SAT, self.sec_size, self.dir_first_sec_sid,
+            name="directory", seen_id=3)
+        dirlist = []
+        did = -1
+        for pos in xrange(0, len(dbytes), 128):
+            did += 1
+            dirlist.append(DirNode(did, dbytes[pos:pos+128], 0, logfile))
+        self.dirlist = dirlist
+        _build_family_tree(dirlist, 0, dirlist[0].root_DID) # and stand well back ...
+        if DEBUG:
+            for d in dirlist:
+                d.dump(DEBUG)
+        #
+        # === get the SSCS ===
+        #
+        sscs_dir = self.dirlist[0]
+        assert sscs_dir.etype == 5 # root entry
+        if sscs_dir.first_SID < 0 or sscs_dir.tot_size == 0:
+            # Problem reported by Frank Hoffsuemmer: some software was
+            # writing -1 instead of -2 (EOCSID) for the first_SID
+            # when the SCCS was empty. Not having EOCSID caused assertion
+            # failure in _get_stream.
+            # Solution: avoid calling _get_stream in any case when the
+            # SCSS appears to be empty.
+            self.SSCS = ""
+        else:
+            self.SSCS = self._get_stream(
+                self.mem, 512, self.SAT, sec_size, sscs_dir.first_SID,
+                sscs_dir.tot_size, name="SSCS", seen_id=4)
+        # if DEBUG: print >> logfile, "SSCS", repr(self.SSCS)
+        #
+        # === build the SSAT ===
+        #
+        self.SSAT = []
+        if SSAT_tot_secs > 0 and sscs_dir.tot_size == 0:
+            print("WARNING *** OLE2 inconsistency: SSCS size is 0 but SSAT size is non-zero", file=logfile)
+        if sscs_dir.tot_size > 0:
+            sid = SSAT_first_sec_sid
+            nsecs = SSAT_tot_secs
+            while sid >= 0 and nsecs > 0:
+                if seen[sid]:
+                    raise CompDocError("SSAT corruption: seen[%d] == %d" % (sid, seen[sid]))
+                seen[sid] = 5
+                nsecs -= 1
+                start_pos = 512 + sid * sec_size
+                news = list(unpack(fmt, mem[start_pos:start_pos+sec_size]))
+                self.SSAT.extend(news)
+                sid = self.SAT[sid]
+            if DEBUG: print("SSAT last sid %d; remaining sectors %d" % (sid, nsecs), file=logfile)
+            assert nsecs == 0 and sid == EOCSID
+        if DEBUG:
+            print("SSAT", file=logfile)
+            dump_list(self.SSAT, 10, logfile)
+        if DEBUG:
+            print("seen", file=logfile)
+            dump_list(seen, 20, logfile)
+
+    def _get_stream(self, mem, base, sat, sec_size, start_sid, size=None, name='', seen_id=None):
+        # print >> self.logfile, "_get_stream", base, sec_size, start_sid, size
+        sectors = []
+        s = start_sid
+        if size is None:
+            # nothing to check against
+            while s >= 0:
+                if seen_id is not None:
+                    if self.seen[s]:
+                        raise CompDocError("%s corruption: seen[%d] == %d" % (name, s, self.seen[s]))
+                    self.seen[s] = seen_id
+                start_pos = base + s * sec_size
+                sectors.append(mem[start_pos:start_pos+sec_size])
+                try:
+                    s = sat[s]
+                except IndexError:
+                    raise CompDocError(
+                        "OLE2 stream %r: sector allocation table invalid entry (%d)" %
+                        (name, s)
+                    )
+            assert s == EOCSID
+        else:
+            todo = size
+            while s >= 0:
+                if seen_id is not None:
+                    if self.seen[s]:
+                        raise CompDocError("%s corruption: seen[%d] == %d" % (name, s, self.seen[s]))
+                    self.seen[s] = seen_id
+                start_pos = base + s * sec_size
+                grab = sec_size
+                if grab > todo:
+                    grab = todo
+                todo -= grab
+                sectors.append(mem[start_pos:start_pos+grab])
+                try:
+                    s = sat[s]
+                except IndexError:
+                    raise CompDocError(
+                        "OLE2 stream %r: sector allocation table invalid entry (%d)" %
+                        (name, s)
+                    )
+            assert s == EOCSID
+            if todo != 0:
+                fprintf(self.logfile,
+                    "WARNING *** OLE2 stream %r: expected size %d, actual size %d\n",
+                    name, size, size - todo)
+
+        return b''.join(sectors)
+
+    def _dir_search(self, path, storage_DID=0):
+        # Return matching DirNode instance, or None
+        head = path[0]
+        tail = path[1:]
+        dl = self.dirlist
+        for child in dl[storage_DID].children:
+            if dl[child].name.lower() == head.lower():
+                et = dl[child].etype
+                if et == 2:
+                    return dl[child]
+                if et == 1:
+                    if not tail:
+                        raise CompDocError("Requested component is a 'storage'")
+                    return self._dir_search(tail, child)
+                dl[child].dump(1)
+                raise CompDocError("Requested stream is not a 'user stream'")
+        return None
+
+
+    def get_named_stream(self, qname):
+        """
+        Interrogate the compound document's directory; return the stream as a
+        string if found, otherwise return ``None``.
+
+        :param qname:
+          Name of the desired stream e.g. ``'Workbook'``.
+          Should be in Unicode or convertible thereto.
+        """
+        d = self._dir_search(qname.split("/"))
+        if d is None:
+            return None
+        if d.tot_size >= self.min_size_std_stream:
+            return self._get_stream(
+                self.mem, 512, self.SAT, self.sec_size, d.first_SID,
+                d.tot_size, name=qname, seen_id=d.DID+6)
+        else:
+            return self._get_stream(
+                self.SSCS, 0, self.SSAT, self.short_sec_size, d.first_SID,
+                d.tot_size, name=qname + " (from SSCS)", seen_id=None)
+
+    def locate_named_stream(self, qname):
+        """
+        Interrogate the compound document's directory.
+
+        If the named stream is not found, ``(None, 0, 0)`` will be returned.
+
+        If the named stream is found and is contiguous within the original
+        byte sequence (``mem``) used when the document was opened,
+        then ``(mem, offset_to_start_of_stream, length_of_stream)`` is returned.
+
+        Otherwise a new string is built from the fragments and
+        ``(new_string, 0, length_of_stream)`` is returned.
+
+        :param qname:
+          Name of the desired stream e.g. ``'Workbook'``.
+          Should be in Unicode or convertible thereto.
+        """
+        d = self._dir_search(qname.split("/"))
+        if d is None:
+            return (None, 0, 0)
+        if d.tot_size > self.mem_data_len:
+            raise CompDocError("%r stream length (%d bytes) > file data size (%d bytes)"
+                % (qname, d.tot_size, self.mem_data_len))
+        if d.tot_size >= self.min_size_std_stream:
+            result = self._locate_stream(
+                self.mem, 512, self.SAT, self.sec_size, d.first_SID,
+                d.tot_size, qname, d.DID+6)
+            if self.DEBUG:
+                print("\nseen", file=self.logfile)
+                dump_list(self.seen, 20, self.logfile)
+            return result
+        else:
+            return (
+                self._get_stream(
+                    self.SSCS, 0, self.SSAT, self.short_sec_size, d.first_SID,
+                    d.tot_size, qname + " (from SSCS)", None),
+                0,
+                d.tot_size,
+            )
+
+    def _locate_stream(self, mem, base, sat, sec_size, start_sid, expected_stream_size, qname, seen_id):
+        # print >> self.logfile, "_locate_stream", base, sec_size, start_sid, expected_stream_size
+        s = start_sid
+        if s < 0:
+            raise CompDocError("_locate_stream: start_sid (%d) is -ve" % start_sid)
+        p = -99 # dummy previous SID
+        start_pos = -9999
+        end_pos = -8888
+        slices = []
+        tot_found = 0
+        found_limit = (expected_stream_size + sec_size - 1) // sec_size
+        while s >= 0:
+            if self.seen[s]:
+                if not self.ignore_workbook_corruption:
+                    print("_locate_stream(%s): seen" % qname, file=self.logfile); dump_list(self.seen, 20, self.logfile)
+                    raise CompDocError("%s corruption: seen[%d] == %d" % (qname, s, self.seen[s]))
+            self.seen[s] = seen_id
+            tot_found += 1
+            if tot_found > found_limit:
+                # Note: expected size rounded up to higher sector
+                raise CompDocError(
+                    "%s: size exceeds expected %d bytes; corrupt?"
+                    % (qname, found_limit * sec_size)
+                )
+            if s == p+1:
+                # contiguous sectors
+                end_pos += sec_size
+            else:
+                # start new slice
+                if p >= 0:
+                    # not first time
+                    slices.append((start_pos, end_pos))
+                start_pos = base + s * sec_size
+                end_pos = start_pos + sec_size
+            p = s
+            s = sat[s]
+        assert s == EOCSID
+        assert tot_found == found_limit
+        # print >> self.logfile, "_locate_stream(%s): seen" % qname; dump_list(self.seen, 20, self.logfile)
+        if not slices:
+            # The stream is contiguous ... just what we like!
+            return (mem, start_pos, expected_stream_size)
+        slices.append((start_pos, end_pos))
+        # print >> self.logfile, "+++>>> %d fragments" % len(slices)
+        return (b''.join(mem[start_pos:end_pos] for start_pos, end_pos in slices), 0, expected_stream_size)
+
+# ==========================================================================================
+def x_dump_line(alist, stride, f, dpos, equal=0):
+    print("%5d%s" % (dpos, " ="[equal]), end=' ', file=f)
+    for value in alist[dpos:dpos + stride]:
+        print(str(value), end=' ', file=f)
+    print(file=f)
+
+def dump_list(alist, stride, f=sys.stdout):
+    def _dump_line(dpos, equal=0):
+        print("%5d%s" % (dpos, " ="[equal]), end=' ', file=f)
+        for value in alist[dpos:dpos + stride]:
+            print(str(value), end=' ', file=f)
+        print(file=f)
+    pos = None
+    oldpos = None
+    for pos in xrange(0, len(alist), stride):
+        if oldpos is None:
+            _dump_line(pos)
+            oldpos = pos
+        elif alist[pos:pos+stride] != alist[oldpos:oldpos+stride]:
+            if pos - oldpos > stride:
+                _dump_line(pos - stride, equal=1)
+            _dump_line(pos)
+            oldpos = pos
+    if oldpos is not None and pos is not None and pos != oldpos:
+        _dump_line(pos, equal=1)

+ 1324 - 0
desktop/core/ext-py/xlrd-2.0.1/xlrd/formatting.py

@@ -0,0 +1,1324 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2005-2012 Stephen John Machin, Lingfo Pty Ltd
+# This module is part of the xlrd package, which is released under a
+# BSD-style licence.
+# No part of the content of this file was derived from the works of
+# David Giffin.
+"""
+Module for formatting information.
+"""
+
+from __future__ import print_function
+
+import re
+from struct import unpack
+
+from .biffh import (
+    FDT, FGE, FNU, FTX, FUN, XL_CELL_DATE, XL_CELL_NUMBER, XL_CELL_TEXT,
+    XL_FORMAT, XL_FORMAT2, BaseObject, XLRDError, fprintf, unpack_string,
+    unpack_unicode, upkbits, upkbitsL,
+)
+from .timemachine import *
+
+DEBUG = 0
+
+_cellty_from_fmtty = {
+    FNU: XL_CELL_NUMBER,
+    FUN: XL_CELL_NUMBER,
+    FGE: XL_CELL_NUMBER,
+    FDT: XL_CELL_DATE,
+    FTX: XL_CELL_NUMBER, # Yes, a number can be formatted as text.
+}
+
+excel_default_palette_b5 = (
+    (  0,   0,   0), (255, 255, 255), (255,   0,   0), (  0, 255,   0),
+    (  0,   0, 255), (255, 255,   0), (255,   0, 255), (  0, 255, 255),
+    (128,   0,   0), (  0, 128,   0), (  0,   0, 128), (128, 128,   0),
+    (128,   0, 128), (  0, 128, 128), (192, 192, 192), (128, 128, 128),
+    (153, 153, 255), (153,  51, 102), (255, 255, 204), (204, 255, 255),
+    (102,   0, 102), (255, 128, 128), (  0, 102, 204), (204, 204, 255),
+    (  0,   0, 128), (255,   0, 255), (255, 255,   0), (  0, 255, 255),
+    (128,   0, 128), (128,   0,   0), (  0, 128, 128), (  0,   0, 255),
+    (  0, 204, 255), (204, 255, 255), (204, 255, 204), (255, 255, 153),
+    (153, 204, 255), (255, 153, 204), (204, 153, 255), (227, 227, 227),
+    ( 51, 102, 255), ( 51, 204, 204), (153, 204,   0), (255, 204,   0),
+    (255, 153,   0), (255, 102,   0), (102, 102, 153), (150, 150, 150),
+    (  0,  51, 102), ( 51, 153, 102), (  0,  51,   0), ( 51,  51,   0),
+    (153,  51,   0), (153,  51, 102), ( 51,  51, 153), ( 51,  51,  51),
+)
+
+excel_default_palette_b2 = excel_default_palette_b5[:16]
+
+# Following table borrowed from Gnumeric 1.4 source.
+# Checked against OOo docs and MS docs.
+excel_default_palette_b8 = ( # (red, green, blue)
+    (  0,  0,  0), (255,255,255), (255,  0,  0), (  0,255,  0), # 0
+    (  0,  0,255), (255,255,  0), (255,  0,255), (  0,255,255), # 4
+    (128,  0,  0), (  0,128,  0), (  0,  0,128), (128,128,  0), # 8
+    (128,  0,128), (  0,128,128), (192,192,192), (128,128,128), # 12
+    (153,153,255), (153, 51,102), (255,255,204), (204,255,255), # 16
+    (102,  0,102), (255,128,128), (  0,102,204), (204,204,255), # 20
+    (  0,  0,128), (255,  0,255), (255,255,  0), (  0,255,255), # 24
+    (128,  0,128), (128,  0,  0), (  0,128,128), (  0,  0,255), # 28
+    (  0,204,255), (204,255,255), (204,255,204), (255,255,153), # 32
+    (153,204,255), (255,153,204), (204,153,255), (255,204,153), # 36
+    ( 51,102,255), ( 51,204,204), (153,204,  0), (255,204,  0), # 40
+    (255,153,  0), (255,102,  0), (102,102,153), (150,150,150), # 44
+    (  0, 51,102), ( 51,153,102), (  0, 51,  0), ( 51, 51,  0), # 48
+    (153, 51,  0), (153, 51,102), ( 51, 51,153), ( 51, 51, 51), # 52
+)
+
+default_palette = {
+    80: excel_default_palette_b8,
+    70: excel_default_palette_b5,
+    50: excel_default_palette_b5,
+    45: excel_default_palette_b2,
+    40: excel_default_palette_b2,
+    30: excel_default_palette_b2,
+    21: excel_default_palette_b2,
+    20: excel_default_palette_b2,
+}
+
+# 00H = Normal
+# 01H = RowLevel_lv (see next field)
+# 02H = ColLevel_lv (see next field)
+# 03H = Comma
+# 04H = Currency
+# 05H = Percent
+# 06H = Comma [0] (BIFF4-BIFF8)
+# 07H = Currency [0] (BIFF4-BIFF8)
+# 08H = Hyperlink (BIFF8)
+# 09H = Followed Hyperlink (BIFF8)
+built_in_style_names = [
+    "Normal",
+    "RowLevel_",
+    "ColLevel_",
+    "Comma",
+    "Currency",
+    "Percent",
+    "Comma [0]",
+    "Currency [0]",
+    "Hyperlink",
+    "Followed Hyperlink",
+]
+
+def initialise_colour_map(book):
+    book.colour_map = {}
+    book.colour_indexes_used = {}
+    if not book.formatting_info:
+        return
+    # Add the 8 invariant colours
+    for i in xrange(8):
+        book.colour_map[i] = excel_default_palette_b8[i]
+    # Add the default palette depending on the version
+    dpal = default_palette[book.biff_version]
+    ndpal = len(dpal)
+    for i in xrange(ndpal):
+        book.colour_map[i+8] = dpal[i]
+    # Add the specials -- None means the RGB value is not known
+    # System window text colour for border lines
+    book.colour_map[ndpal+8] = None
+    # System window background colour for pattern background
+    book.colour_map[ndpal+8+1] = None
+    # System ToolTip text colour (used in note objects)
+    book.colour_map[0x51] = None
+    # 32767, system window text colour for fonts
+    book.colour_map[0x7FFF] = None
+
+
+def nearest_colour_index(colour_map, rgb, debug=0):
+    """
+    General purpose function. Uses Euclidean distance.
+    So far used only for pre-BIFF8 ``WINDOW2`` record.
+    Doesn't have to be fast.
+    Doesn't have to be fancy.
+    """
+    best_metric = 3 * 256 * 256
+    best_colourx = 0
+    for colourx, cand_rgb in colour_map.items():
+        if cand_rgb is None:
+            continue
+        metric = 0
+        for v1, v2 in zip(rgb, cand_rgb):
+            metric += (v1 - v2) * (v1 - v2)
+        if metric < best_metric:
+            best_metric = metric
+            best_colourx = colourx
+            if metric == 0:
+                break
+    if 0 and debug:
+        print("nearest_colour_index for %r is %r -> %r; best_metric is %d"
+            % (rgb, best_colourx, colour_map[best_colourx], best_metric))
+    return best_colourx
+
+class EqNeAttrs(object):
+    """
+    This mixin class exists solely so that :class:`Format`, :class:`Font`, and
+    :class:`XF` objects can be compared by value of their attributes.
+    """
+
+    def __eq__(self, other):
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        return self.__dict__ != other.__dict__
+
+class Font(BaseObject, EqNeAttrs):
+    """
+    An Excel "font" contains the details of not only what is normally
+    considered a font, but also several other display attributes.
+    Items correspond to those in the Excel UI's Format -> Cells -> Font tab.
+
+    .. versionadded:: 0.6.1
+    """
+
+    #: 1 = Characters are bold. Redundant; see "weight" attribute.
+    bold = 0
+
+    #: Values:
+    #: ::
+    #:
+    #:   0 = ANSI Latin
+    #:   1 = System default
+    #:   2 = Symbol,
+    #:   77 = Apple Roman,
+    #:   128 = ANSI Japanese Shift-JIS,
+    #:   129 = ANSI Korean (Hangul),
+    #:   130 = ANSI Korean (Johab),
+    #:   134 = ANSI Chinese Simplified GBK,
+    #:   136 = ANSI Chinese Traditional BIG5,
+    #:   161 = ANSI Greek,
+    #:   162 = ANSI Turkish,
+    #:   163 = ANSI Vietnamese,
+    #:   177 = ANSI Hebrew,
+    #:   178 = ANSI Arabic,
+    #:   186 = ANSI Baltic,
+    #:   204 = ANSI Cyrillic,
+    #:   222 = ANSI Thai,
+    #:   238 = ANSI Latin II (Central European),
+    #:   255 = OEM Latin I
+    character_set = 0
+
+    #: An explanation of "colour index" is given in :ref:`palette`.
+    colour_index = 0
+
+    #: 1 = Superscript, 2 = Subscript.
+    escapement = 0
+
+    #: Values:
+    #: ::
+    #:
+    #:   0 = None (unknown or don't care)
+    #:   1 = Roman (variable width, serifed)
+    #:   2 = Swiss (variable width, sans-serifed)
+    #:   3 = Modern (fixed width, serifed or sans-serifed)
+    #:   4 = Script (cursive)
+    #:   5 = Decorative (specialised, for example Old English, Fraktur)
+    family = 0
+
+    #: The 0-based index used to refer to this Font() instance.
+    #: Note that index 4 is never used; xlrd supplies a dummy place-holder.
+    font_index = 0
+
+    #: Height of the font (in twips). A twip = 1/20 of a point.
+    height = 0
+
+    #: 1 = Characters are italic.
+    italic = 0
+
+    #: The name of the font. Example: ``"Arial"``.
+    name = UNICODE_LITERAL("")
+
+    #: 1 = Characters are struck out.
+    struck_out = 0
+
+    #: Values:
+    #: ::
+    #:
+    #:   0 = None
+    #:   1 = Single;  0x21 (33) = Single accounting
+    #:   2 = Double;  0x22 (34) = Double accounting
+    underline_type = 0
+
+    #: 1 = Characters are underlined. Redundant; see
+    #: :attr:`underline_type` attribute.
+    underlined = 0
+
+    #: Font weight (100-1000). Standard values are 400 for normal text
+    #: and 700 for bold text.
+    weight = 400
+
+    #: 1 = Font is outline style (Macintosh only)
+    outline = 0
+
+    #: 1 = Font is shadow style (Macintosh only)
+    shadow = 0
+
+def handle_efont(book, data): # BIFF2 only
+    if not book.formatting_info:
+        return
+    book.font_list[-1].colour_index = unpack('<H', data)[0]
+
+def handle_font(book, data):
+    if not book.formatting_info:
+        return
+    if not book.encoding:
+        book.derive_encoding()
+    blah = DEBUG or book.verbosity >= 2
+    bv = book.biff_version
+    k = len(book.font_list)
+    if k == 4:
+        f = Font()
+        f.name = UNICODE_LITERAL('Dummy Font')
+        f.font_index = k
+        book.font_list.append(f)
+        k += 1
+    f = Font()
+    f.font_index = k
+    book.font_list.append(f)
+    if bv >= 50:
+        (
+            f.height, option_flags, f.colour_index, f.weight,
+            f.escapement, f.underline_type, f.family,
+            f.character_set,
+        ) = unpack('<HHHHHBBB', data[0:13])
+        f.bold = option_flags & 1
+        f.italic = (option_flags & 2) >> 1
+        f.underlined = (option_flags & 4) >> 2
+        f.struck_out = (option_flags & 8) >> 3
+        f.outline = (option_flags & 16) >> 4
+        f.shadow = (option_flags & 32) >> 5
+        if bv >= 80:
+            f.name = unpack_unicode(data, 14, lenlen=1)
+        else:
+            f.name = unpack_string(data, 14, book.encoding, lenlen=1)
+    elif bv >= 30:
+        f.height, option_flags, f.colour_index = unpack('<HHH', data[0:6])
+        f.bold = option_flags & 1
+        f.italic = (option_flags & 2) >> 1
+        f.underlined = (option_flags & 4) >> 2
+        f.struck_out = (option_flags & 8) >> 3
+        f.outline = (option_flags & 16) >> 4
+        f.shadow = (option_flags & 32) >> 5
+        f.name = unpack_string(data, 6, book.encoding, lenlen=1)
+        # Now cook up the remaining attributes ...
+        f.weight = [400, 700][f.bold]
+        f.escapement = 0 # None
+        f.underline_type = f.underlined # None or Single
+        f.family = 0 # Unknown / don't care
+        f.character_set = 1 # System default (0 means "ANSI Latin")
+    else: # BIFF2
+        f.height, option_flags = unpack('<HH', data[0:4])
+        f.colour_index = 0x7FFF # "system window text colour"
+        f.bold = option_flags & 1
+        f.italic = (option_flags & 2) >> 1
+        f.underlined = (option_flags & 4) >> 2
+        f.struck_out = (option_flags & 8) >> 3
+        f.outline = 0
+        f.shadow = 0
+        f.name = unpack_string(data, 4, book.encoding, lenlen=1)
+        # Now cook up the remaining attributes ...
+        f.weight = [400, 700][f.bold]
+        f.escapement = 0 # None
+        f.underline_type = f.underlined # None or Single
+        f.family = 0 # Unknown / don't care
+        f.character_set = 1 # System default (0 means "ANSI Latin")
+    if blah:
+        f.dump(
+            book.logfile,
+            header="--- handle_font: font[%d] ---" % f.font_index,
+            footer="-------------------",
+        )
+
+# === "Number formats" ===
+
+class Format(BaseObject, EqNeAttrs):
+    """
+    "Number format" information from a ``FORMAT`` record.
+
+    .. versionadded:: 0.6.1
+    """
+
+    #: The key into :attr:`~xlrd.book.Book.format_map`
+    format_key = 0
+
+    #: A classification that has been inferred from the format string.
+    #: Currently, this is used only to distinguish between numbers and dates.
+    #: Values::
+    #:
+    #:   FUN = 0 # unknown
+    #:   FDT = 1 # date
+    #:   FNU = 2 # number
+    #:   FGE = 3 # general
+    #:   FTX = 4 # text
+    type = FUN
+
+    #: The format string
+    format_str = UNICODE_LITERAL('')
+
+    def __init__(self, format_key, ty, format_str):
+        self.format_key = format_key
+        self.type = ty
+        self.format_str = format_str
+
+std_format_strings = {
+    # "std" == "standard for US English locale"
+    # #### TODO ... a lot of work to tailor these to the user's locale.
+    # See e.g. gnumeric-1.x.y/src/formats.c
+    0x00: "General",
+    0x01: "0",
+    0x02: "0.00",
+    0x03: "#,##0",
+    0x04: "#,##0.00",
+    0x05: "$#,##0_);($#,##0)",
+    0x06: "$#,##0_);[Red]($#,##0)",
+    0x07: "$#,##0.00_);($#,##0.00)",
+    0x08: "$#,##0.00_);[Red]($#,##0.00)",
+    0x09: "0%",
+    0x0a: "0.00%",
+    0x0b: "0.00E+00",
+    0x0c: "# ?/?",
+    0x0d: "# ??/??",
+    0x0e: "m/d/yy",
+    0x0f: "d-mmm-yy",
+    0x10: "d-mmm",
+    0x11: "mmm-yy",
+    0x12: "h:mm AM/PM",
+    0x13: "h:mm:ss AM/PM",
+    0x14: "h:mm",
+    0x15: "h:mm:ss",
+    0x16: "m/d/yy h:mm",
+    0x25: "#,##0_);(#,##0)",
+    0x26: "#,##0_);[Red](#,##0)",
+    0x27: "#,##0.00_);(#,##0.00)",
+    0x28: "#,##0.00_);[Red](#,##0.00)",
+    0x29: "_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)",
+    0x2a: "_($* #,##0_);_($* (#,##0);_($* \"-\"_);_(@_)",
+    0x2b: "_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)",
+    0x2c: "_($* #,##0.00_);_($* (#,##0.00);_($* \"-\"??_);_(@_)",
+    0x2d: "mm:ss",
+    0x2e: "[h]:mm:ss",
+    0x2f: "mm:ss.0",
+    0x30: "##0.0E+0",
+    0x31: "@",
+}
+
+fmt_code_ranges = [ # both-inclusive ranges of "standard" format codes
+    # Source: the openoffice.org doc't
+    # and the OOXML spec Part 4, section 3.8.30
+    ( 0,  0, FGE),
+    ( 1, 13, FNU),
+    (14, 22, FDT),
+    (27, 36, FDT), # CJK date formats
+    (37, 44, FNU),
+    (45, 47, FDT),
+    (48, 48, FNU),
+    (49, 49, FTX),
+    # Gnumeric assumes (or assumed) that built-in formats finish at 49, not at 163
+    (50, 58, FDT), # CJK date formats
+    (59, 62, FNU), # Thai number (currency?) formats
+    (67, 70, FNU), # Thai number (currency?) formats
+    (71, 81, FDT), # Thai date formats
+]
+
+std_format_code_types = {}
+for lo, hi, ty in fmt_code_ranges:
+    for x in xrange(lo, hi+1):
+        std_format_code_types[x] = ty
+del lo, hi, ty, x
+
+date_chars = UNICODE_LITERAL('ymdhs') # year, month/minute, day, hour, second
+date_char_dict = {}
+for _c in date_chars + date_chars.upper():
+    date_char_dict[_c] = 5
+del _c, date_chars
+
+skip_char_dict = {}
+for _c in UNICODE_LITERAL('$-+/(): '):
+    skip_char_dict[_c] = 1
+
+num_char_dict = {
+    UNICODE_LITERAL('0'): 5,
+    UNICODE_LITERAL('#'): 5,
+    UNICODE_LITERAL('?'): 5,
+}
+
+non_date_formats = {
+    UNICODE_LITERAL('0.00E+00'):1,
+    UNICODE_LITERAL('##0.0E+0'):1,
+    UNICODE_LITERAL('General') :1,
+    UNICODE_LITERAL('GENERAL') :1, # OOo Calc 1.1.4 does this.
+    UNICODE_LITERAL('general') :1,  # pyExcelerator 0.6.3 does this.
+    UNICODE_LITERAL('@')       :1,
+}
+
+fmt_bracketed_sub = re.compile(r'\[[^]]*\]').sub
+
+# Boolean format strings (actual cases)
+# '"Yes";"Yes";"No"'
+# '"True";"True";"False"'
+# '"On";"On";"Off"'
+
+def is_date_format_string(book, fmt):
+    # Heuristics:
+    # Ignore "text" and [stuff in square brackets (aarrgghh -- see below)].
+    # Handle backslashed-escaped chars properly.
+    # E.g. hh\hmm\mss\s should produce a display like 23h59m59s
+    # Date formats have one or more of ymdhs (caseless) in them.
+    # Numeric formats have # and 0.
+    # N.B. 'General"."' hence get rid of "text" first.
+    # TODO: Find where formats are interpreted in Gnumeric
+    # TODO: '[h]\\ \\h\\o\\u\\r\\s' ([h] means don't care about hours > 23)
+    state = 0
+    s = ''
+
+    for c in fmt:
+        if state == 0:
+            if c == UNICODE_LITERAL('"'):
+                state = 1
+            elif c in UNICODE_LITERAL(r"\_*"):
+                state = 2
+            elif c in skip_char_dict:
+                pass
+            else:
+                s += c
+        elif state == 1:
+            if c == UNICODE_LITERAL('"'):
+                state = 0
+        elif state == 2:
+            # Ignore char after backslash, underscore or asterisk
+            state = 0
+        assert 0 <= state <= 2
+    if book.verbosity >= 4:
+        print("is_date_format_string: reduced format is %s" % REPR(s), file=book.logfile)
+    s = fmt_bracketed_sub('', s)
+    if s in non_date_formats:
+        return False
+    state = 0
+    separator = ";"
+    got_sep = 0
+    date_count = num_count = 0
+    for c in s:
+        if c in date_char_dict:
+            date_count += date_char_dict[c]
+        elif c in num_char_dict:
+            num_count += num_char_dict[c]
+        elif c == separator:
+            got_sep = 1
+    # print num_count, date_count, repr(fmt)
+    if date_count and not num_count:
+        return True
+    if num_count and not date_count:
+        return False
+    if date_count:
+        if book.verbosity:
+            fprintf(book.logfile,
+                'WARNING *** is_date_format: ambiguous d=%d n=%d fmt=%r\n',
+                date_count, num_count, fmt)
+    elif not got_sep:
+        if book.verbosity:
+            fprintf(book.logfile,
+                "WARNING *** format %r produces constant result\n",
+                fmt)
+    return date_count > num_count
+
+def handle_format(self, data, rectype=XL_FORMAT):
+    DEBUG = 0
+    bv = self.biff_version
+    if rectype == XL_FORMAT2:
+        bv = min(bv, 30)
+    if not self.encoding:
+        self.derive_encoding()
+    strpos = 2
+    if bv >= 50:
+        fmtkey = unpack('<H', data[0:2])[0]
+    else:
+        fmtkey = self.actualfmtcount
+        if bv <= 30:
+            strpos = 0
+    self.actualfmtcount += 1
+    if bv >= 80:
+        unistrg = unpack_unicode(data, 2)
+    else:
+        unistrg = unpack_string(data, strpos, self.encoding, lenlen=1)
+    blah = DEBUG or self.verbosity >= 3
+    if blah:
+        fprintf(self.logfile,
+            "FORMAT: count=%d fmtkey=0x%04x (%d) s=%r\n",
+            self.actualfmtcount, fmtkey, fmtkey, unistrg)
+    is_date_s = self.is_date_format_string(unistrg)
+    ty = [FGE, FDT][is_date_s]
+    if not(fmtkey > 163 or bv < 50):
+        # user_defined if fmtkey > 163
+        # N.B. Gnumeric incorrectly starts these at 50 instead of 164 :-(
+        # if earlier than BIFF 5, standard info is useless
+        std_ty = std_format_code_types.get(fmtkey, FUN)
+        # print "std ty", std_ty
+        is_date_c = std_ty == FDT
+        if self.verbosity and 0 < fmtkey < 50 and (is_date_c ^ is_date_s):
+            DEBUG = 2
+            fprintf(self.logfile,
+                "WARNING *** Conflict between "
+                "std format key %d and its format string %r\n",
+                fmtkey, unistrg)
+    if DEBUG == 2:
+        fprintf(self.logfile,
+            "ty: %d; is_date_c: %r; is_date_s: %r; fmt_strg: %r",
+            ty, is_date_c, is_date_s, unistrg)
+    fmtobj = Format(fmtkey, ty, unistrg)
+    if blah:
+        fmtobj.dump(self.logfile,
+            header="--- handle_format [%d] ---" % (self.actualfmtcount-1, ))
+    self.format_map[fmtkey] = fmtobj
+    self.format_list.append(fmtobj)
+
+# =============================================================================
+
+def handle_palette(book, data):
+    if not book.formatting_info:
+        return
+    blah = DEBUG or book.verbosity >= 2
+    n_colours, = unpack('<H', data[:2])
+    expected_n_colours = (16, 56)[book.biff_version >= 50]
+    if (DEBUG or book.verbosity >= 1) and n_colours != expected_n_colours:
+        fprintf(book.logfile,
+            "NOTE *** Expected %d colours in PALETTE record, found %d\n",
+            expected_n_colours, n_colours)
+    elif blah:
+        fprintf(book.logfile,
+            "PALETTE record with %d colours\n", n_colours)
+    fmt = '<xx%di' % n_colours # use i to avoid long integers
+    expected_size = 4 * n_colours + 2
+    actual_size = len(data)
+    tolerance = 4
+    if not expected_size <= actual_size <= expected_size + tolerance:
+        raise XLRDError('PALETTE record: expected size %d, actual size %d' % (expected_size, actual_size))
+    colours = unpack(fmt, data[:expected_size])
+    assert book.palette_record == [] # There should be only 1 PALETTE record
+    # a colour will be 0xbbggrr
+    # IOW, red is at the little end
+    for i in xrange(n_colours):
+        c = colours[i]
+        red   =  c        & 0xff
+        green = (c >>  8) & 0xff
+        blue  = (c >> 16) & 0xff
+        old_rgb = book.colour_map[8+i]
+        new_rgb = (red, green, blue)
+        book.palette_record.append(new_rgb)
+        book.colour_map[8+i] = new_rgb
+        if blah:
+            if new_rgb != old_rgb:
+                print("%2d: %r -> %r" % (i, old_rgb, new_rgb), file=book.logfile)
+
+def palette_epilogue(book):
+    # Check colour indexes in fonts etc.
+    # This must be done here as FONT records
+    # come *before* the PALETTE record :-(
+    for font in book.font_list:
+        if font.font_index == 4: # the missing font record
+            continue
+        cx = font.colour_index
+        if cx == 0x7fff: # system window text colour
+            continue
+        if cx in book.colour_map:
+            book.colour_indexes_used[cx] = 1
+        elif book.verbosity:
+            print("Size of colour table:", len(book.colour_map), file=book.logfile)
+            fprintf(book.logfile, "*** Font #%d (%r): colour index 0x%04x is unknown\n",
+                font.font_index, font.name, cx)
+    if book.verbosity >= 1:
+        used = sorted(book.colour_indexes_used.keys())
+        print("\nColour indexes used:\n%r\n" % used, file=book.logfile)
+
+def handle_style(book, data):
+    if not book.formatting_info:
+        return
+    blah = DEBUG or book.verbosity >= 2
+    bv = book.biff_version
+    flag_and_xfx, built_in_id, level = unpack('<HBB', data[:4])
+    xf_index = flag_and_xfx & 0x0fff
+    if data == b"\0\0\0\0" and "Normal" not in book.style_name_map:
+        # Erroneous record (doesn't have built-in bit set).
+        # Example file supplied by Jeff Bell.
+        built_in = 1
+        built_in_id = 0
+        xf_index = 0
+        name = "Normal"
+        level = 255
+    elif flag_and_xfx & 0x8000:
+        # built-in style
+        built_in = 1
+        name = built_in_style_names[built_in_id]
+        if 1 <= built_in_id <= 2:
+            name += str(level + 1)
+    else:
+        # user-defined style
+        built_in = 0
+        built_in_id = 0
+        level = 0
+        if bv >= 80:
+            try:
+                name = unpack_unicode(data, 2, lenlen=2)
+            except UnicodeDecodeError:
+                print("STYLE: built_in=%d xf_index=%d built_in_id=%d level=%d"
+                    % (built_in, xf_index, built_in_id, level), file=book.logfile)
+                print("raw bytes:", repr(data[2:]), file=book.logfile)
+                raise
+        else:
+            name = unpack_string(data, 2, book.encoding, lenlen=1)
+        if blah and not name:
+            print("WARNING *** A user-defined style has a zero-length name", file=book.logfile)
+    book.style_name_map[name] = (built_in, xf_index)
+    if blah:
+        fprintf(book.logfile, "STYLE: built_in=%d xf_index=%d built_in_id=%d level=%d name=%r\n",
+            built_in, xf_index, built_in_id, level, name)
+
+def check_colour_indexes_in_obj(book, obj, orig_index):
+    alist = sorted(obj.__dict__.items())
+    for attr, nobj in alist:
+        if hasattr(nobj, 'dump'):
+            check_colour_indexes_in_obj(book, nobj, orig_index)
+        elif attr.find('colour_index') >= 0:
+            if nobj in book.colour_map:
+                book.colour_indexes_used[nobj] = 1
+                continue
+            oname = obj.__class__.__name__
+            print("*** xf #%d : %s.%s =  0x%04x (unknown)"
+                % (orig_index, oname, attr, nobj), file=book.logfile)
+
+def fill_in_standard_formats(book):
+    for x in std_format_code_types.keys():
+        if x not in book.format_map:
+            ty = std_format_code_types[x]
+            # Note: many standard format codes (mostly CJK date formats) have
+            # format strings that vary by locale; xlrd does not (yet)
+            # handle those; the type (date or numeric) is recorded but the fmt_str will be None.
+            fmt_str = std_format_strings.get(x)
+            fmtobj = Format(x, ty, fmt_str)
+            book.format_map[x] = fmtobj
+
+def handle_xf(self, data):
+    # self is a Book instance
+    # DEBUG = 0
+    blah = DEBUG or self.verbosity >= 3
+    bv = self.biff_version
+    xf = XF()
+    xf.alignment = XFAlignment()
+    xf.alignment.indent_level = 0
+    xf.alignment.shrink_to_fit = 0
+    xf.alignment.text_direction = 0
+    xf.border = XFBorder()
+    xf.border.diag_up = 0
+    xf.border.diag_down = 0
+    xf.border.diag_colour_index = 0
+    xf.border.diag_line_style = 0 # no line
+    xf.background = XFBackground()
+    xf.protection = XFProtection()
+    # fill in the known standard formats
+    if bv >= 50 and not self.xfcount:
+        # i.e. do this once before we process the first XF record
+        fill_in_standard_formats(self)
+    if bv >= 80:
+        unpack_fmt = '<HHHBBBBIiH'
+        (
+            xf.font_index, xf.format_key, pkd_type_par,
+            pkd_align1, xf.alignment.rotation, pkd_align2,
+            pkd_used, pkd_brdbkg1, pkd_brdbkg2, pkd_brdbkg3,
+        ) = unpack(unpack_fmt, data[0:20])
+        upkbits(xf.protection, pkd_type_par, (
+            (0, 0x01, 'cell_locked'),
+            (1, 0x02, 'formula_hidden'),
+        ))
+        upkbits(xf, pkd_type_par, (
+            (2, 0x0004, 'is_style'),
+            # Following is not in OOo docs, but is mentioned
+            # in Gnumeric source and also in (deep breath)
+            # org.apache.poi.hssf.record.ExtendedFormatRecord.java
+            (3, 0x0008, 'lotus_123_prefix'), # Meaning is not known.
+            (4, 0xFFF0, 'parent_style_index'),
+        ))
+        upkbits(xf.alignment, pkd_align1, (
+            (0, 0x07, 'hor_align'),
+            (3, 0x08, 'text_wrapped'),
+            (4, 0x70, 'vert_align'),
+        ))
+        upkbits(xf.alignment, pkd_align2, (
+            (0, 0x0f, 'indent_level'),
+            (4, 0x10, 'shrink_to_fit'),
+            (6, 0xC0, 'text_direction'),
+        ))
+        reg = pkd_used >> 2
+        attr_stems = [
+            'format',
+            'font',
+            'alignment',
+            'border',
+            'background',
+            'protection',
+        ]
+        for attr_stem in attr_stems:
+            attr = "_" + attr_stem + "_flag"
+            setattr(xf, attr, reg & 1)
+            reg >>= 1
+        upkbitsL(xf.border, pkd_brdbkg1, (
+            (0,  0x0000000f,  'left_line_style'),
+            (4,  0x000000f0,  'right_line_style'),
+            (8,  0x00000f00,  'top_line_style'),
+            (12, 0x0000f000,  'bottom_line_style'),
+            (16, 0x007f0000,  'left_colour_index'),
+            (23, 0x3f800000,  'right_colour_index'),
+            (30, 0x40000000,  'diag_down'),
+            (31, 0x80000000, 'diag_up'),
+        ))
+        upkbits(xf.border, pkd_brdbkg2, (
+            (0,  0x0000007F, 'top_colour_index'),
+            (7,  0x00003F80, 'bottom_colour_index'),
+            (14, 0x001FC000, 'diag_colour_index'),
+            (21, 0x01E00000, 'diag_line_style'),
+        ))
+        upkbitsL(xf.background, pkd_brdbkg2, (
+            (26, 0xFC000000, 'fill_pattern'),
+        ))
+        upkbits(xf.background, pkd_brdbkg3, (
+            (0, 0x007F, 'pattern_colour_index'),
+            (7, 0x3F80, 'background_colour_index'),
+        ))
+    elif bv >= 50:
+        unpack_fmt = '<HHHBBIi'
+        (
+            xf.font_index, xf.format_key, pkd_type_par,
+            pkd_align1, pkd_orient_used,
+            pkd_brdbkg1, pkd_brdbkg2,
+        ) = unpack(unpack_fmt, data[0:16])
+        upkbits(xf.protection, pkd_type_par, (
+            (0, 0x01, 'cell_locked'),
+            (1, 0x02, 'formula_hidden'),
+        ))
+        upkbits(xf, pkd_type_par, (
+            (2, 0x0004, 'is_style'),
+            (3, 0x0008, 'lotus_123_prefix'), # Meaning is not known.
+            (4, 0xFFF0, 'parent_style_index'),
+        ))
+        upkbits(xf.alignment, pkd_align1, (
+            (0, 0x07, 'hor_align'),
+            (3, 0x08, 'text_wrapped'),
+            (4, 0x70, 'vert_align'),
+        ))
+        orientation = pkd_orient_used & 0x03
+        xf.alignment.rotation = [0, 255, 90, 180][orientation]
+        reg = pkd_orient_used >> 2
+        attr_stems = [
+            'format',
+            'font',
+            'alignment',
+            'border',
+            'background',
+            'protection',
+        ]
+        for attr_stem in attr_stems:
+            attr = "_" + attr_stem + "_flag"
+            setattr(xf, attr, reg & 1)
+            reg >>= 1
+        upkbitsL(xf.background, pkd_brdbkg1, (
+            ( 0, 0x0000007F, 'pattern_colour_index'),
+            ( 7, 0x00003F80, 'background_colour_index'),
+            (16, 0x003F0000, 'fill_pattern'),
+        ))
+        upkbitsL(xf.border, pkd_brdbkg1, (
+            (22, 0x01C00000,  'bottom_line_style'),
+            (25, 0xFE000000, 'bottom_colour_index'),
+        ))
+        upkbits(xf.border, pkd_brdbkg2, (
+            ( 0, 0x00000007, 'top_line_style'),
+            ( 3, 0x00000038, 'left_line_style'),
+            ( 6, 0x000001C0, 'right_line_style'),
+            ( 9, 0x0000FE00, 'top_colour_index'),
+            (16, 0x007F0000, 'left_colour_index'),
+            (23, 0x3F800000, 'right_colour_index'),
+        ))
+    elif bv >= 40:
+        unpack_fmt = '<BBHBBHI'
+        (
+            xf.font_index, xf.format_key, pkd_type_par,
+            pkd_align_orient, pkd_used,
+            pkd_bkg_34, pkd_brd_34,
+        ) = unpack(unpack_fmt, data[0:12])
+        upkbits(xf.protection, pkd_type_par, (
+            (0, 0x01, 'cell_locked'),
+            (1, 0x02, 'formula_hidden'),
+        ))
+        upkbits(xf, pkd_type_par, (
+            (2, 0x0004, 'is_style'),
+            (3, 0x0008, 'lotus_123_prefix'), # Meaning is not known.
+            (4, 0xFFF0, 'parent_style_index'),
+        ))
+        upkbits(xf.alignment, pkd_align_orient, (
+            (0, 0x07, 'hor_align'),
+            (3, 0x08, 'text_wrapped'),
+            (4, 0x30, 'vert_align'),
+        ))
+        orientation = (pkd_align_orient & 0xC0) >> 6
+        xf.alignment.rotation = [0, 255, 90, 180][orientation]
+        reg = pkd_used >> 2
+        attr_stems = [
+            'format',
+            'font',
+            'alignment',
+            'border',
+            'background',
+            'protection',
+        ]
+        for attr_stem in attr_stems:
+            attr = "_" + attr_stem + "_flag"
+            setattr(xf, attr, reg & 1)
+            reg >>= 1
+        upkbits(xf.background, pkd_bkg_34, (
+            ( 0, 0x003F, 'fill_pattern'),
+            ( 6, 0x07C0, 'pattern_colour_index'),
+            (11, 0xF800, 'background_colour_index'),
+        ))
+        upkbitsL(xf.border, pkd_brd_34, (
+            ( 0, 0x00000007,  'top_line_style'),
+            ( 3, 0x000000F8,  'top_colour_index'),
+            ( 8, 0x00000700,  'left_line_style'),
+            (11, 0x0000F800,  'left_colour_index'),
+            (16, 0x00070000,  'bottom_line_style'),
+            (19, 0x00F80000,  'bottom_colour_index'),
+            (24, 0x07000000,  'right_line_style'),
+            (27, 0xF8000000, 'right_colour_index'),
+        ))
+    elif bv == 30:
+        unpack_fmt = '<BBBBHHI'
+        (
+            xf.font_index, xf.format_key, pkd_type_prot,
+            pkd_used, pkd_align_par,
+            pkd_bkg_34, pkd_brd_34,
+        ) = unpack(unpack_fmt, data[0:12])
+        upkbits(xf.protection, pkd_type_prot, (
+            (0, 0x01, 'cell_locked'),
+            (1, 0x02, 'formula_hidden'),
+        ))
+        upkbits(xf, pkd_type_prot, (
+            (2, 0x0004, 'is_style'),
+            (3, 0x0008, 'lotus_123_prefix'), # Meaning is not known.
+        ))
+        upkbits(xf.alignment, pkd_align_par, (
+            (0, 0x07, 'hor_align'),
+            (3, 0x08, 'text_wrapped'),
+        ))
+        upkbits(xf, pkd_align_par, (
+            (4, 0xFFF0, 'parent_style_index'),
+        ))
+        reg = pkd_used >> 2
+        attr_stems = [
+            'format',
+            'font',
+            'alignment',
+            'border',
+            'background',
+            'protection',
+        ]
+        for attr_stem in attr_stems:
+            attr = "_" + attr_stem + "_flag"
+            setattr(xf, attr, reg & 1)
+            reg >>= 1
+        upkbits(xf.background, pkd_bkg_34, (
+            ( 0, 0x003F, 'fill_pattern'),
+            ( 6, 0x07C0, 'pattern_colour_index'),
+            (11, 0xF800, 'background_colour_index'),
+        ))
+        upkbitsL(xf.border, pkd_brd_34, (
+            ( 0, 0x00000007,  'top_line_style'),
+            ( 3, 0x000000F8,  'top_colour_index'),
+            ( 8, 0x00000700,  'left_line_style'),
+            (11, 0x0000F800,  'left_colour_index'),
+            (16, 0x00070000,  'bottom_line_style'),
+            (19, 0x00F80000,  'bottom_colour_index'),
+            (24, 0x07000000,  'right_line_style'),
+            (27, 0xF8000000, 'right_colour_index'),
+        ))
+        xf.alignment.vert_align = 2 # bottom
+        xf.alignment.rotation = 0
+    elif bv == 21:
+        ## Warning: incomplete treatment; formatting_info not fully supported.
+        ## Probably need to offset incoming BIFF2 XF[n] to BIFF8-like XF[n+16],
+        ## and create XF[0:16] like the standard ones in BIFF8 *AND* add 16 to
+        ## all XF references in cell records :-(
+        (xf.font_index, format_etc, halign_etc) = unpack('<BxBB', data)
+        xf.format_key = format_etc & 0x3F
+        upkbits(xf.protection, format_etc, (
+            (6, 0x40, 'cell_locked'),
+            (7, 0x80, 'formula_hidden'),
+        ))
+        upkbits(xf.alignment, halign_etc, (
+            (0, 0x07, 'hor_align'),
+        ))
+        for mask, side in ((0x08, 'left'), (0x10, 'right'), (0x20, 'top'), (0x40, 'bottom')):
+            if halign_etc & mask:
+                colour_index, line_style = 8, 1 # black, thin
+            else:
+                colour_index, line_style = 0, 0 # none, none
+            setattr(xf.border, side + '_colour_index', colour_index)
+            setattr(xf.border, side + '_line_style', line_style)
+        bg = xf.background
+        if halign_etc & 0x80:
+            bg.fill_pattern = 17
+        else:
+            bg.fill_pattern = 0
+        bg.background_colour_index = 9 # white
+        bg.pattern_colour_index = 8 # black
+        xf.parent_style_index = 0 # ???????????
+        xf.alignment.vert_align = 2 # bottom
+        xf.alignment.rotation = 0
+        attr_stems = [
+            'format',
+            'font',
+            'alignment',
+            'border',
+            'background',
+            'protection',
+        ]
+        for attr_stem in attr_stems:
+            attr = "_" + attr_stem + "_flag"
+            setattr(xf, attr, 1)
+    else:
+        raise XLRDError('programmer stuff-up: bv=%d' % bv)
+
+    xf.xf_index = len(self.xf_list)
+    self.xf_list.append(xf)
+    self.xfcount += 1
+    if blah:
+        xf.dump(
+            self.logfile,
+            header="--- handle_xf: xf[%d] ---" % xf.xf_index,
+            footer=" ",
+        )
+    try:
+        fmt = self.format_map[xf.format_key]
+        cellty = _cellty_from_fmtty[fmt.type]
+    except KeyError:
+        cellty = XL_CELL_NUMBER
+    self._xf_index_to_xl_type_map[xf.xf_index] = cellty
+
+    # Now for some assertions ...
+    if self.formatting_info:
+        if self.verbosity and xf.is_style and xf.parent_style_index != 0x0FFF:
+            msg = "WARNING *** XF[%d] is a style XF but parent_style_index is 0x%04x, not 0x0fff\n"
+            fprintf(self.logfile, msg, xf.xf_index, xf.parent_style_index)
+        check_colour_indexes_in_obj(self, xf, xf.xf_index)
+    if xf.format_key not in self.format_map:
+        msg = "WARNING *** XF[%d] unknown (raw) format key (%d, 0x%04x)\n"
+        if self.verbosity:
+            fprintf(self.logfile, msg,
+                xf.xf_index, xf.format_key, xf.format_key)
+        xf.format_key = 0
+
+def xf_epilogue(self):
+    # self is a Book instance.
+    self._xf_epilogue_done = 1
+    num_xfs = len(self.xf_list)
+    blah = DEBUG or self.verbosity >= 3
+    blah1 = DEBUG or self.verbosity >= 1
+    if blah:
+        fprintf(self.logfile, "xf_epilogue called ...\n")
+
+    def check_same(book_arg, xf_arg, parent_arg, attr):
+        # the _arg caper is to avoid a Warning msg from Python 2.1 :-(
+        if getattr(xf_arg, attr) != getattr(parent_arg, attr):
+            fprintf(book_arg.logfile,
+                "NOTE !!! XF[%d] parent[%d] %s different\n",
+                xf_arg.xf_index, parent_arg.xf_index, attr)
+
+    for xfx in xrange(num_xfs):
+        xf = self.xf_list[xfx]
+
+        try:
+            fmt = self.format_map[xf.format_key]
+            cellty = _cellty_from_fmtty[fmt.type]
+        except KeyError:
+            cellty = XL_CELL_TEXT
+        self._xf_index_to_xl_type_map[xf.xf_index] = cellty
+        # Now for some assertions etc
+        if not self.formatting_info:
+            continue
+        if xf.is_style:
+            continue
+        if not(0 <= xf.parent_style_index < num_xfs):
+            if blah1:
+                fprintf(self.logfile,
+                    "WARNING *** XF[%d]: is_style=%d but parent_style_index=%d\n",
+                    xf.xf_index, xf.is_style, xf.parent_style_index)
+            # make it conform
+            xf.parent_style_index = 0
+        if self.biff_version >= 30:
+            if blah1:
+                if xf.parent_style_index == xf.xf_index:
+                    fprintf(self.logfile,
+                        "NOTE !!! XF[%d]: parent_style_index is also %d\n",
+                        xf.xf_index, xf.parent_style_index)
+                elif not self.xf_list[xf.parent_style_index].is_style:
+                    fprintf(self.logfile,
+                        "NOTE !!! XF[%d]: parent_style_index is %d; style flag not set\n",
+                        xf.xf_index, xf.parent_style_index)
+            if blah1 and xf.parent_style_index > xf.xf_index:
+                fprintf(self.logfile,
+                    "NOTE !!! XF[%d]: parent_style_index is %d; out of order?\n",
+                    xf.xf_index, xf.parent_style_index)
+            parent = self.xf_list[xf.parent_style_index]
+            if not xf._alignment_flag and not parent._alignment_flag:
+                if blah1: check_same(self, xf, parent, 'alignment')
+            if not xf._background_flag and not parent._background_flag:
+                if blah1: check_same(self, xf, parent, 'background')
+            if not xf._border_flag and not parent._border_flag:
+                if blah1: check_same(self, xf, parent, 'border')
+            if not xf._protection_flag and not parent._protection_flag:
+                if blah1: check_same(self, xf, parent, 'protection')
+            if not xf._format_flag and not parent._format_flag:
+                if blah1 and xf.format_key != parent.format_key:
+                    fprintf(self.logfile,
+                        "NOTE !!! XF[%d] fmtk=%d, parent[%d] fmtk=%r\n%r / %r\n",
+                        xf.xf_index, xf.format_key, parent.xf_index, parent.format_key,
+                        self.format_map[xf.format_key].format_str,
+                        self.format_map[parent.format_key].format_str)
+            if not xf._font_flag and not parent._font_flag:
+                if blah1 and xf.font_index != parent.font_index:
+                    fprintf(self.logfile,
+                        "NOTE !!! XF[%d] fontx=%d, parent[%d] fontx=%r\n",
+                        xf.xf_index, xf.font_index, parent.xf_index, parent.font_index)
+
+def initialise_book(book):
+    initialise_colour_map(book)
+    book._xf_epilogue_done = 0
+    methods = (
+        handle_font,
+        handle_efont,
+        handle_format,
+        is_date_format_string,
+        handle_palette,
+        palette_epilogue,
+        handle_style,
+        handle_xf,
+        xf_epilogue,
+    )
+    for method in methods:
+        setattr(book.__class__, method.__name__, method)
+
+class XFBorder(BaseObject, EqNeAttrs):
+    """
+    A collection of the border-related attributes of an ``XF`` record.
+    Items correspond to those in the Excel UI's Format -> Cells -> Border tab.
+
+    An explanations of "colour index" is given in :ref:`palette`.
+
+    There are five line style attributes; possible values and the
+    associated meanings are::
+
+      0 = No line,
+      1 = Thin,
+      2 = Medium,
+      3 = Dashed,
+      4 = Dotted,
+      5 = Thick,
+      6 = Double,
+      7 = Hair,
+      8 = Medium dashed,
+      9 = Thin dash-dotted,
+      10 = Medium dash-dotted,
+      11 = Thin dash-dot-dotted,
+      12 = Medium dash-dot-dotted,
+      13 = Slanted medium dash-dotted.
+
+    The line styles 8 to 13 appear in BIFF8 files (Excel 97 and later) only.
+    For pictures of the line styles, refer to OOo docs s3.10 (p22)
+    "Line Styles for Cell Borders (BIFF3-BIFF8)".</p>
+
+    .. versionadded:: 0.6.1
+    """
+
+    #: The colour index for the cell's top line
+    top_colour_index = 0
+    #: The colour index for the cell's bottom line
+    bottom_colour_index = 0
+
+    #: The colour index for the cell's left line
+    left_colour_index = 0
+
+    #: The colour index for the cell's right line
+    right_colour_index = 0
+
+    #: The colour index for the cell's diagonal lines, if any
+    diag_colour_index = 0
+
+    #: The line style for the cell's top line
+    top_line_style = 0
+
+    #: The line style for the cell's bottom line
+    bottom_line_style = 0
+
+    #: The line style for the cell's left line
+    left_line_style = 0
+
+    #: The line style for the cell's right line
+    right_line_style = 0
+
+    #: The line style for the cell's diagonal lines, if any
+    diag_line_style = 0
+
+    #: 1 = draw a diagonal from top left to bottom right
+    diag_down = 0
+
+    #: 1 = draw a diagonal from bottom left to top right
+    diag_up = 0
+
+class XFBackground(BaseObject, EqNeAttrs):
+    """
+    A collection of the background-related attributes of an ``XF`` record.
+    Items correspond to those in the Excel UI's Format -> Cells -> Patterns tab.
+
+    An explanations of "colour index" is given in :ref:`palette`.
+
+    .. versionadded:: 0.6.1
+    """
+
+    #: See section 3.11 of the OOo docs.
+    fill_pattern = 0
+
+    #: See section 3.11 of the OOo docs.
+    background_colour_index = 0
+
+    #: See section 3.11 of the OOo docs.
+    pattern_colour_index = 0
+
+
+class XFAlignment(BaseObject, EqNeAttrs):
+    """
+    A collection of the alignment and similar attributes of an ``XF`` record.
+    Items correspond to those in the Excel UI's Format -> Cells -> Alignment tab.
+
+    .. versionadded:: 0.6.1
+    """
+
+    #: Values: section 6.115 (p 214) of OOo docs
+    hor_align = 0
+
+    #: Values: section 6.115 (p 215) of OOo docs
+    vert_align = 0
+
+    #: Values: section 6.115 (p 215) of OOo docs.
+    #:
+    #: .. note::
+    #:   file versions BIFF7 and earlier use the documented
+    #:   :attr:`orientation` attribute; this will be mapped (without loss)
+    #:   into :attr:`rotation`.
+    rotation = 0
+
+    #: 1 = text is wrapped at right margin
+    text_wrapped = 0
+
+    #: A number in ``range(15)``.
+    indent_level = 0
+
+    #: 1 = shrink font size to fit text into cell.
+    shrink_to_fit = 0
+
+    #: 0 = according to context; 1 = left-to-right; 2 = right-to-left
+    text_direction = 0
+
+class XFProtection(BaseObject, EqNeAttrs):
+    """
+    A collection of the protection-related attributes of an ``XF`` record.
+    Items correspond to those in the Excel UI's Format -> Cells -> Protection tab.
+    Note the OOo docs include the "cell or style" bit in this bundle of
+    attributes. This is incorrect; the bit is used in determining which bundles
+    to use.
+
+    .. versionadded:: 0.6.1
+    """
+
+    #: 1 = Cell is prevented from being changed, moved, resized, or deleted
+    #: (only if the sheet is protected).
+    cell_locked = 0
+
+    #: 1 = Hide formula so that it doesn't appear in the formula bar when
+    #: the cell is selected (only if the sheet is protected).
+    formula_hidden = 0
+
+class XF(BaseObject):
+    """
+    eXtended Formatting information for cells, rows, columns and styles.
+
+    Each of the 6 flags below describes the validity of
+    a specific group of attributes.
+
+    In cell XFs:
+
+    - ``flag==0`` means the attributes of the parent style ``XF`` are
+      used, (but only if the attributes are valid there);
+
+    - ``flag==1`` means the attributes of this ``XF`` are used.
+
+    In style XFs:
+
+    - ``flag==0`` means the attribute setting is valid;
+    - ``flag==1`` means the attribute should be ignored.
+
+    .. note::
+      the API provides both "raw" XFs and "computed" XFs. In the latter case,
+      cell XFs have had the above inheritance mechanism applied.
+
+    .. versionadded:: 0.6.1
+    """
+
+    #: 0 = cell XF, 1 = style XF
+    is_style = 0
+
+    #: cell XF: Index into Book.xf_list of this XF's style XF
+    #:
+    #: style XF: 0xFFF
+    parent_style_index = 0
+
+    #
+    _format_flag = 0
+
+    #
+    _font_flag = 0
+
+    #
+    _alignment_flag = 0
+
+    #
+    _border_flag = 0
+
+    #
+    _background_flag = 0
+
+    _protection_flag = 0
+
+    #: Index into :attr:`~xlrd.book.Book.xf_list`
+    xf_index = 0
+
+    #: Index into :attr:`~xlrd.book.Book.font_list`
+    font_index = 0
+
+    #: Key into :attr:`~xlrd.book.Book.format_map`
+    #:
+    #: .. warning::
+    #:   OOo docs on the XF record call this "Index to FORMAT record".
+    #:   It is not an index in the Python sense. It is a key to a map.
+    #:   It is true *only* for Excel 4.0 and earlier files
+    #:   that the key into format_map from an XF instance
+    #:   is the same as the index into format_list, and *only*
+    #:   if the index is less than 164.
+    format_key = 0
+
+    #: An instance of an :class:`XFProtection` object.
+    protection = None
+
+    #: An instance of an :class:`XFBackground` object.
+    background = None
+
+    #: An instance of an :class:`XFAlignment` object.
+    alignment = None
+
+    #: An instance of an :class:`XFBorder` object.
+    border = None

+ 2190 - 0
desktop/core/ext-py/xlrd-2.0.1/xlrd/formula.py

@@ -0,0 +1,2190 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2005-2012 Stephen John Machin, Lingfo Pty Ltd
+# This module is part of the xlrd package, which is released under a
+# BSD-style licence.
+# No part of the content of this file was derived from the works of
+# David Giffin.
+"""
+Module for parsing/evaluating Microsoft Excel formulas.
+"""
+
+from __future__ import print_function
+
+import copy
+import operator as opr
+from struct import unpack
+
+from .biffh import (
+    BaseObject, XLRDError, error_text_from_code, hex_char_dump,
+    unpack_string_update_pos, unpack_unicode_update_pos,
+)
+from .timemachine import *
+
+__all__ = [
+    'oBOOL', 'oERR', 'oNUM', 'oREF', 'oREL', 'oSTRG', 'oUNK',
+    'decompile_formula',
+    'dump_formula',
+    'evaluate_name_formula',
+    'okind_dict',
+    'rangename3d', 'rangename3drel', 'cellname', 'cellnameabs', 'colname',
+    'FMLA_TYPE_CELL',
+    'FMLA_TYPE_SHARED',
+    'FMLA_TYPE_ARRAY',
+    'FMLA_TYPE_COND_FMT',
+    'FMLA_TYPE_DATA_VAL',
+    'FMLA_TYPE_NAME',
+    'Operand', 'Ref3D',
+]
+
+FMLA_TYPE_CELL = 1
+FMLA_TYPE_SHARED = 2
+FMLA_TYPE_ARRAY = 4
+FMLA_TYPE_COND_FMT = 8
+FMLA_TYPE_DATA_VAL = 16
+FMLA_TYPE_NAME = 32
+ALL_FMLA_TYPES = 63
+
+
+FMLA_TYPEDESCR_MAP = {
+    1 : 'CELL',
+    2 : 'SHARED',
+    4 : 'ARRAY',
+    8 : 'COND-FMT',
+    16: 'DATA-VAL',
+    32: 'NAME',
+}
+
+_TOKEN_NOT_ALLOWED = {
+    0x01:   ALL_FMLA_TYPES - FMLA_TYPE_CELL, # tExp
+    0x02:   ALL_FMLA_TYPES - FMLA_TYPE_CELL, # tTbl
+    0x0F:   FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tIsect
+    0x10:   FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tUnion/List
+    0x11:   FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tRange
+    0x20:   FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tArray
+    0x23:   FMLA_TYPE_SHARED, # tName
+    0x39:   FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tNameX
+    0x3A:   FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tRef3d
+    0x3B:   FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tArea3d
+    0x2C:   FMLA_TYPE_CELL + FMLA_TYPE_ARRAY, # tRefN
+    0x2D:   FMLA_TYPE_CELL + FMLA_TYPE_ARRAY, # tAreaN
+    # plus weird stuff like tMem*
+}.get
+
+oBOOL = 3
+oERR =  4
+oMSNG = 5 # tMissArg
+oNUM =  2
+oREF = -1
+oREL = -2
+oSTRG = 1
+oUNK =  0
+
+okind_dict = {
+    -2: "oREL",
+    -1: "oREF",
+    0 : "oUNK",
+    1 : "oSTRG",
+    2 : "oNUM",
+    3 : "oBOOL",
+    4 : "oERR",
+    5 : "oMSNG",
+}
+
+listsep = ',' #### probably should depend on locale
+
+
+# sztabN[opcode] -> the number of bytes to consume.
+# -1 means variable
+# -2 means this opcode not implemented in this version.
+# Which N to use? Depends on biff_version; see szdict.
+sztab0 = [-2, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -2, -1, 8, 4, 2, 2, 3, 9, 8, 2, 3, 8, 4, 7, 5, 5, 5, 2, 4, 7, 4, 7, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2, 3, -2, -2, -2, -2, -2, -2, -2]
+sztab1 = [-2, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -2, -1, 11, 5, 2, 2, 3, 9, 9, 2, 3, 11, 4, 7, 7, 7, 7, 3, 4, 7, 4, 7, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2, 3, -2, -2, -2, -2, -2, -2, -2]
+sztab2 = [-2, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -2, -1, 11, 5, 2, 2, 3, 9, 9, 3, 4, 11, 4, 7, 7, 7, 7, 3, 4, 7, 4, 7, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2]
+sztab3 = [-2, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -2, -1, -2, -2, 2, 2, 3, 9, 9, 3, 4, 15, 4, 7, 7, 7, 7, 3, 4, 7, 4, 7, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2, -2, 25, 18, 21, 18, 21, -2, -2]
+sztab4 = [-2, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -2, -2, 2, 2, 3, 9, 9, 3, 4, 5, 5, 9, 7, 7, 7, 3, 5, 9, 5, 9, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2, -2, 7, 7, 11, 7, 11, -2, -2]
+
+szdict = {
+    20 : sztab0,
+    21 : sztab0,
+    30 : sztab1,
+    40 : sztab2,
+    45 : sztab2,
+    50 : sztab3,
+    70 : sztab3,
+    80 : sztab4,
+}
+
+# For debugging purposes ... the name for each opcode
+# (without the prefix "t" used on OOo docs)
+onames = ['Unk00', 'Exp', 'Tbl', 'Add', 'Sub', 'Mul', 'Div', 'Power', 'Concat', 'LT', 'LE', 'EQ', 'GE', 'GT', 'NE', 'Isect', 'List', 'Range', 'Uplus', 'Uminus', 'Percent', 'Paren', 'MissArg', 'Str', 'Extended', 'Attr', 'Sheet', 'EndSheet', 'Err', 'Bool', 'Int', 'Num', 'Array', 'Func', 'FuncVar', 'Name', 'Ref', 'Area', 'MemArea', 'MemErr', 'MemNoMem', 'MemFunc', 'RefErr', 'AreaErr', 'RefN', 'AreaN', 'MemAreaN', 'MemNoMemN', '', '', '', '', '', '', '', '', 'FuncCE', 'NameX', 'Ref3d', 'Area3d', 'RefErr3d', 'AreaErr3d', '', '']
+
+func_defs = {
+    # index: (name, min#args, max#args, flags, #known_args, return_type, kargs)
+    0  : ('COUNT',            0, 30, 0x04,  1, 'V', 'R'),
+    1  : ('IF',               2,  3, 0x04,  3, 'V', 'VRR'),
+    2  : ('ISNA',             1,  1, 0x02,  1, 'V', 'V'),
+    3  : ('ISERROR',          1,  1, 0x02,  1, 'V', 'V'),
+    4  : ('SUM',              0, 30, 0x04,  1, 'V', 'R'),
+    5  : ('AVERAGE',          1, 30, 0x04,  1, 'V', 'R'),
+    6  : ('MIN',              1, 30, 0x04,  1, 'V', 'R'),
+    7  : ('MAX',              1, 30, 0x04,  1, 'V', 'R'),
+    8  : ('ROW',              0,  1, 0x04,  1, 'V', 'R'),
+    9  : ('COLUMN',           0,  1, 0x04,  1, 'V', 'R'),
+    10 : ('NA',               0,  0, 0x02,  0, 'V', ''),
+    11 : ('NPV',              2, 30, 0x04,  2, 'V', 'VR'),
+    12 : ('STDEV',            1, 30, 0x04,  1, 'V', 'R'),
+    13 : ('DOLLAR',           1,  2, 0x04,  1, 'V', 'V'),
+    14 : ('FIXED',            2,  3, 0x04,  3, 'V', 'VVV'),
+    15 : ('SIN',              1,  1, 0x02,  1, 'V', 'V'),
+    16 : ('COS',              1,  1, 0x02,  1, 'V', 'V'),
+    17 : ('TAN',              1,  1, 0x02,  1, 'V', 'V'),
+    18 : ('ATAN',             1,  1, 0x02,  1, 'V', 'V'),
+    19 : ('PI',               0,  0, 0x02,  0, 'V', ''),
+    20 : ('SQRT',             1,  1, 0x02,  1, 'V', 'V'),
+    21 : ('EXP',              1,  1, 0x02,  1, 'V', 'V'),
+    22 : ('LN',               1,  1, 0x02,  1, 'V', 'V'),
+    23 : ('LOG10',            1,  1, 0x02,  1, 'V', 'V'),
+    24 : ('ABS',              1,  1, 0x02,  1, 'V', 'V'),
+    25 : ('INT',              1,  1, 0x02,  1, 'V', 'V'),
+    26 : ('SIGN',             1,  1, 0x02,  1, 'V', 'V'),
+    27 : ('ROUND',            2,  2, 0x02,  2, 'V', 'VV'),
+    28 : ('LOOKUP',           2,  3, 0x04,  2, 'V', 'VR'),
+    29 : ('INDEX',            2,  4, 0x0c,  4, 'R', 'RVVV'),
+    30 : ('REPT',             2,  2, 0x02,  2, 'V', 'VV'),
+    31 : ('MID',              3,  3, 0x02,  3, 'V', 'VVV'),
+    32 : ('LEN',              1,  1, 0x02,  1, 'V', 'V'),
+    33 : ('VALUE',            1,  1, 0x02,  1, 'V', 'V'),
+    34 : ('TRUE',             0,  0, 0x02,  0, 'V', ''),
+    35 : ('FALSE',            0,  0, 0x02,  0, 'V', ''),
+    36 : ('AND',              1, 30, 0x04,  1, 'V', 'R'),
+    37 : ('OR',               1, 30, 0x04,  1, 'V', 'R'),
+    38 : ('NOT',              1,  1, 0x02,  1, 'V', 'V'),
+    39 : ('MOD',              2,  2, 0x02,  2, 'V', 'VV'),
+    40 : ('DCOUNT',           3,  3, 0x02,  3, 'V', 'RRR'),
+    41 : ('DSUM',             3,  3, 0x02,  3, 'V', 'RRR'),
+    42 : ('DAVERAGE',         3,  3, 0x02,  3, 'V', 'RRR'),
+    43 : ('DMIN',             3,  3, 0x02,  3, 'V', 'RRR'),
+    44 : ('DMAX',             3,  3, 0x02,  3, 'V', 'RRR'),
+    45 : ('DSTDEV',           3,  3, 0x02,  3, 'V', 'RRR'),
+    46 : ('VAR',              1, 30, 0x04,  1, 'V', 'R'),
+    47 : ('DVAR',             3,  3, 0x02,  3, 'V', 'RRR'),
+    48 : ('TEXT',             2,  2, 0x02,  2, 'V', 'VV'),
+    49 : ('LINEST',           1,  4, 0x04,  4, 'A', 'RRVV'),
+    50 : ('TREND',            1,  4, 0x04,  4, 'A', 'RRRV'),
+    51 : ('LOGEST',           1,  4, 0x04,  4, 'A', 'RRVV'),
+    52 : ('GROWTH',           1,  4, 0x04,  4, 'A', 'RRRV'),
+    56 : ('PV',               3,  5, 0x04,  5, 'V', 'VVVVV'),
+    57 : ('FV',               3,  5, 0x04,  5, 'V', 'VVVVV'),
+    58 : ('NPER',             3,  5, 0x04,  5, 'V', 'VVVVV'),
+    59 : ('PMT',              3,  5, 0x04,  5, 'V', 'VVVVV'),
+    60 : ('RATE',             3,  6, 0x04,  6, 'V', 'VVVVVV'),
+    61 : ('MIRR',             3,  3, 0x02,  3, 'V', 'RVV'),
+    62 : ('IRR',              1,  2, 0x04,  2, 'V', 'RV'),
+    63 : ('RAND',             0,  0, 0x0a,  0, 'V', ''),
+    64 : ('MATCH',            2,  3, 0x04,  3, 'V', 'VRR'),
+    65 : ('DATE',             3,  3, 0x02,  3, 'V', 'VVV'),
+    66 : ('TIME',             3,  3, 0x02,  3, 'V', 'VVV'),
+    67 : ('DAY',              1,  1, 0x02,  1, 'V', 'V'),
+    68 : ('MONTH',            1,  1, 0x02,  1, 'V', 'V'),
+    69 : ('YEAR',             1,  1, 0x02,  1, 'V', 'V'),
+    70 : ('WEEKDAY',          1,  2, 0x04,  2, 'V', 'VV'),
+    71 : ('HOUR',             1,  1, 0x02,  1, 'V', 'V'),
+    72 : ('MINUTE',           1,  1, 0x02,  1, 'V', 'V'),
+    73 : ('SECOND',           1,  1, 0x02,  1, 'V', 'V'),
+    74 : ('NOW',              0,  0, 0x0a,  0, 'V', ''),
+    75 : ('AREAS',            1,  1, 0x02,  1, 'V', 'R'),
+    76 : ('ROWS',             1,  1, 0x02,  1, 'V', 'R'),
+    77 : ('COLUMNS',          1,  1, 0x02,  1, 'V', 'R'),
+    78 : ('OFFSET',           3,  5, 0x04,  5, 'R', 'RVVVV'),
+    82 : ('SEARCH',           2,  3, 0x04,  3, 'V', 'VVV'),
+    83 : ('TRANSPOSE',        1,  1, 0x02,  1, 'A', 'A'),
+    86 : ('TYPE',             1,  1, 0x02,  1, 'V', 'V'),
+    92 : ('SERIESSUM',        4,  4, 0x02,  4, 'V', 'VVVA'),
+    97 : ('ATAN2',            2,  2, 0x02,  2, 'V', 'VV'),
+    98 : ('ASIN',             1,  1, 0x02,  1, 'V', 'V'),
+    99 : ('ACOS',             1,  1, 0x02,  1, 'V', 'V'),
+    100: ('CHOOSE',           2, 30, 0x04,  2, 'V', 'VR'),
+    101: ('HLOOKUP',          3,  4, 0x04,  4, 'V', 'VRRV'),
+    102: ('VLOOKUP',          3,  4, 0x04,  4, 'V', 'VRRV'),
+    105: ('ISREF',            1,  1, 0x02,  1, 'V', 'R'),
+    109: ('LOG',              1,  2, 0x04,  2, 'V', 'VV'),
+    111: ('CHAR',             1,  1, 0x02,  1, 'V', 'V'),
+    112: ('LOWER',            1,  1, 0x02,  1, 'V', 'V'),
+    113: ('UPPER',            1,  1, 0x02,  1, 'V', 'V'),
+    114: ('PROPER',           1,  1, 0x02,  1, 'V', 'V'),
+    115: ('LEFT',             1,  2, 0x04,  2, 'V', 'VV'),
+    116: ('RIGHT',            1,  2, 0x04,  2, 'V', 'VV'),
+    117: ('EXACT',            2,  2, 0x02,  2, 'V', 'VV'),
+    118: ('TRIM',             1,  1, 0x02,  1, 'V', 'V'),
+    119: ('REPLACE',          4,  4, 0x02,  4, 'V', 'VVVV'),
+    120: ('SUBSTITUTE',       3,  4, 0x04,  4, 'V', 'VVVV'),
+    121: ('CODE',             1,  1, 0x02,  1, 'V', 'V'),
+    124: ('FIND',             2,  3, 0x04,  3, 'V', 'VVV'),
+    125: ('CELL',             1,  2, 0x0c,  2, 'V', 'VR'),
+    126: ('ISERR',            1,  1, 0x02,  1, 'V', 'V'),
+    127: ('ISTEXT',           1,  1, 0x02,  1, 'V', 'V'),
+    128: ('ISNUMBER',         1,  1, 0x02,  1, 'V', 'V'),
+    129: ('ISBLANK',          1,  1, 0x02,  1, 'V', 'V'),
+    130: ('T',                1,  1, 0x02,  1, 'V', 'R'),
+    131: ('N',                1,  1, 0x02,  1, 'V', 'R'),
+    140: ('DATEVALUE',        1,  1, 0x02,  1, 'V', 'V'),
+    141: ('TIMEVALUE',        1,  1, 0x02,  1, 'V', 'V'),
+    142: ('SLN',              3,  3, 0x02,  3, 'V', 'VVV'),
+    143: ('SYD',              4,  4, 0x02,  4, 'V', 'VVVV'),
+    144: ('DDB',              4,  5, 0x04,  5, 'V', 'VVVVV'),
+    148: ('INDIRECT',         1,  2, 0x0c,  2, 'R', 'VV'),
+    162: ('CLEAN',            1,  1, 0x02,  1, 'V', 'V'),
+    163: ('MDETERM',          1,  1, 0x02,  1, 'V', 'A'),
+    164: ('MINVERSE',         1,  1, 0x02,  1, 'A', 'A'),
+    165: ('MMULT',            2,  2, 0x02,  2, 'A', 'AA'),
+    167: ('IPMT',             4,  6, 0x04,  6, 'V', 'VVVVVV'),
+    168: ('PPMT',             4,  6, 0x04,  6, 'V', 'VVVVVV'),
+    169: ('COUNTA',           0, 30, 0x04,  1, 'V', 'R'),
+    183: ('PRODUCT',          0, 30, 0x04,  1, 'V', 'R'),
+    184: ('FACT',             1,  1, 0x02,  1, 'V', 'V'),
+    189: ('DPRODUCT',         3,  3, 0x02,  3, 'V', 'RRR'),
+    190: ('ISNONTEXT',        1,  1, 0x02,  1, 'V', 'V'),
+    193: ('STDEVP',           1, 30, 0x04,  1, 'V', 'R'),
+    194: ('VARP',             1, 30, 0x04,  1, 'V', 'R'),
+    195: ('DSTDEVP',          3,  3, 0x02,  3, 'V', 'RRR'),
+    196: ('DVARP',            3,  3, 0x02,  3, 'V', 'RRR'),
+    197: ('TRUNC',            1,  2, 0x04,  2, 'V', 'VV'),
+    198: ('ISLOGICAL',        1,  1, 0x02,  1, 'V', 'V'),
+    199: ('DCOUNTA',          3,  3, 0x02,  3, 'V', 'RRR'),
+    204: ('USDOLLAR',         1,  2, 0x04,  2, 'V', 'VV'),
+    205: ('FINDB',            2,  3, 0x04,  3, 'V', 'VVV'),
+    206: ('SEARCHB',          2,  3, 0x04,  3, 'V', 'VVV'),
+    207: ('REPLACEB',         4,  4, 0x02,  4, 'V', 'VVVV'),
+    208: ('LEFTB',            1,  2, 0x04,  2, 'V', 'VV'),
+    209: ('RIGHTB',           1,  2, 0x04,  2, 'V', 'VV'),
+    210: ('MIDB',             3,  3, 0x02,  3, 'V', 'VVV'),
+    211: ('LENB',             1,  1, 0x02,  1, 'V', 'V'),
+    212: ('ROUNDUP',          2,  2, 0x02,  2, 'V', 'VV'),
+    213: ('ROUNDDOWN',        2,  2, 0x02,  2, 'V', 'VV'),
+    214: ('ASC',              1,  1, 0x02,  1, 'V', 'V'),
+    215: ('DBCS',             1,  1, 0x02,  1, 'V', 'V'),
+    216: ('RANK',             2,  3, 0x04,  3, 'V', 'VRV'),
+    219: ('ADDRESS',          2,  5, 0x04,  5, 'V', 'VVVVV'),
+    220: ('DAYS360',          2,  3, 0x04,  3, 'V', 'VVV'),
+    221: ('TODAY',            0,  0, 0x0a,  0, 'V', ''),
+    222: ('VDB',              5,  7, 0x04,  7, 'V', 'VVVVVVV'),
+    227: ('MEDIAN',           1, 30, 0x04,  1, 'V', 'R'),
+    228: ('SUMPRODUCT',       1, 30, 0x04,  1, 'V', 'A'),
+    229: ('SINH',             1,  1, 0x02,  1, 'V', 'V'),
+    230: ('COSH',             1,  1, 0x02,  1, 'V', 'V'),
+    231: ('TANH',             1,  1, 0x02,  1, 'V', 'V'),
+    232: ('ASINH',            1,  1, 0x02,  1, 'V', 'V'),
+    233: ('ACOSH',            1,  1, 0x02,  1, 'V', 'V'),
+    234: ('ATANH',            1,  1, 0x02,  1, 'V', 'V'),
+    235: ('DGET',             3,  3, 0x02,  3, 'V', 'RRR'),
+    244: ('INFO',             1,  1, 0x02,  1, 'V', 'V'),
+    247: ('DB',               4,  5, 0x04,  5, 'V', 'VVVVV'),
+    252: ('FREQUENCY',        2,  2, 0x02,  2, 'A', 'RR'),
+    261: ('ERROR.TYPE',       1,  1, 0x02,  1, 'V', 'V'),
+    269: ('AVEDEV',           1, 30, 0x04,  1, 'V', 'R'),
+    270: ('BETADIST',         3,  5, 0x04,  1, 'V', 'V'),
+    271: ('GAMMALN',          1,  1, 0x02,  1, 'V', 'V'),
+    272: ('BETAINV',          3,  5, 0x04,  1, 'V', 'V'),
+    273: ('BINOMDIST',        4,  4, 0x02,  4, 'V', 'VVVV'),
+    274: ('CHIDIST',          2,  2, 0x02,  2, 'V', 'VV'),
+    275: ('CHIINV',           2,  2, 0x02,  2, 'V', 'VV'),
+    276: ('COMBIN',           2,  2, 0x02,  2, 'V', 'VV'),
+    277: ('CONFIDENCE',       3,  3, 0x02,  3, 'V', 'VVV'),
+    278: ('CRITBINOM',        3,  3, 0x02,  3, 'V', 'VVV'),
+    279: ('EVEN',             1,  1, 0x02,  1, 'V', 'V'),
+    280: ('EXPONDIST',        3,  3, 0x02,  3, 'V', 'VVV'),
+    281: ('FDIST',            3,  3, 0x02,  3, 'V', 'VVV'),
+    282: ('FINV',             3,  3, 0x02,  3, 'V', 'VVV'),
+    283: ('FISHER',           1,  1, 0x02,  1, 'V', 'V'),
+    284: ('FISHERINV',        1,  1, 0x02,  1, 'V', 'V'),
+    285: ('FLOOR',            2,  2, 0x02,  2, 'V', 'VV'),
+    286: ('GAMMADIST',        4,  4, 0x02,  4, 'V', 'VVVV'),
+    287: ('GAMMAINV',         3,  3, 0x02,  3, 'V', 'VVV'),
+    288: ('CEILING',          2,  2, 0x02,  2, 'V', 'VV'),
+    289: ('HYPGEOMDIST',      4,  4, 0x02,  4, 'V', 'VVVV'),
+    290: ('LOGNORMDIST',      3,  3, 0x02,  3, 'V', 'VVV'),
+    291: ('LOGINV',           3,  3, 0x02,  3, 'V', 'VVV'),
+    292: ('NEGBINOMDIST',     3,  3, 0x02,  3, 'V', 'VVV'),
+    293: ('NORMDIST',         4,  4, 0x02,  4, 'V', 'VVVV'),
+    294: ('NORMSDIST',        1,  1, 0x02,  1, 'V', 'V'),
+    295: ('NORMINV',          3,  3, 0x02,  3, 'V', 'VVV'),
+    296: ('NORMSINV',         1,  1, 0x02,  1, 'V', 'V'),
+    297: ('STANDARDIZE',      3,  3, 0x02,  3, 'V', 'VVV'),
+    298: ('ODD',              1,  1, 0x02,  1, 'V', 'V'),
+    299: ('PERMUT',           2,  2, 0x02,  2, 'V', 'VV'),
+    300: ('POISSON',          3,  3, 0x02,  3, 'V', 'VVV'),
+    301: ('TDIST',            3,  3, 0x02,  3, 'V', 'VVV'),
+    302: ('WEIBULL',          4,  4, 0x02,  4, 'V', 'VVVV'),
+    303: ('SUMXMY2',          2,  2, 0x02,  2, 'V', 'AA'),
+    304: ('SUMX2MY2',         2,  2, 0x02,  2, 'V', 'AA'),
+    305: ('SUMX2PY2',         2,  2, 0x02,  2, 'V', 'AA'),
+    306: ('CHITEST',          2,  2, 0x02,  2, 'V', 'AA'),
+    307: ('CORREL',           2,  2, 0x02,  2, 'V', 'AA'),
+    308: ('COVAR',            2,  2, 0x02,  2, 'V', 'AA'),
+    309: ('FORECAST',         3,  3, 0x02,  3, 'V', 'VAA'),
+    310: ('FTEST',            2,  2, 0x02,  2, 'V', 'AA'),
+    311: ('INTERCEPT',        2,  2, 0x02,  2, 'V', 'AA'),
+    312: ('PEARSON',          2,  2, 0x02,  2, 'V', 'AA'),
+    313: ('RSQ',              2,  2, 0x02,  2, 'V', 'AA'),
+    314: ('STEYX',            2,  2, 0x02,  2, 'V', 'AA'),
+    315: ('SLOPE',            2,  2, 0x02,  2, 'V', 'AA'),
+    316: ('TTEST',            4,  4, 0x02,  4, 'V', 'AAVV'),
+    317: ('PROB',             3,  4, 0x04,  3, 'V', 'AAV'),
+    318: ('DEVSQ',            1, 30, 0x04,  1, 'V', 'R'),
+    319: ('GEOMEAN',          1, 30, 0x04,  1, 'V', 'R'),
+    320: ('HARMEAN',          1, 30, 0x04,  1, 'V', 'R'),
+    321: ('SUMSQ',            0, 30, 0x04,  1, 'V', 'R'),
+    322: ('KURT',             1, 30, 0x04,  1, 'V', 'R'),
+    323: ('SKEW',             1, 30, 0x04,  1, 'V', 'R'),
+    324: ('ZTEST',            2,  3, 0x04,  2, 'V', 'RV'),
+    325: ('LARGE',            2,  2, 0x02,  2, 'V', 'RV'),
+    326: ('SMALL',            2,  2, 0x02,  2, 'V', 'RV'),
+    327: ('QUARTILE',         2,  2, 0x02,  2, 'V', 'RV'),
+    328: ('PERCENTILE',       2,  2, 0x02,  2, 'V', 'RV'),
+    329: ('PERCENTRANK',      2,  3, 0x04,  2, 'V', 'RV'),
+    330: ('MODE',             1, 30, 0x04,  1, 'V', 'A'),
+    331: ('TRIMMEAN',         2,  2, 0x02,  2, 'V', 'RV'),
+    332: ('TINV',             2,  2, 0x02,  2, 'V', 'VV'),
+    336: ('CONCATENATE',      0, 30, 0x04,  1, 'V', 'V'),
+    337: ('POWER',            2,  2, 0x02,  2, 'V', 'VV'),
+    342: ('RADIANS',          1,  1, 0x02,  1, 'V', 'V'),
+    343: ('DEGREES',          1,  1, 0x02,  1, 'V', 'V'),
+    344: ('SUBTOTAL',         2, 30, 0x04,  2, 'V', 'VR'),
+    345: ('SUMIF',            2,  3, 0x04,  3, 'V', 'RVR'),
+    346: ('COUNTIF',          2,  2, 0x02,  2, 'V', 'RV'),
+    347: ('COUNTBLANK',       1,  1, 0x02,  1, 'V', 'R'),
+    350: ('ISPMT',            4,  4, 0x02,  4, 'V', 'VVVV'),
+    351: ('DATEDIF',          3,  3, 0x02,  3, 'V', 'VVV'),
+    352: ('DATESTRING',       1,  1, 0x02,  1, 'V', 'V'),
+    353: ('NUMBERSTRING',     2,  2, 0x02,  2, 'V', 'VV'),
+    354: ('ROMAN',            1,  2, 0x04,  2, 'V', 'VV'),
+    358: ('GETPIVOTDATA',     2,  2, 0x02,  2, 'V', 'RV'),
+    359: ('HYPERLINK',        1,  2, 0x04,  2, 'V', 'VV'),
+    360: ('PHONETIC',         1,  1, 0x02,  1, 'V', 'V'),
+    361: ('AVERAGEA',         1, 30, 0x04,  1, 'V', 'R'),
+    362: ('MAXA',             1, 30, 0x04,  1, 'V', 'R'),
+    363: ('MINA',             1, 30, 0x04,  1, 'V', 'R'),
+    364: ('STDEVPA',          1, 30, 0x04,  1, 'V', 'R'),
+    365: ('VARPA',            1, 30, 0x04,  1, 'V', 'R'),
+    366: ('STDEVA',           1, 30, 0x04,  1, 'V', 'R'),
+    367: ('VARA',             1, 30, 0x04,  1, 'V', 'R'),
+    368: ('BAHTTEXT',         1,  1, 0x02,  1, 'V', 'V'),
+    369: ('THAIDAYOFWEEK',    1,  1, 0x02,  1, 'V', 'V'),
+    370: ('THAIDIGIT',        1,  1, 0x02,  1, 'V', 'V'),
+    371: ('THAIMONTHOFYEAR',  1,  1, 0x02,  1, 'V', 'V'),
+    372: ('THAINUMSOUND',     1,  1, 0x02,  1, 'V', 'V'),
+    373: ('THAINUMSTRING',    1,  1, 0x02,  1, 'V', 'V'),
+    374: ('THAISTRINGLENGTH', 1,  1, 0x02,  1, 'V', 'V'),
+    375: ('ISTHAIDIGIT',      1,  1, 0x02,  1, 'V', 'V'),
+    376: ('ROUNDBAHTDOWN',    1,  1, 0x02,  1, 'V', 'V'),
+    377: ('ROUNDBAHTUP',      1,  1, 0x02,  1, 'V', 'V'),
+    378: ('THAIYEAR',         1,  1, 0x02,  1, 'V', 'V'),
+    379: ('RTD',              2,  5, 0x04,  1, 'V', 'V'),
+}
+
+tAttrNames = {
+    0x00: "Skip??", # seen in SAMPLES.XLS which shipped with Excel 5.0
+    0x01: "Volatile",
+    0x02: "If",
+    0x04: "Choose",
+    0x08: "Skip",
+    0x10: "Sum",
+    0x20: "Assign",
+    0x40: "Space",
+    0x41: "SpaceVolatile",
+}
+
+error_opcodes = set([0x07, 0x08, 0x0A, 0x0B, 0x1C, 0x1D, 0x2F])
+
+tRangeFuncs = (min, max, min, max, min, max)
+tIsectFuncs = (max, min, max, min, max, min)
+
+def do_box_funcs(box_funcs, boxa, boxb):
+    return tuple(
+        func(numa, numb)
+        for func, numa, numb in zip(box_funcs, boxa.coords, boxb.coords)
+    )
+
+def adjust_cell_addr_biff8(rowval, colval, reldelta, browx=None, bcolx=None):
+    row_rel = (colval >> 15) & 1
+    col_rel = (colval >> 14) & 1
+    rowx = rowval
+    colx = colval & 0xff
+    if reldelta:
+        if row_rel and rowx >= 32768:
+            rowx -= 65536
+        if col_rel and colx >= 128:
+            colx -= 256
+    else:
+        if row_rel:
+            rowx -= browx
+        if col_rel:
+            colx -= bcolx
+    return rowx, colx, row_rel, col_rel
+
+def adjust_cell_addr_biff_le7(
+        rowval, colval, reldelta, browx=None, bcolx=None):
+    row_rel = (rowval >> 15) & 1
+    col_rel = (rowval >> 14) & 1
+    rowx = rowval & 0x3fff
+    colx = colval
+    if reldelta:
+        if row_rel and rowx >= 8192:
+            rowx -= 16384
+        if col_rel and colx >= 128:
+            colx -= 256
+    else:
+        if row_rel:
+            rowx -= browx
+        if col_rel:
+            colx -= bcolx
+    return rowx, colx, row_rel, col_rel
+
+def get_cell_addr(data, pos, bv, reldelta, browx=None, bcolx=None):
+    if bv >= 80:
+        rowval, colval = unpack("<HH", data[pos:pos+4])
+        # print "    rv=%04xh cv=%04xh" % (rowval, colval)
+        return adjust_cell_addr_biff8(rowval, colval, reldelta, browx, bcolx)
+    else:
+        rowval, colval = unpack("<HB", data[pos:pos+3])
+        # print "    rv=%04xh cv=%04xh" % (rowval, colval)
+        return adjust_cell_addr_biff_le7(
+                    rowval, colval, reldelta, browx, bcolx)
+
+def get_cell_range_addr(data, pos, bv, reldelta, browx=None, bcolx=None):
+    if bv >= 80:
+        row1val, row2val, col1val, col2val = unpack("<HHHH", data[pos:pos+8])
+        # print "    rv=%04xh cv=%04xh" % (row1val, col1val)
+        # print "    rv=%04xh cv=%04xh" % (row2val, col2val)
+        res1 = adjust_cell_addr_biff8(row1val, col1val, reldelta, browx, bcolx)
+        res2 = adjust_cell_addr_biff8(row2val, col2val, reldelta, browx, bcolx)
+        return res1, res2
+    else:
+        row1val, row2val, col1val, col2val = unpack("<HHBB", data[pos:pos+6])
+        # print "    rv=%04xh cv=%04xh" % (row1val, col1val)
+        # print "    rv=%04xh cv=%04xh" % (row2val, col2val)
+        res1 = adjust_cell_addr_biff_le7(
+                    row1val, col1val, reldelta, browx, bcolx)
+        res2 = adjust_cell_addr_biff_le7(
+                    row2val, col2val, reldelta, browx, bcolx)
+        return res1, res2
+
+def get_externsheet_local_range(bk, refx, blah=0):
+    try:
+        info = bk._externsheet_info[refx]
+    except IndexError:
+        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("/// 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("/// get_externsheet_local_range(refx=%d) -> external %r" % (refx, info), file=bk.logfile)
+        return (-4, -4) # external reference
+    if ref_first_sheetx == 0xFFFE == ref_last_sheetx:
+        if blah:
+            print("/// get_externsheet_local_range(refx=%d) -> unspecified sheet %r" % (refx, info), file=bk.logfile)
+        return (-1, -1) # internal reference, any sheet
+    if ref_first_sheetx == 0xFFFF == ref_last_sheetx:
+        if blah:
+            print("/// get_externsheet_local_range(refx=%d) -> deleted sheet(s)" % (refx, ), file=bk.logfile)
+        return (-2, -2) # internal reference, deleted sheet(s)
+    nsheets = len(bk._all_sheets_map)
+    if not(0 <= ref_first_sheetx <= ref_last_sheetx < nsheets):
+        if blah:
+            print("/// get_externsheet_local_range(refx=%d) -> %r" % (refx, info), file=bk.logfile)
+            print("--- first/last sheet not in range(%d)" % nsheets, file=bk.logfile)
+        return (-102, -102) # stuffed up somewhere :-(
+    xlrd_sheetx1 = bk._all_sheets_map[ref_first_sheetx]
+    xlrd_sheetx2 = bk._all_sheets_map[ref_last_sheetx]
+    if not(0 <= xlrd_sheetx1 <= xlrd_sheetx2):
+        return (-3, -3) # internal reference, but to a macro sheet
+    return xlrd_sheetx1, xlrd_sheetx2
+
+def get_externsheet_local_range_b57(
+        bk, raw_extshtx, ref_first_sheetx, ref_last_sheetx, blah=0):
+    if raw_extshtx > 0:
+        if blah:
+            print("/// get_externsheet_local_range_b57(raw_extshtx=%d) -> external" % raw_extshtx, file=bk.logfile)
+        return (-4, -4) # external reference
+    if ref_first_sheetx == -1 and ref_last_sheetx == -1:
+        return (-2, -2) # internal reference, deleted sheet(s)
+    nsheets = len(bk._all_sheets_map)
+    if not(0 <= ref_first_sheetx <= ref_last_sheetx < nsheets):
+        if blah:
+            print("/// get_externsheet_local_range_b57(%d, %d, %d) -> ???"
+                % (raw_extshtx, ref_first_sheetx, ref_last_sheetx), file=bk.logfile)
+            print("--- first/last sheet not in range(%d)" % nsheets, file=bk.logfile)
+        return (-103, -103) # stuffed up somewhere :-(
+    xlrd_sheetx1 = bk._all_sheets_map[ref_first_sheetx]
+    xlrd_sheetx2 = bk._all_sheets_map[ref_last_sheetx]
+    if not(0 <= xlrd_sheetx1 <= xlrd_sheetx2):
+        return (-3, -3) # internal reference, but to a macro sheet
+    return xlrd_sheetx1, xlrd_sheetx2
+
+class FormulaError(Exception):
+    pass
+
+
+class Operand(object):
+    """
+    Used in evaluating formulas.
+    The following table describes the kinds and how their values
+    are represented.
+
+    .. raw:: html
+
+        <table border="1" cellpadding="7">
+        <tr>
+        <th>Kind symbol</th>
+        <th>Kind number</th>
+        <th>Value representation</th>
+        </tr>
+        <tr>
+        <td>oBOOL</td>
+        <td align="center">3</td>
+        <td>integer: 0 => False; 1 => True</td>
+        </tr>
+        <tr>
+        <td>oERR</td>
+        <td align="center">4</td>
+        <td>None, or an int error code (same as XL_CELL_ERROR in the Cell class).
+        </td>
+        </tr>
+        <tr>
+        <td>oMSNG</td>
+        <td align="center">5</td>
+        <td>Used by Excel as a placeholder for a missing (not supplied) function
+        argument. Should *not* appear as a final formula result. Value is None.</td>
+        </tr>
+        <tr>
+        <td>oNUM</td>
+        <td align="center">2</td>
+        <td>A float. Note that there is no way of distinguishing dates.</td>
+        </tr>
+        <tr>
+        <td>oREF</td>
+        <td align="center">-1</td>
+        <td>The value is either None or a non-empty list of
+        absolute Ref3D instances.<br>
+        </td>
+        </tr>
+        <tr>
+        <td>oREL</td>
+        <td align="center">-2</td>
+        <td>The value is None or a non-empty list of
+        fully or partially relative Ref3D instances.
+        </td>
+        </tr>
+        <tr>
+        <td>oSTRG</td>
+        <td align="center">1</td>
+        <td>A Unicode string.</td>
+        </tr>
+        <tr>
+        <td>oUNK</td>
+        <td align="center">0</td>
+        <td>The kind is unknown or ambiguous. The value is None</td>
+        </tr>
+        </table>
+    """
+
+    #: None means that the actual value of the operand is a variable
+    #: (depends on cell data), not a constant.
+    value = None
+
+    #: oUNK means that the kind of operand is not known unambiguously.
+    kind = oUNK
+
+    #: The reconstituted text of the original formula. Function names will be
+    #: in English irrespective of the original language, which doesn't seem
+    #: to be recorded anywhere. The separator is ",", not ";" or whatever else
+    #: might be more appropriate for the end-user's locale; patches welcome.
+    text = '?'
+
+    def __init__(self, akind=None, avalue=None, arank=0, atext='?'):
+        if akind is not None:
+            self.kind = akind
+        if avalue is not None:
+            self.value = avalue
+        self.rank = arank
+        # rank is an internal gizmo (operator precedence);
+        # it's used in reconstructing formula text.
+        self.text = atext
+
+    def __repr__(self):
+        kind_text = okind_dict.get(self.kind, "?Unknown kind?")
+        return "Operand(kind=%s, value=%r, text=%r)" \
+            % (kind_text, self.value, self.text)
+
+
+class Ref3D(tuple):
+    """
+    Represents an absolute or relative 3-dimensional reference to a box
+    of one or more cells.
+
+    The ``coords`` attribute is a tuple of the form::
+
+      (shtxlo, shtxhi, rowxlo, rowxhi, colxlo, colxhi)
+
+    where ``0 <= thingxlo <= thingx < thingxhi``.
+
+    .. note::
+      It is quite possible to have ``thingx > nthings``; for example
+      ``Print_Titles`` could have ``colxhi == 256`` and/or ``rowxhi == 65536``
+      irrespective of how many columns/rows are actually used in the worksheet.
+      The caller will need to decide how to handle this situation.
+      Keyword: :class:`IndexError` :-)
+
+    The components of the coords attribute are also available as individual
+    attributes: ``shtxlo``, ``shtxhi``, ``rowxlo``, ``rowxhi``, ``colxlo``, and
+    ``colxhi``.
+
+    The ``relflags`` attribute is a 6-tuple of flags which indicate whether
+    the corresponding (sheet|row|col)(lo|hi) is relative (1) or absolute (0).
+
+    .. note::
+      There is necessarily no information available as to what cell(s)
+      the reference could possibly be relative to. The caller must decide what
+      if any use to make of ``oREL`` operands.
+
+    .. note:
+      A partially relative reference may well be a typo.
+      For example, define name ``A1Z10`` as ``$a$1:$z10`` (missing ``$`` after
+      ``z``) while the cursor is on cell ``Sheet3!A27``.
+
+      The resulting :class:`Ref3D` instance will have
+      ``coords = (2, 3, 0, -16, 0, 26)``
+      and ``relflags = (0, 0, 0, 1, 0, 0).<br>
+
+      So far, only one possibility of a sheet-relative component in
+      a reference has been noticed: a 2D reference located in the
+      "current sheet".
+
+      This will appear as ``coords = (0, 1, ...)`` and
+      ``relflags = (1, 1, ...)``.
+
+    .. versionadded:: 0.6.0
+    """
+
+    def __init__(self, atuple):
+        self.coords = atuple[0:6]
+        self.relflags = atuple[6:12]
+        if not self.relflags:
+            self.relflags = (0, 0, 0, 0, 0, 0)
+        (self.shtxlo, self.shtxhi,
+        self.rowxlo, self.rowxhi,
+        self.colxlo, self.colxhi) = self.coords
+
+    def __repr__(self):
+        if not self.relflags or self.relflags == (0, 0, 0, 0, 0, 0):
+            return "Ref3D(coords=%r)" % (self.coords, )
+        else:
+            return "Ref3D(coords=%r, relflags=%r)" \
+                % (self.coords, self.relflags)
+
+tAdd = 0x03
+tSub = 0x04
+tMul = 0x05
+tDiv = 0x06
+tPower = 0x07
+tConcat = 0x08
+tLT, tLE, tEQ, tGE, tGT, tNE = range(0x09, 0x0F)
+
+
+def nop(x):
+    return x
+
+def _opr_pow(x, y): return x ** y
+
+def _opr_lt(x, y): return x <  y
+def _opr_le(x, y): return x <= y
+def _opr_eq(x, y): return x == y
+def _opr_ge(x, y): return x >= y
+def _opr_gt(x, y): return x >  y
+def _opr_ne(x, y): return x != y
+
+def num2strg(num):
+    """
+    Attempt to emulate Excel's default conversion from number to string.
+    """
+    s = str(num)
+    if s.endswith(".0"):
+        s = s[:-2]
+    return s
+
+_arith_argdict = {oNUM: nop,     oSTRG: float}
+_cmp_argdict =   {oNUM: nop,     oSTRG: nop}
+# Seems no conversions done on relops; in Excel, "1" > 9 produces TRUE.
+_strg_argdict =  {oNUM:num2strg, oSTRG:nop}
+binop_rules = {
+    tAdd:   (_arith_argdict, oNUM, opr.add,  30, '+'),
+    tSub:   (_arith_argdict, oNUM, opr.sub,  30, '-'),
+    tMul:   (_arith_argdict, oNUM, opr.mul,  40, '*'),
+    tDiv:   (_arith_argdict, oNUM, opr.truediv,  40, '/'),
+    tPower: (_arith_argdict, oNUM, _opr_pow, 50, '^',),
+    tConcat:(_strg_argdict, oSTRG, opr.add,  20, '&'),
+    tLT:    (_cmp_argdict, oBOOL, _opr_lt,   10, '<'),
+    tLE:    (_cmp_argdict, oBOOL, _opr_le,   10, '<='),
+    tEQ:    (_cmp_argdict, oBOOL, _opr_eq,   10, '='),
+    tGE:    (_cmp_argdict, oBOOL, _opr_ge,   10, '>='),
+    tGT:    (_cmp_argdict, oBOOL, _opr_gt,   10, '>'),
+    tNE:    (_cmp_argdict, oBOOL, _opr_ne,   10, '<>'),
+}
+
+unop_rules = {
+    0x13: (lambda x: -x,        70, '-', ''), # unary minus
+    0x12: (lambda x: x,         70, '+', ''), # unary plus
+    0x14: (lambda x: x / 100.0, 60, '',  '%'),# percent
+}
+
+LEAF_RANK = 90
+FUNC_RANK = 90
+
+STACK_ALARM_LEVEL = 5
+STACK_PANIC_LEVEL = 10
+
+def evaluate_name_formula(bk, nobj, namex, blah=0, level=0):
+    if level > STACK_ALARM_LEVEL:
+        blah = 1
+    data = nobj.raw_formula
+    fmlalen = nobj.basic_formula_len
+    bv = bk.biff_version
+    reldelta = 1 # All defined name formulas use "Method B" [OOo docs]
+    if blah:
+        print("::: evaluate_name_formula %r %r %d %d %r level=%d"
+            % (namex, nobj.name, fmlalen, bv, data, level), file=bk.logfile)
+        hex_char_dump(data, 0, fmlalen, fout=bk.logfile)
+    if level > STACK_PANIC_LEVEL:
+        raise XLRDError("Excessive indirect references in NAME formula")
+    sztab = szdict[bv]
+    pos = 0
+    stack = []
+    any_rel = 0
+    any_err = 0
+    any_external = 0
+    unk_opnd = Operand(oUNK, None)
+    error_opnd = Operand(oERR, None)
+    spush = stack.append
+
+    def do_binop(opcd, stk):
+        assert len(stk) >= 2
+        bop = stk.pop()
+        aop = stk.pop()
+        argdict, result_kind, func, rank, sym = binop_rules[opcd]
+        otext = ''.join([
+            '('[:aop.rank < rank],
+            aop.text,
+            ')'[:aop.rank < rank],
+            sym,
+            '('[:bop.rank < rank],
+            bop.text,
+            ')'[:bop.rank < rank],
+        ])
+        resop = Operand(result_kind, None, rank, otext)
+        try:
+            bconv = argdict[bop.kind]
+            aconv = argdict[aop.kind]
+        except KeyError:
+            stk.append(resop)
+            return
+        if bop.value is None or aop.value is None:
+            stk.append(resop)
+            return
+        bval = bconv(bop.value)
+        aval = aconv(aop.value)
+        result = func(aval, bval)
+        if result_kind == oBOOL:
+            result = 1 if result else 0
+        resop.value = result
+        stk.append(resop)
+
+    def do_unaryop(opcode, result_kind, stk):
+        assert len(stk) >= 1
+        aop = stk.pop()
+        val = aop.value
+        func, rank, sym1, sym2 = unop_rules[opcode]
+        otext = ''.join([
+            sym1,
+            '('[:aop.rank < rank],
+            aop.text,
+            ')'[:aop.rank < rank],
+            sym2,
+        ])
+        if val is not None:
+            val = func(val)
+        stk.append(Operand(result_kind, val, rank, otext))
+
+    def not_in_name_formula(op_arg, oname_arg):
+        msg = "ERROR *** Token 0x%02x (%s) found in NAME formula" \
+              % (op_arg, oname_arg)
+        raise FormulaError(msg)
+
+    if fmlalen == 0:
+        stack = [unk_opnd]
+
+    while 0 <= pos < fmlalen:
+        op = BYTES_ORD(data[pos])
+        opcode = op & 0x1f
+        optype = (op & 0x60) >> 5
+        if optype:
+            opx = opcode + 32
+        else:
+            opx = opcode
+        oname = onames[opx] # + [" RVA"][optype]
+        sz = sztab[opx]
+        if blah:
+            print("Pos:%d Op:0x%02x Name:t%s Sz:%d opcode:%02xh optype:%02xh"
+                % (pos, op, oname, sz, opcode, optype), file=bk.logfile)
+            print("Stack =", stack, file=bk.logfile)
+        if sz == -2:
+            msg = 'ERROR *** Unexpected token 0x%02x ("%s"); biff_version=%d' \
+                % (op, oname, bv)
+            raise FormulaError(msg)
+        if not optype:
+            if 0x00 <= opcode <= 0x02: # unk_opnd, tExp, tTbl
+                not_in_name_formula(op, oname)
+            elif 0x03 <= opcode <= 0x0E:
+                # Add, Sub, Mul, Div, Power
+                # tConcat
+                # tLT, ..., tNE
+                do_binop(opcode, stack)
+            elif opcode == 0x0F: # tIsect
+                if blah: print("tIsect pre", stack, file=bk.logfile)
+                assert len(stack) >= 2
+                bop = stack.pop()
+                aop = stack.pop()
+                sym = ' '
+                rank = 80 ########## check #######
+                otext = ''.join([
+                    '('[:aop.rank < rank],
+                    aop.text,
+                    ')'[:aop.rank < rank],
+                    sym,
+                    '('[:bop.rank < rank],
+                    bop.text,
+                    ')'[:bop.rank < rank],
+                ])
+                res = Operand(oREF)
+                res.text = otext
+                if bop.kind == oERR or aop.kind == oERR:
+                    res.kind = oERR
+                elif bop.kind == oUNK or aop.kind == oUNK:
+                    # This can happen with undefined
+                    # (go search in the current sheet) labels.
+                    # For example =Bob Sales
+                    # Each label gets a NAME record with an empty formula (!)
+                    # Evaluation of the tName token classifies it as oUNK
+                    # res.kind = oREF
+                    pass
+                elif bop.kind == oREF == aop.kind:
+                    if aop.value is not None and bop.value is not None:
+                        assert len(aop.value) == 1
+                        assert len(bop.value) == 1
+                        coords = do_box_funcs(
+                            tIsectFuncs, aop.value[0], bop.value[0])
+                        res.value = [Ref3D(coords)]
+                elif bop.kind == oREL == aop.kind:
+                    res.kind = oREL
+                    if aop.value is not None and bop.value is not None:
+                        assert len(aop.value) == 1
+                        assert len(bop.value) == 1
+                        coords = do_box_funcs(
+                            tIsectFuncs, aop.value[0], bop.value[0])
+                        relfa = aop.value[0].relflags
+                        relfb = bop.value[0].relflags
+                        if relfa == relfb:
+                            res.value = [Ref3D(coords + relfa)]
+                else:
+                    pass
+                spush(res)
+                if blah: print("tIsect post", stack, file=bk.logfile)
+            elif opcode == 0x10: # tList
+                if blah: print("tList pre", stack, file=bk.logfile)
+                assert len(stack) >= 2
+                bop = stack.pop()
+                aop = stack.pop()
+                sym = ','
+                rank = 80 ########## check #######
+                otext = ''.join([
+                    '('[:aop.rank < rank],
+                    aop.text,
+                    ')'[:aop.rank < rank],
+                    sym,
+                    '('[:bop.rank < rank],
+                    bop.text,
+                    ')'[:bop.rank < rank],
+                ])
+                res = Operand(oREF, None, rank, otext)
+                if bop.kind == oERR or aop.kind == oERR:
+                    res.kind = oERR
+                elif bop.kind in (oREF, oREL) and aop.kind in (oREF, oREL):
+                    res.kind = oREF
+                    if aop.kind == oREL or bop.kind == oREL:
+                        res.kind = oREL
+                    if aop.value is not None and bop.value is not None:
+                        assert len(aop.value) >= 1
+                        assert len(bop.value) == 1
+                        res.value = aop.value + bop.value
+                else:
+                    pass
+                spush(res)
+                if blah: print("tList post", stack, file=bk.logfile)
+            elif opcode == 0x11: # tRange
+                if blah: print("tRange pre", stack, file=bk.logfile)
+                assert len(stack) >= 2
+                bop = stack.pop()
+                aop = stack.pop()
+                sym = ':'
+                rank = 80 ########## check #######
+                otext = ''.join([
+                    '('[:aop.rank < rank],
+                    aop.text,
+                    ')'[:aop.rank < rank],
+                    sym,
+                    '('[:bop.rank < rank],
+                    bop.text,
+                    ')'[:bop.rank < rank],
+                ])
+                res = Operand(oREF, None, rank, otext)
+                if bop.kind == oERR or aop.kind == oERR:
+                    res = oERR
+                elif bop.kind == oREF == aop.kind:
+                    if aop.value is not None and bop.value is not None:
+                        assert len(aop.value) == 1
+                        assert len(bop.value) == 1
+                        coords = do_box_funcs(
+                            tRangeFuncs, aop.value[0], bop.value[0])
+                        res.value = [Ref3D(coords)]
+                elif bop.kind == oREL == aop.kind:
+                    res.kind = oREL
+                    if aop.value is not None and bop.value is not None:
+                        assert len(aop.value) == 1
+                        assert len(bop.value) == 1
+                        coords = do_box_funcs(
+                            tRangeFuncs, aop.value[0], bop.value[0])
+                        relfa = aop.value[0].relflags
+                        relfb = bop.value[0].relflags
+                        if relfa == relfb:
+                            res.value = [Ref3D(coords + relfa)]
+                else:
+                    pass
+                spush(res)
+                if blah: print("tRange post", stack, file=bk.logfile)
+            elif 0x12 <= opcode <= 0x14: # tUplus, tUminus, tPercent
+                do_unaryop(opcode, oNUM, stack)
+            elif opcode == 0x15: # tParen
+                # source cosmetics
+                pass
+            elif opcode == 0x16: # tMissArg
+                spush(Operand(oMSNG, None, LEAF_RANK, ''))
+            elif opcode == 0x17: # tStr
+                if bv <= 70:
+                    strg, newpos = unpack_string_update_pos(
+                                        data, pos+1, bk.encoding, lenlen=1)
+                else:
+                    strg, newpos = unpack_unicode_update_pos(
+                                        data, pos+1, lenlen=1)
+                sz = newpos - pos
+                if blah: print("   sz=%d strg=%r" % (sz, strg), file=bk.logfile)
+                text = '"' + strg.replace('"', '""') + '"'
+                spush(Operand(oSTRG, strg, LEAF_RANK, text))
+            elif opcode == 0x18: # tExtended
+                # new with BIFF 8
+                assert bv >= 80
+                # not in OOo docs
+                raise FormulaError("tExtended token not implemented")
+            elif opcode == 0x19: # tAttr
+                subop, nc = unpack("<BH", data[pos+1:pos+4])
+                subname = tAttrNames.get(subop, "??Unknown??")
+                if subop == 0x04: # Choose
+                    sz = nc * 2 + 6
+                elif subop == 0x10: # Sum (single arg)
+                    sz = 4
+                    if blah: print("tAttrSum", stack, file=bk.logfile)
+                    assert len(stack) >= 1
+                    aop = stack[-1]
+                    otext = 'SUM(%s)' % aop.text
+                    stack[-1] = Operand(oNUM, None, FUNC_RANK, otext)
+                else:
+                    sz = 4
+                if blah:
+                    print("   subop=%02xh subname=t%s sz=%d nc=%02xh"
+                        % (subop, subname, sz, nc), file=bk.logfile)
+            elif 0x1A <= opcode <= 0x1B: # tSheet, tEndSheet
+                assert bv < 50
+                raise FormulaError("tSheet & tEndsheet tokens not implemented")
+            elif 0x1C <= opcode <= 0x1F: # tErr, tBool, tInt, tNum
+                inx = opcode - 0x1C
+                nb = [1, 1, 2, 8][inx]
+                kind = [oERR, oBOOL, oNUM, oNUM][inx]
+                value, = unpack("<" + "BBHd"[inx], data[pos+1:pos+1+nb])
+                if inx == 2: # tInt
+                    value = float(value)
+                    text = str(value)
+                elif inx == 3: # tNum
+                    text = str(value)
+                elif inx == 1: # tBool
+                    text = ('FALSE', 'TRUE')[value]
+                else:
+                    text = '"' +error_text_from_code[value] + '"'
+                spush(Operand(kind, value, LEAF_RANK, text))
+            else:
+                raise FormulaError("Unhandled opcode: 0x%02x" % opcode)
+            if sz <= 0:
+                raise FormulaError("Size not set for opcode 0x%02x" % opcode)
+            pos += sz
+            continue
+        if opcode == 0x00: # tArray
+            spush(unk_opnd)
+        elif opcode == 0x01: # tFunc
+            nb = 1 + int(bv >= 40)
+            funcx = unpack("<" + " BH"[nb], data[pos+1:pos+1+nb])[0]
+            func_attrs = func_defs.get(funcx, None)
+            if not func_attrs:
+                print("*** formula/tFunc unknown FuncID:%d"
+                      % funcx, file=bk.logfile)
+                spush(unk_opnd)
+            else:
+                func_name, nargs = func_attrs[:2]
+                if blah:
+                    print("    FuncID=%d name=%s nargs=%d"
+                          % (funcx, func_name, nargs), file=bk.logfile)
+                assert len(stack) >= nargs
+                if nargs:
+                    argtext = listsep.join(arg.text for arg in stack[-nargs:])
+                    otext = "%s(%s)" % (func_name, argtext)
+                    del stack[-nargs:]
+                else:
+                    otext = func_name + "()"
+                res = Operand(oUNK, None, FUNC_RANK, otext)
+                spush(res)
+        elif opcode == 0x02: #tFuncVar
+            nb = 1 + int(bv >= 40)
+            nargs, funcx = unpack("<B" + " BH"[nb], data[pos+1:pos+2+nb])
+            prompt, nargs = divmod(nargs, 128)
+            macro, funcx = divmod(funcx, 32768)
+            if blah:
+                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("*** formula/tFuncVar unknown FuncID:%d"
+                      % funcx, file=bk.logfile)
+                spush(unk_opnd)
+            else:
+                func_name, minargs, maxargs = func_attrs[:3]
+                if blah:
+                    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:])
+                otext = "%s(%s)" % (func_name, argtext)
+                res = Operand(oUNK, None, FUNC_RANK, otext)
+                if funcx == 1: # IF
+                    testarg = stack[-nargs]
+                    if testarg.kind not in (oNUM, oBOOL):
+                        if blah and testarg.kind != oUNK:
+                            print("IF testarg kind?", file=bk.logfile)
+                    elif testarg.value not in (0, 1):
+                        if blah and testarg.value is not None:
+                            print("IF testarg value?", file=bk.logfile)
+                    else:
+                        if nargs == 2 and not testarg.value:
+                            # IF(FALSE, tv) => FALSE
+                            res.kind, res.value = oBOOL, 0
+                        else:
+                            respos = -nargs + 2 - int(testarg.value)
+                            chosen = stack[respos]
+                            if chosen.kind == oMSNG:
+                                res.kind, res.value = oNUM, 0
+                            else:
+                                res.kind, res.value = chosen.kind, chosen.value
+                        if blah:
+                            print("$$$$$$ IF => constant", file=bk.logfile)
+                elif funcx == 100: # CHOOSE
+                    testarg = stack[-nargs]
+                    if testarg.kind == oNUM:
+                        if 1 <= testarg.value < nargs:
+                            chosen = stack[-nargs + int(testarg.value)]
+                            if chosen.kind == oMSNG:
+                                res.kind, res.value = oNUM, 0
+                            else:
+                                res.kind, res.value = chosen.kind, chosen.value
+                del stack[-nargs:]
+                spush(res)
+        elif opcode == 0x03: #tName
+            tgtnamex = unpack("<H", data[pos+1:pos+3])[0] - 1
+            # Only change with BIFF version is number of trailing UNUSED bytes!
+            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 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
+            else:
+                assert len(tgtobj.stack) == 1
+                res = copy.deepcopy(tgtobj.stack[0])
+            res.rank = LEAF_RANK
+            if tgtobj.scope == -1:
+                res.text = tgtobj.name
+            else:
+                res.text = "%s!%s" \
+                           % (bk._sheet_names[tgtobj.scope], tgtobj.name)
+            if blah:
+                print("    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("  ", 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("   ", coords, file=bk.logfile)
+            res = Operand(oUNK, None)
+            if optype == 1:
+                relflags = (1, 1, row_rel, row_rel, col_rel, col_rel)
+                res = Operand(oREL, [Ref3D(coords + relflags)])
+            spush(res)
+        elif opcode == 0x05: # tArea
+            # not_in_name_formula(op, oname)
+            res1, res2 = get_cell_range_addr(data, pos+1, bv, reldelta)
+            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("   ", coords, file=bk.logfile)
+            res = Operand(oUNK, None)
+            if optype == 1:
+                relflags = (1, 1, row_rel1, row_rel2, col_rel1, col_rel2)
+                res = Operand(oREL, [Ref3D(coords + relflags)])
+            spush(res)
+        elif opcode == 0x06: # tMemArea
+            not_in_name_formula(op, oname)
+        elif opcode == 0x09: # tMemFunc
+            nb = unpack("<H", data[pos+1:pos+3])[0]
+            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)
+            # 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
+            # spush(res)
+        elif opcode == 0x0D: #tAreaN
+            not_in_name_formula(op, oname)
+            # res = get_cell_range_addr(data, pos+1, bv, reldelta=1)
+            # # note *ALL* tAreaN usage has signed offset for relative addresses
+            # any_rel = 1
+            # if blah: print >> bk.logfile, "   ", res
+        elif opcode == 0x1A: # tRef3d
+            if bv >= 80:
+                res = get_cell_addr(data, pos+3, bv, reldelta)
+                refx = unpack("<H", data[pos+1:pos+3])[0]
+                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("<hxxxxxxxxhh", data[pos+1:pos+15])
+                if blah:
+                    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
+            is_rel = row_rel or col_rel
+            any_rel = any_rel or is_rel
+            coords = (shx1, shx2+1, rowx, rowx+1, colx, colx+1)
+            any_err |= shx1 < -1
+            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)
+                ref3d = Ref3D(coords + relflags)
+                res.kind = oREL
+                res.text = rangename3drel(bk, ref3d, r1c1=1)
+            else:
+                ref3d = Ref3D(coords)
+                res.kind = oREF
+                res.text = rangename3d(bk, ref3d)
+            res.rank = LEAF_RANK
+            if optype == 1:
+                res.value = [ref3d]
+            spush(res)
+        elif opcode == 0x1B: # tArea3d
+            if bv >= 80:
+                res1, res2 = get_cell_range_addr(data, pos+3, bv, reldelta)
+                refx = unpack("<H", data[pos+1:pos+3])[0]
+                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("<hxxxxxxxxhh", data[pos+1:pos+15])
+                if blah:
+                    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
+            rowx1, colx1, row_rel1, col_rel1 = res1
+            rowx2, colx2, row_rel2, col_rel2 = res2
+            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("   ", coords, file=bk.logfile)
+            res = Operand(oUNK, None)
+            if is_rel:
+                relflags = (0, 0, row_rel1, row_rel2, col_rel1, col_rel2)
+                ref3d = Ref3D(coords + relflags)
+                res.kind = oREL
+                res.text = rangename3drel(bk, ref3d, r1c1=1)
+            else:
+                ref3d = Ref3D(coords)
+                res.kind = oREF
+                res.text = rangename3d(bk, ref3d)
+            res.rank = LEAF_RANK
+            if optype == 1:
+                res.value = [ref3d]
+
+            spush(res)
+        elif opcode == 0x19: # tNameX
+            dodgy = 0
+            res = Operand(oUNK, None)
+            if bv >= 80:
+                refx, tgtnamex = unpack("<HH", data[pos+1:pos+5])
+                tgtnamex -= 1
+                origrefx = refx
+            else:
+                refx, tgtnamex = unpack("<hxxxxxxxxH", data[pos+1:pos+13])
+                tgtnamex -= 1
+                origrefx = refx
+                if refx > 0:
+                    refx -= 1
+                elif refx < 0:
+                    refx = -refx - 1
+                else:
+                    dodgy = 1
+            if blah:
+                print("   origrefx=%d refx=%d tgtnamex=%d dodgy=%d"
+                    % (origrefx, refx, tgtnamex, dodgy), file=bk.logfile)
+            if tgtnamex == namex:
+                if blah: print("!!!! Self-referential !!!!", file=bk.logfile)
+                dodgy = any_err = 1
+            if not dodgy:
+                if bv >= 80:
+                    shx1, shx2 = get_externsheet_local_range(bk, refx, blah)
+                elif origrefx > 0:
+                    shx1, shx2 = (-4, -4) # external ref
+                else:
+                    exty = bk._externsheet_type_b57[refx]
+                    if exty == 4: # non-specific sheet in own doc't
+                        shx1, shx2 = (-1, -1) # internal, any sheet
+                    else:
+                        shx1, shx2 = (-666, -666)
+            if dodgy or shx1 < -1:
+                otext = "<<Name #%d in external(?) file #%d>>" \
+                        % (tgtnamex, origrefx)
+                res = Operand(oUNK, None, LEAF_RANK, otext)
+            else:
+                tgtobj = bk.name_obj_list[tgtnamex]
+                if not tgtobj.evaluated:
+                    ### recursive ###
+                    evaluate_name_formula(bk, tgtobj, tgtnamex, blah, level+1)
+                if tgtobj.macro or tgtobj.binary or tgtobj.any_err:
+                    if blah:
+                        tgtobj.dump(
+                            bk.logfile,
+                            header="!!! bad tgtobj !!!",
+                            footer="------------------",
+                        )
+                    res = Operand(oUNK, None)
+                    any_err = any_err or tgtobj.macro or tgtobj.binary or tgtobj.any_err
+                    any_rel = any_rel or tgtobj.any_rel
+                else:
+                    assert len(tgtobj.stack) == 1
+                    res = copy.deepcopy(tgtobj.stack[0])
+                res.rank = LEAF_RANK
+                if tgtobj.scope == -1:
+                    res.text = tgtobj.name
+                else:
+                    res.text = "%s!%s" \
+                               % (bk._sheet_names[tgtobj.scope], tgtobj.name)
+                if blah:
+                    print("    tNameX: setting text to", repr(res.text), file=bk.logfile)
+            spush(res)
+        elif opcode in error_opcodes:
+            any_err = 1
+            spush(error_opnd)
+        else:
+            if blah:
+                print("FORMULA: /// Not handled yet: t" + oname, file=bk.logfile)
+            any_err = 1
+        if sz <= 0:
+            raise FormulaError("Fatal: token size is not positive")
+        pos += sz
+    any_rel = not not any_rel
+    if blah:
+        fprintf(bk.logfile, "End of formula. level=%d any_rel=%d any_err=%d stack=%r\n",
+            level, not not any_rel, any_err, stack)
+        if len(stack) >= 2:
+            print("*** Stack has unprocessed args", file=bk.logfile)
+        print(file=bk.logfile)
+    nobj.stack = stack
+    if len(stack) != 1:
+        nobj.result = None
+    else:
+        nobj.result = stack[0]
+    nobj.any_rel = any_rel
+    nobj.any_err = any_err
+    nobj.any_external = any_external
+    nobj.evaluated = 1
+
+#### under construction #############################################################################
+def decompile_formula(bk, fmla, fmlalen,
+                      fmlatype=None, browx=None, bcolx=None,
+                      blah=0, level=0, r1c1=0):
+    if level > STACK_ALARM_LEVEL:
+        blah = 1
+    reldelta = fmlatype in (FMLA_TYPE_SHARED, FMLA_TYPE_NAME, FMLA_TYPE_COND_FMT, FMLA_TYPE_DATA_VAL)
+    data = fmla
+    bv = bk.biff_version
+    if blah:
+        print("::: decompile_formula len=%d fmlatype=%r browx=%r bcolx=%r reldelta=%d %r level=%d"
+            % (fmlalen, fmlatype, browx, bcolx, reldelta, data, level), file=bk.logfile)
+        hex_char_dump(data, 0, fmlalen, fout=bk.logfile)
+    if level > STACK_PANIC_LEVEL:
+        raise XLRDError("Excessive indirect references in formula")
+    sztab = szdict[bv]
+    pos = 0
+    stack = []
+    any_rel = 0
+    any_err = 0
+    unk_opnd = Operand(oUNK, None)
+    error_opnd = Operand(oERR, None)
+    spush = stack.append
+
+    def do_binop(opcd, stk):
+        assert len(stk) >= 2
+        bop = stk.pop()
+        aop = stk.pop()
+        argdict, result_kind, func, rank, sym = binop_rules[opcd]
+        otext = ''.join([
+            '('[:aop.rank < rank],
+            aop.text,
+            ')'[:aop.rank < rank],
+            sym,
+            '('[:bop.rank < rank],
+            bop.text,
+            ')'[:bop.rank < rank],
+        ])
+        resop = Operand(result_kind, None, rank, otext)
+        stk.append(resop)
+
+    def do_unaryop(opcode, result_kind, stk):
+        assert len(stk) >= 1
+        aop = stk.pop()
+        func, rank, sym1, sym2 = unop_rules[opcode]
+        otext = ''.join([
+            sym1,
+            '('[:aop.rank < rank],
+            aop.text,
+            ')'[:aop.rank < rank],
+            sym2,
+        ])
+        stk.append(Operand(result_kind, None, rank, otext))
+
+    def unexpected_opcode(op_arg, oname_arg):
+        msg = "ERROR *** Unexpected token 0x%02x (%s) found in formula type %s" \
+              % (op_arg, oname_arg, FMLA_TYPEDESCR_MAP[fmlatype])
+        print(msg, file=bk.logfile)
+        # raise FormulaError(msg)
+
+    if fmlalen == 0:
+        stack = [unk_opnd]
+
+    while 0 <= pos < fmlalen:
+        op = BYTES_ORD(data[pos])
+        opcode = op & 0x1f
+        optype = (op & 0x60) >> 5
+        if optype:
+            opx = opcode + 32
+        else:
+            opx = opcode
+        oname = onames[opx] # + [" RVA"][optype]
+        sz = sztab[opx]
+        if blah:
+            print("Pos:%d Op:0x%02x opname:t%s Sz:%d opcode:%02xh optype:%02xh"
+                % (pos, op, oname, sz, opcode, optype), file=bk.logfile)
+            print("Stack =", stack, file=bk.logfile)
+        if sz == -2:
+            msg = 'ERROR *** Unexpected token 0x%02x ("%s"); biff_version=%d' \
+                % (op, oname, bv)
+            raise FormulaError(msg)
+        if _TOKEN_NOT_ALLOWED(opx, 0) & fmlatype:
+            unexpected_opcode(op, oname)
+        if not optype:
+            if opcode <= 0x01: # tExp
+                if bv >= 30:
+                    fmt = '<x2H'
+                else:
+                    fmt = '<xHB'
+                assert pos == 0 and fmlalen == sz and not stack
+                rowx, colx = unpack(fmt, data)
+                text = "SHARED FMLA at rowx=%d colx=%d" % (rowx, colx)
+                spush(Operand(oUNK, None, LEAF_RANK, text))
+                if not fmlatype & (FMLA_TYPE_CELL | FMLA_TYPE_ARRAY):
+                    unexpected_opcode(op, oname)
+            elif 0x03 <= opcode <= 0x0E:
+                # Add, Sub, Mul, Div, Power
+                # tConcat
+                # tLT, ..., tNE
+                do_binop(opcode, stack)
+            elif opcode == 0x0F: # tIsect
+                if blah: print("tIsect pre", stack, file=bk.logfile)
+                assert len(stack) >= 2
+                bop = stack.pop()
+                aop = stack.pop()
+                sym = ' '
+                rank = 80 ########## check #######
+                otext = ''.join([
+                    '('[:aop.rank < rank],
+                    aop.text,
+                    ')'[:aop.rank < rank],
+                    sym,
+                    '('[:bop.rank < rank],
+                    bop.text,
+                    ')'[:bop.rank < rank],
+                ])
+                res = Operand(oREF)
+                res.text = otext
+                if bop.kind == oERR or aop.kind == oERR:
+                    res.kind = oERR
+                elif bop.kind == oUNK or aop.kind == oUNK:
+                    # This can happen with undefined
+                    # (go search in the current sheet) labels.
+                    # For example =Bob Sales
+                    # Each label gets a NAME record with an empty formula (!)
+                    # Evaluation of the tName token classifies it as oUNK
+                    # res.kind = oREF
+                    pass
+                elif bop.kind == oREF == aop.kind:
+                    pass
+                elif bop.kind == oREL == aop.kind:
+                    res.kind = oREL
+                else:
+                    pass
+                spush(res)
+                if blah: print("tIsect post", stack, file=bk.logfile)
+            elif opcode == 0x10: # tList
+                if blah: print("tList pre", stack, file=bk.logfile)
+                assert len(stack) >= 2
+                bop = stack.pop()
+                aop = stack.pop()
+                sym = ','
+                rank = 80 ########## check #######
+                otext = ''.join([
+                    '('[:aop.rank < rank],
+                    aop.text,
+                    ')'[:aop.rank < rank],
+                    sym,
+                    '('[:bop.rank < rank],
+                    bop.text,
+                    ')'[:bop.rank < rank],
+                ])
+                res = Operand(oREF, None, rank, otext)
+                if bop.kind == oERR or aop.kind == oERR:
+                    res.kind = oERR
+                elif bop.kind in (oREF, oREL) and aop.kind in (oREF, oREL):
+                    res.kind = oREF
+                    if aop.kind == oREL or bop.kind == oREL:
+                        res.kind = oREL
+                else:
+                    pass
+                spush(res)
+                if blah: print("tList post", stack, file=bk.logfile)
+            elif opcode == 0x11: # tRange
+                if blah: print("tRange pre", stack, file=bk.logfile)
+                assert len(stack) >= 2
+                bop = stack.pop()
+                aop = stack.pop()
+                sym = ':'
+                rank = 80 ########## check #######
+                otext = ''.join([
+                    '('[:aop.rank < rank],
+                    aop.text,
+                    ')'[:aop.rank < rank],
+                    sym,
+                    '('[:bop.rank < rank],
+                    bop.text,
+                    ')'[:bop.rank < rank],
+                ])
+                res = Operand(oREF, None, rank, otext)
+                if bop.kind == oERR or aop.kind == oERR:
+                    res = oERR
+                elif bop.kind == oREF == aop.kind:
+                    pass
+                else:
+                    pass
+                spush(res)
+                if blah: print("tRange post", stack, file=bk.logfile)
+            elif 0x12 <= opcode <= 0x14: # tUplus, tUminus, tPercent
+                do_unaryop(opcode, oNUM, stack)
+            elif opcode == 0x15: # tParen
+                # source cosmetics
+                pass
+            elif opcode == 0x16: # tMissArg
+                spush(Operand(oMSNG, None, LEAF_RANK, ''))
+            elif opcode == 0x17: # tStr
+                if bv <= 70:
+                    strg, newpos = unpack_string_update_pos(
+                                        data, pos+1, bk.encoding, lenlen=1)
+                else:
+                    strg, newpos = unpack_unicode_update_pos(
+                                        data, pos+1, lenlen=1)
+                sz = newpos - pos
+                if blah: print("   sz=%d strg=%r" % (sz, strg), file=bk.logfile)
+                text = '"' + strg.replace('"', '""') + '"'
+                spush(Operand(oSTRG, None, LEAF_RANK, text))
+            elif opcode == 0x18: # tExtended
+                # new with BIFF 8
+                assert bv >= 80
+                # not in OOo docs, don't even know how to determine its length
+                raise FormulaError("tExtended token not implemented")
+            elif opcode == 0x19: # tAttr
+                subop, nc = unpack("<BH", data[pos+1:pos+4])
+                subname = tAttrNames.get(subop, "??Unknown??")
+                if subop == 0x04: # Choose
+                    sz = nc * 2 + 6
+                elif subop == 0x10: # Sum (single arg)
+                    sz = 4
+                    if blah: print("tAttrSum", stack, file=bk.logfile)
+                    assert len(stack) >= 1
+                    aop = stack[-1]
+                    otext = 'SUM(%s)' % aop.text
+                    stack[-1] = Operand(oNUM, None, FUNC_RANK, otext)
+                else:
+                    sz = 4
+                if blah:
+                    print("   subop=%02xh subname=t%s sz=%d nc=%02xh"
+                        % (subop, subname, sz, nc), file=bk.logfile)
+            elif 0x1A <= opcode <= 0x1B: # tSheet, tEndSheet
+                assert bv < 50
+                raise FormulaError("tSheet & tEndsheet tokens not implemented")
+            elif 0x1C <= opcode <= 0x1F: # tErr, tBool, tInt, tNum
+                inx = opcode - 0x1C
+                nb = [1, 1, 2, 8][inx]
+                kind = [oERR, oBOOL, oNUM, oNUM][inx]
+                value, = unpack("<" + "BBHd"[inx], data[pos+1:pos+1+nb])
+                if inx == 2: # tInt
+                    value = float(value)
+                    text = str(value)
+                elif inx == 3: # tNum
+                    text = str(value)
+                elif inx == 1: # tBool
+                    text = ('FALSE', 'TRUE')[value]
+                else:
+                    text = '"' +error_text_from_code[value] + '"'
+                spush(Operand(kind, None, LEAF_RANK, text))
+            else:
+                raise FormulaError("Unhandled opcode: 0x%02x" % opcode)
+            if sz <= 0:
+                raise FormulaError("Size not set for opcode 0x%02x" % opcode)
+            pos += sz
+            continue
+        if opcode == 0x00: # tArray
+            spush(unk_opnd)
+        elif opcode == 0x01: # tFunc
+            nb = 1 + int(bv >= 40)
+            funcx = unpack("<" + " BH"[nb], data[pos+1:pos+1+nb])[0]
+            func_attrs = func_defs.get(funcx, None)
+            if not func_attrs:
+                print("*** formula/tFunc unknown FuncID:%d" % funcx, file=bk.logfile)
+                spush(unk_opnd)
+            else:
+                func_name, nargs = func_attrs[:2]
+                if blah:
+                    print("    FuncID=%d name=%s nargs=%d"
+                          % (funcx, func_name, nargs), file=bk.logfile)
+                assert len(stack) >= nargs
+                if nargs:
+                    argtext = listsep.join(arg.text for arg in stack[-nargs:])
+                    otext = "%s(%s)" % (func_name, argtext)
+                    del stack[-nargs:]
+                else:
+                    otext = func_name + "()"
+                res = Operand(oUNK, None, FUNC_RANK, otext)
+                spush(res)
+        elif opcode == 0x02: #tFuncVar
+            nb = 1 + int(bv >= 40)
+            nargs, funcx = unpack("<B" + " BH"[nb], data[pos+1:pos+2+nb])
+            prompt, nargs = divmod(nargs, 128)
+            macro, funcx = divmod(funcx, 32768)
+            if blah:
+                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("*** formula/tFuncVar unknown FuncID:%d"
+                      % funcx, file=bk.logfile)
+                spush(unk_opnd)
+            else:
+                func_name, minargs, maxargs = func_attrs[:3]
+                if blah:
+                    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:])
+                otext = "%s(%s)" % (func_name, argtext)
+                res = Operand(oUNK, None, FUNC_RANK, otext)
+                del stack[-nargs:]
+                spush(res)
+        elif opcode == 0x03: #tName
+            tgtnamex = unpack("<H", data[pos+1:pos+3])[0] - 1
+            # Only change with BIFF version is number of trailing UNUSED bytes!
+            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("    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("  ", res, file=bk.logfile)
+            rowx, colx, row_rel, col_rel = res
+            is_rel = row_rel or col_rel
+            if is_rel:
+                okind = oREL
+            else:
+                okind = oREF
+            otext = cellnamerel(rowx, colx, row_rel, col_rel, browx, bcolx, r1c1)
+            res = Operand(okind, None, LEAF_RANK, otext)
+            spush(res)
+        elif opcode == 0x05: # tArea
+            res1, res2 = get_cell_range_addr(
+                            data, pos+1, bv, reldelta, browx, bcolx)
+            if blah: print("  ", res1, res2, file=bk.logfile)
+            rowx1, colx1, row_rel1, col_rel1 = res1
+            rowx2, colx2, row_rel2, col_rel2 = res2
+            coords = (rowx1, rowx2+1, colx1, colx2+1)
+            relflags = (row_rel1, row_rel2, col_rel1, col_rel2)
+            if sum(relflags):  # relative
+                okind = oREL
+            else:
+                okind = oREF
+            if blah: print("   ", coords, relflags, file=bk.logfile)
+            otext = rangename2drel(coords, relflags, browx, bcolx, r1c1)
+            res = Operand(okind, None, LEAF_RANK, otext)
+            spush(res)
+        elif opcode == 0x06: # tMemArea
+            not_in_name_formula(op, oname)
+        elif opcode == 0x09: # tMemFunc
+            nb = unpack("<H", data[pos+1:pos+3])[0]
+            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("   ", res, file=bk.logfile)
+            rowx, colx, row_rel, col_rel = res
+            is_rel = row_rel or col_rel
+            if is_rel:
+                okind = oREL
+            else:
+                okind = oREF
+            otext = cellnamerel(rowx, colx, row_rel, col_rel, browx, bcolx, r1c1)
+            res = Operand(okind, None, LEAF_RANK, otext)
+            spush(res)
+        elif opcode == 0x0D: #tAreaN
+            # res = get_cell_range_addr(data, pos+1, bv, reldelta, browx, bcolx)
+            # # note *ALL* tAreaN usage has signed offset for relative addresses
+            # any_rel = 1
+            # if blah: print >> bk.logfile, "   ", res
+            res1, res2 = get_cell_range_addr(
+                            data, pos+1, bv, reldelta, browx, bcolx)
+            if blah: print("  ", res1, res2, file=bk.logfile)
+            rowx1, colx1, row_rel1, col_rel1 = res1
+            rowx2, colx2, row_rel2, col_rel2 = res2
+            coords = (rowx1, rowx2+1, colx1, colx2+1)
+            relflags = (row_rel1, row_rel2, col_rel1, col_rel2)
+            if sum(relflags):  # relative
+                okind = oREL
+            else:
+                okind = oREF
+            if blah: print("   ", coords, relflags, file=bk.logfile)
+            otext = rangename2drel(coords, relflags, browx, bcolx, r1c1)
+            res = Operand(okind, None, LEAF_RANK, otext)
+            spush(res)
+        elif opcode == 0x1A: # tRef3d
+            if bv >= 80:
+                res = get_cell_addr(data, pos+3, bv, reldelta, browx, bcolx)
+                refx = unpack("<H", data[pos+1:pos+3])[0]
+                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("<hxxxxxxxxhh", data[pos+1:pos+15])
+                if blah:
+                    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
+            is_rel = row_rel or col_rel
+            any_rel = any_rel or is_rel
+            coords = (shx1, shx2+1, rowx, rowx+1, colx, colx+1)
+            any_err |= shx1 < -1
+            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)
+                ref3d = Ref3D(coords + relflags)
+                res.kind = oREL
+                res.text = rangename3drel(bk, ref3d, browx, bcolx, r1c1)
+            else:
+                ref3d = Ref3D(coords)
+                res.kind = oREF
+                res.text = rangename3d(bk, ref3d)
+            res.rank = LEAF_RANK
+            res.value = None
+            spush(res)
+        elif opcode == 0x1B: # tArea3d
+            if bv >= 80:
+                res1, res2 = get_cell_range_addr(data, pos+3, bv, reldelta)
+                refx = unpack("<H", data[pos+1:pos+3])[0]
+                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("<hxxxxxxxxhh", data[pos+1:pos+15])
+                if blah:
+                    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
+            rowx1, colx1, row_rel1, col_rel1 = res1
+            rowx2, colx2, row_rel2, col_rel2 = res2
+            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("   ", coords, file=bk.logfile)
+            res = Operand(oUNK, None)
+            if is_rel:
+                relflags = (0, 0, row_rel1, row_rel2, col_rel1, col_rel2)
+                ref3d = Ref3D(coords + relflags)
+                res.kind = oREL
+                res.text = rangename3drel(bk, ref3d, browx, bcolx, r1c1)
+            else:
+                ref3d = Ref3D(coords)
+                res.kind = oREF
+                res.text = rangename3d(bk, ref3d)
+            res.rank = LEAF_RANK
+            spush(res)
+        elif opcode == 0x19: # tNameX
+            dodgy = 0
+            res = Operand(oUNK, None)
+            if bv >= 80:
+                refx, tgtnamex = unpack("<HH", data[pos+1:pos+5])
+                tgtnamex -= 1
+                origrefx = refx
+            else:
+                refx, tgtnamex = unpack("<hxxxxxxxxH", data[pos+1:pos+13])
+                tgtnamex -= 1
+                origrefx = refx
+                if refx > 0:
+                    refx -= 1
+                elif refx < 0:
+                    refx = -refx - 1
+                else:
+                    dodgy = 1
+            if blah:
+                print("   origrefx=%d refx=%d tgtnamex=%d dodgy=%d"
+                    % (origrefx, refx, tgtnamex, dodgy), file=bk.logfile)
+            # if tgtnamex == namex:
+            #     if blah: print >> bk.logfile, "!!!! Self-referential !!!!"
+            #     dodgy = any_err = 1
+            if not dodgy:
+                if bv >= 80:
+                    shx1, shx2 = get_externsheet_local_range(bk, refx, blah)
+                elif origrefx > 0:
+                    shx1, shx2 = (-4, -4) # external ref
+                else:
+                    exty = bk._externsheet_type_b57[refx]
+                    if exty == 4: # non-specific sheet in own doc't
+                        shx1, shx2 = (-1, -1) # internal, any sheet
+                    else:
+                        shx1, shx2 = (-666, -666)
+            okind = oUNK
+            ovalue = None
+            if shx1 == -5: # addin func name
+                okind = oSTRG
+                ovalue = bk.addin_func_names[tgtnamex]
+                otext = '"' + ovalue.replace('"', '""') + '"'
+            elif dodgy or shx1 < -1:
+                otext = "<<Name #%d in external(?) file #%d>>" \
+                        % (tgtnamex, origrefx)
+            else:
+                tgtobj = bk.name_obj_list[tgtnamex]
+                if tgtobj.scope == -1:
+                    otext = tgtobj.name
+                else:
+                    otext = "%s!%s" \
+                            % (bk._sheet_names[tgtobj.scope], tgtobj.name)
+                if blah:
+                    print("    tNameX: setting text to", repr(res.text), file=bk.logfile)
+            res = Operand(okind, ovalue, LEAF_RANK, otext)
+            spush(res)
+        elif opcode in error_opcodes:
+            any_err = 1
+            spush(error_opnd)
+        else:
+            if blah:
+                print("FORMULA: /// Not handled yet: t" + oname, file=bk.logfile)
+            any_err = 1
+        if sz <= 0:
+            raise FormulaError("Fatal: token size is not positive")
+        pos += sz
+    any_rel = not not any_rel
+    if blah:
+        print("End of formula. level=%d any_rel=%d any_err=%d stack=%r" %
+            (level, not not any_rel, any_err, stack), file=bk.logfile)
+        if len(stack) >= 2:
+            print("*** Stack has unprocessed args", file=bk.logfile)
+        print(file=bk.logfile)
+
+    if len(stack) != 1:
+        result = None
+    else:
+        result = stack[0].text
+    return result
+
+#### under deconstruction ###
+def dump_formula(bk, data, fmlalen, bv, reldelta, blah=0, isname=0):
+    if blah:
+        print("dump_formula", fmlalen, bv, len(data), file=bk.logfile)
+        hex_char_dump(data, 0, fmlalen, fout=bk.logfile)
+    assert bv >= 80 #### this function needs updating ####
+    sztab = szdict[bv]
+    pos = 0
+    stack = []
+    any_rel = 0
+    any_err = 0
+    spush = stack.append
+    while 0 <= pos < fmlalen:
+        op = BYTES_ORD(data[pos])
+        opcode = op & 0x1f
+        optype = (op & 0x60) >> 5
+        if optype:
+            opx = opcode + 32
+        else:
+            opx = opcode
+        oname = onames[opx] # + [" RVA"][optype]
+
+        sz = sztab[opx]
+        if blah:
+            print("Pos:%d Op:0x%02x Name:t%s Sz:%d opcode:%02xh optype:%02xh"
+                % (pos, op, oname, sz, opcode, optype), file=bk.logfile)
+        if not optype:
+            if 0x01 <= opcode <= 0x02: # tExp, tTbl
+                # reference to a shared formula or table record
+                rowx, colx = unpack("<HH", data[pos+1:pos+5])
+                if blah: print("  ", (rowx, colx), file=bk.logfile)
+            elif opcode == 0x10: # tList
+                if blah: print("tList pre", stack, file=bk.logfile)
+                assert len(stack) >= 2
+                bop = stack.pop()
+                aop = stack.pop()
+                spush(aop + bop)
+                if blah: print("tlist post", stack, file=bk.logfile)
+            elif opcode == 0x11: # tRange
+                if blah: print("tRange pre", stack, file=bk.logfile)
+                assert len(stack) >= 2
+                bop = stack.pop()
+                aop = stack.pop()
+                assert len(aop) == 1
+                assert len(bop) == 1
+                result = do_box_funcs(tRangeFuncs, aop[0], bop[0])
+                spush(result)
+                if blah: print("tRange post", stack, file=bk.logfile)
+            elif opcode == 0x0F: # tIsect
+                if blah: print("tIsect pre", stack, file=bk.logfile)
+                assert len(stack) >= 2
+                bop = stack.pop()
+                aop = stack.pop()
+                assert len(aop) == 1
+                assert len(bop) == 1
+                result = do_box_funcs(tIsectFuncs, aop[0], bop[0])
+                spush(result)
+                if blah: print("tIsect post", stack, file=bk.logfile)
+            elif opcode == 0x19: # tAttr
+                subop, nc = unpack("<BH", data[pos+1:pos+4])
+                subname = tAttrNames.get(subop, "??Unknown??")
+                if subop == 0x04: # Choose
+                    sz = nc * 2 + 6
+                else:
+                    sz = 4
+                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 = 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("   sz=%d strg=%r" % (sz, strg), file=bk.logfile)
+            else:
+                if sz <= 0:
+                    print("**** Dud size; exiting ****", file=bk.logfile)
+                    return
+            pos += sz
+            continue
+        if opcode == 0x00: # tArray
+            pass
+        elif opcode == 0x01: # tFunc
+            nb = 1 + int(bv >= 40)
+            funcx = unpack("<" + " BH"[nb], data[pos+1:pos+1+nb])
+            if blah: print("   FuncID=%d" % funcx, file=bk.logfile)
+        elif opcode == 0x02: #tFuncVar
+            nb = 1 + int(bv >= 40)
+            nargs, funcx = unpack("<B" + " BH"[nb], data[pos+1:pos+2+nb])
+            prompt, nargs = divmod(nargs, 128)
+            macro, funcx = divmod(funcx, 32768)
+            if blah: print("   FuncID=%d nargs=%d macro=%d prompt=%d" % (funcx, nargs, macro, prompt), file=bk.logfile)
+        elif opcode == 0x03: #tName
+            namex = unpack("<H", data[pos+1:pos+3])
+            # Only change with BIFF version is the number of trailing UNUSED bytes!!!
+            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("  ", res, file=bk.logfile)
+        elif opcode == 0x05: # tArea
+            res = get_cell_range_addr(data, pos+1, bv, reldelta)
+            if blah: print("  ", res, file=bk.logfile)
+        elif opcode == 0x09: # tMemFunc
+            nb = unpack("<H", data[pos+1:pos+3])[0]
+            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("   ", 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("   ", res, file=bk.logfile)
+        elif opcode == 0x1A: # tRef3d
+            refx = unpack("<H", data[pos+1:pos+3])[0]
+            res = get_cell_addr(data, pos+3, bv, reldelta)
+            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("   ", coords, file=bk.logfile)
+            if optype == 1: spush([coords])
+        elif opcode == 0x1B: # tArea3d
+            refx = unpack("<H", data[pos+1:pos+3])[0]
+            res1, res2 = get_cell_range_addr(data, pos+3, bv, reldelta)
+            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("   ", coords, file=bk.logfile)
+            if optype == 1: spush([coords])
+        elif opcode == 0x19: # tNameX
+            refx, namex = unpack("<HH", data[pos+1:pos+5])
+            if blah: print("   refx=%d namex=%d" % (refx, namex), file=bk.logfile)
+        elif opcode in error_opcodes:
+            any_err = 1
+        else:
+            if blah: print("FORMULA: /// Not handled yet: t" + oname, file=bk.logfile)
+            any_err = 1
+        if sz <= 0:
+            print("**** Dud size; exiting ****", file=bk.logfile)
+            return
+        pos += sz
+    if blah:
+        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("*** Stack has unprocessed args", file=bk.logfile)
+
+# === Some helper functions for displaying cell references ===
+
+# I'm aware of only one possibility of a sheet-relative component in
+# a reference: a 2D reference located in the "current sheet".
+# xlrd stores this internally with bounds of (0, 1, ...) and
+# relative flags of (1, 1, ...). These functions display the
+# sheet component as empty, just like Excel etc.
+
+def rownamerel(rowx, rowxrel, browx=None, r1c1=0):
+    # if no base rowx is provided, we have to return r1c1
+    if browx is None:
+        r1c1 = True
+    if not rowxrel:
+        if r1c1:
+            return "R%d" % (rowx+1)
+        return "$%d" % (rowx+1)
+    if r1c1:
+        if rowx:
+            return "R[%d]" % rowx
+        return "R"
+    return "%d" % ((browx + rowx) % 65536 + 1)
+
+def colnamerel(colx, colxrel, bcolx=None, r1c1=0):
+    # if no base colx is provided, we have to return r1c1
+    if bcolx is None:
+        r1c1 = True
+    if not colxrel:
+        if r1c1:
+            return "C%d" % (colx + 1)
+        return "$" + colname(colx)
+    if r1c1:
+        if colx:
+            return "C[%d]" % colx
+        return "C"
+    return colname((bcolx + colx) % 256)
+
+def cellname(rowx, colx):
+    """Utility function: ``(5, 7)`` => ``'H6'``"""
+    return "%s%d" % (colname(colx), rowx+1)
+
+def cellnameabs(rowx, colx, r1c1=0):
+    """Utility function: ``(5, 7)`` => ``'$H$6'``"""
+    if r1c1:
+        return "R%dC%d" % (rowx+1, colx+1)
+    return "$%s$%d" % (colname(colx), rowx+1)
+
+def cellnamerel(rowx, colx, rowxrel, colxrel, browx=None, bcolx=None, r1c1=0):
+    if not rowxrel and not colxrel:
+        return cellnameabs(rowx, colx, r1c1)
+    if (rowxrel and browx is None) or (colxrel and bcolx is None):
+        # must flip the whole cell into R1C1 mode
+        r1c1 = True
+    c = colnamerel(colx, colxrel, bcolx, r1c1)
+    r = rownamerel(rowx, rowxrel, browx, r1c1)
+    if r1c1:
+        return r + c
+    return c + r
+
+def colname(colx):
+    """Utility function: ``7`` => ``'H'``, ``27`` => ``'AB'``"""
+    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+    if colx <= 25:
+        return alphabet[colx]
+    else:
+        xdiv26, xmod26 = divmod(colx, 26)
+        return alphabet[xdiv26 - 1] + alphabet[xmod26]
+
+def rangename2d(rlo, rhi, clo, chi, r1c1=0):
+    """ ``(5, 20, 7, 10)`` => ``'$H$6:$J$20'`` """
+    if r1c1:
+        return
+    if rhi == rlo+1 and chi == clo+1:
+        return cellnameabs(rlo, clo, r1c1)
+    return "%s:%s" % (cellnameabs(rlo, clo, r1c1), cellnameabs(rhi-1, chi-1, r1c1))
+
+def rangename2drel(rlo_rhi_clo_chi, rlorel_rhirel_clorel_chirel, browx=None, bcolx=None, r1c1=0):
+    rlo, rhi, clo, chi = rlo_rhi_clo_chi
+    rlorel, rhirel, clorel, chirel = rlorel_rhirel_clorel_chirel
+    if (rlorel or rhirel) and browx is None:
+        r1c1 = True
+    if (clorel or chirel) and bcolx is None:
+        r1c1 = True
+    return "%s:%s" % (
+        cellnamerel(rlo,   clo,   rlorel, clorel, browx, bcolx, r1c1),
+        cellnamerel(rhi-1, chi-1, rhirel, chirel, browx, bcolx, r1c1),
+    )
+
+
+def rangename3d(book, ref3d):
+    """
+    Utility function:
+    ``Ref3D(1, 4, 5, 20, 7, 10)`` =>
+    ``'Sheet2:Sheet3!$H$6:$J$20'``
+    (assuming Excel's default sheetnames)
+    """
+    coords = ref3d.coords
+    return "%s!%s" % (
+        sheetrange(book, *coords[:2]),
+        rangename2d(*coords[2:6]))
+
+def rangename3drel(book, ref3d, browx=None, bcolx=None, r1c1=0):
+    """
+    Utility function:
+    ``Ref3D(coords=(0, 1, -32, -22, -13, 13), relflags=(0, 0, 1, 1, 1, 1))``
+
+    In R1C1 mode => ``'Sheet1!R[-32]C[-13]:R[-23]C[12]'``
+
+    In A1 mode => depends on base cell ``(browx, bcolx)``
+    """
+    coords = ref3d.coords
+    relflags = ref3d.relflags
+    shdesc = sheetrangerel(book, coords[:2], relflags[:2])
+    rngdesc = rangename2drel(coords[2:6], relflags[2:6], browx, bcolx, r1c1)
+    if not shdesc:
+        return rngdesc
+    return "%s!%s" % (shdesc, rngdesc)
+
+def quotedsheetname(shnames, shx):
+    if shx >= 0:
+        shname = shnames[shx]
+    else:
+        shname = {
+            -1: "?internal; any sheet?",
+            -2: "internal; deleted sheet",
+            -3: "internal; macro sheet",
+            -4: "<<external>>",
+        }.get(shx, "?error %d?" % shx)
+    if "'" in shname:
+        return "'" + shname.replace("'", "''") + "'"
+    if " " in shname:
+        return "'" + shname + "'"
+    return shname
+
+def sheetrange(book, slo, shi):
+    shnames = book.sheet_names()
+    shdesc = quotedsheetname(shnames, slo)
+    if slo != shi-1:
+        shdesc += ":" + quotedsheetname(shnames, shi-1)
+    return shdesc
+
+def sheetrangerel(book, srange, srangerel):
+    slo, shi = srange
+    slorel, shirel = srangerel
+    if not slorel and not shirel:
+        return sheetrange(book, slo, shi)
+    assert (slo == 0 == shi-1) and slorel and shirel
+    return ""
+
+# ==============================================================

+ 1 - 0
desktop/core/ext-py/xlrd-2.0.1/xlrd/info.py

@@ -0,0 +1 @@
+__version__ = __VERSION__ = "2.0.1"

+ 2490 - 0
desktop/core/ext-py/xlrd-2.0.1/xlrd/sheet.py

@@ -0,0 +1,2490 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2005-2013 Stephen John Machin, Lingfo Pty Ltd
+# This module is part of the xlrd package, which is released under a
+# BSD-style licence.
+
+from __future__ import print_function
+
+from array import array
+from struct import calcsize, unpack
+
+from .biffh import *
+from .formatting import Format, nearest_colour_index
+from .formula import (
+    FMLA_TYPE_CELL, FMLA_TYPE_SHARED, decompile_formula, dump_formula,
+    rangename2d,
+)
+from .timemachine import *
+
+DEBUG = 0
+OBJ_MSO_DEBUG = 0
+
+_WINDOW2_options = (
+    # Attribute names and initial values to use in case
+    # a WINDOW2 record is not written.
+    ("show_formulas", 0),
+    ("show_grid_lines", 1),
+    ("show_sheet_headers", 1),
+    ("panes_are_frozen", 0),
+    ("show_zero_values", 1),
+    ("automatic_grid_line_colour", 1),
+    ("columns_from_right_to_left", 0),
+    ("show_outline_symbols", 1),
+    ("remove_splits_if_pane_freeze_is_removed", 0),
+    # Multiple sheets can be selected, but only one can be active
+    # (hold down Ctrl and click multiple tabs in the file in OOo)
+    ("sheet_selected", 0),
+    # "sheet_visible" should really be called "sheet_active"
+    # and is 1 when this sheet is the sheet displayed when the file
+    # is open. More than likely only one sheet should ever be set as
+    # visible.
+    # This would correspond to the Book's sheet_active attribute, but
+    # that doesn't exist as WINDOW1 records aren't currently processed.
+    # The real thing is the visibility attribute from the BOUNDSHEET record.
+    ("sheet_visible", 0),
+    ("show_in_page_break_preview", 0),
+)
+
+
+
+class Sheet(BaseObject):
+    """
+    Contains the data for one worksheet.
+
+    In the cell access functions, ``rowx`` is a row index, counting from
+    zero, and ``colx`` is a column index, counting from zero.
+    Negative values for row/column indexes and slice positions are supported in
+    the expected fashion.
+
+    For information about cell types and cell values, refer to the documentation
+    of the :class:`Cell` class.
+
+    .. warning::
+
+      You don't instantiate this class yourself. You access :class:`Sheet`
+      objects via the :class:`~xlrd.book.Book` object that
+      was returned when you called :func:`xlrd.open_workbook`.
+    """
+
+    #: Name of sheet.
+    name = ''
+
+    #: A reference to the :class:`~xlrd.book.Book` object to which this sheet
+    #: belongs.
+    #:
+    #: Example usage: ``some_sheet.book.datemode``
+    book = None
+
+    #: Number of rows in sheet. A row index is in ``range(thesheet.nrows)``.
+    nrows = 0
+
+    #: Nominal number of columns in sheet. It is one more than the maximum
+    #: column index found, ignoring trailing empty cells.
+    #: See also the ``ragged_rows`` parameter to :func:`~xlrd.open_workbook`
+    #: and :meth:`~xlrd.sheet.Sheet.row_len`.
+    ncols = 0
+
+
+    #: The map from a column index to a :class:`Colinfo` object. Often there is
+    #: an entry in ``COLINFO`` records for all column indexes in ``range(257)``.
+    #:
+    #: .. note::
+    #:   xlrd ignores the entry for the non-existent
+    #:   257th column.
+    #:
+    #: On the other hand, there may be no entry for unused columns.
+    #:
+    #: .. versionadded:: 0.6.1
+    #:
+    #: Populated only if ``open_workbook(..., formatting_info=True)``
+    colinfo_map = {}
+
+    #: The map from a row index to a :class:`Rowinfo` object.
+    #:
+    #: ..note::
+    #:   It is possible to have missing entries -- at least one source of
+    #:   XLS files doesn't bother writing ``ROW`` records.
+    #:
+    #: .. versionadded:: 0.6.1
+    #:
+    #: Populated only if ``open_workbook(..., formatting_info=True)``
+    rowinfo_map = {}
+
+    #: List of address ranges of cells containing column labels.
+    #: These are set up in Excel by Insert > Name > Labels > Columns.
+    #:
+    #: .. versionadded:: 0.6.0
+    #:
+    #: How to deconstruct the list:
+    #:
+    #: .. code-block:: python
+    #:
+    #:   for crange in thesheet.col_label_ranges:
+    #:       rlo, rhi, clo, chi = crange
+    #:       for rx in xrange(rlo, rhi):
+    #:           for cx in xrange(clo, chi):
+    #:               print "Column label at (rowx=%d, colx=%d) is %r" \
+    #:                   (rx, cx, thesheet.cell_value(rx, cx))
+    col_label_ranges = []
+
+    #: List of address ranges of cells containing row labels.
+    #: For more details, see :attr:`col_label_ranges`.
+    #:
+    #: .. versionadded:: 0.6.0
+    row_label_ranges = []
+
+    #: List of address ranges of cells which have been merged.
+    #: These are set up in Excel by Format > Cells > Alignment, then ticking
+    #: the "Merge cells" box.
+    #:
+    #: .. note::
+    #:   The upper limits are exclusive: i.e. ``[2, 3, 7, 9]`` only
+    #:   spans two cells.
+    #:
+    #: .. note:: Extracted only if ``open_workbook(..., formatting_info=True)``
+    #:
+    #: .. versionadded:: 0.6.1
+    #:
+    #: How to deconstruct the list:
+    #:
+    #: .. code-block:: python
+    #:
+    #:   for crange in thesheet.merged_cells:
+    #:       rlo, rhi, clo, chi = crange
+    #:       for rowx in xrange(rlo, rhi):
+    #:           for colx in xrange(clo, chi):
+    #:               # cell (rlo, clo) (the top left one) will carry the data
+    #:               # and formatting info; the remainder will be recorded as
+    #:               # blank cells, but a renderer will apply the formatting info
+    #:               # for the top left cell (e.g. border, pattern) to all cells in
+    #:               # the range.
+    merged_cells = []
+
+    #: Mapping of ``(rowx, colx)`` to list of ``(offset, font_index)`` tuples.
+    #: The offset defines where in the string the font begins to be used.
+    #: Offsets are expected to be in ascending order.
+    #: If the first offset is not zero, the meaning is that the cell's ``XF``'s
+    #: font should be used from offset 0.
+    #:
+    #: This is a sparse mapping. There is no entry for cells that are not
+    #: formatted with rich text.
+    #:
+    #: How to use:
+    #:
+    #: .. code-block:: python
+    #:
+    #:   runlist = thesheet.rich_text_runlist_map.get((rowx, colx))
+    #:   if runlist:
+    #:       for offset, font_index in runlist:
+    #:           # do work here.
+    #:           pass
+    #:
+    #: .. versionadded:: 0.7.2
+    #:
+    #: Populated only if ``open_workbook(..., formatting_info=True)``
+    rich_text_runlist_map = {}
+
+    #: Default column width from ``DEFCOLWIDTH`` record, else ``None``.
+    #: From the OOo docs:
+    #:
+    #:   Column width in characters, using the width of the zero character
+    #:   from default font (first FONT record in the file). Excel adds some
+    #:   extra space to the default width, depending on the default font and
+    #:   default font size. The algorithm how to exactly calculate the resulting
+    #:   column width is not known.
+    #:   Example: The default width of 8 set in this record results in a column
+    #:   width of 8.43 using Arial font with a size of 10 points.
+    #:
+    #: For the default hierarchy, refer to the :class:`Colinfo` class.
+    #:
+    #: .. versionadded:: 0.6.1
+    defcolwidth = None
+
+    #: Default column width from ``STANDARDWIDTH`` record, else ``None``.
+    #:
+    #: From the OOo docs:
+    #:
+    #:   Default width of the columns in 1/256 of the width of the zero
+    #:   character, using default font (first FONT record in the file).
+    #:
+    #: For the default hierarchy, refer to the :class:`Colinfo` class.
+    #:
+    #: .. versionadded:: 0.6.1
+    standardwidth = None
+
+    #: Default value to be used for a row if there is
+    #: no ``ROW`` record for that row.
+    #: From the *optional* ``DEFAULTROWHEIGHT`` record.
+    default_row_height = None
+
+    #: Default value to be used for a row if there is
+    #: no ``ROW`` record for that row.
+    #: From the *optional* ``DEFAULTROWHEIGHT`` record.
+    default_row_height_mismatch = None
+
+    #: Default value to be used for a row if there is
+    #: no ``ROW`` record for that row.
+    #: From the *optional* ``DEFAULTROWHEIGHT`` record.
+    default_row_hidden = None
+
+    #: Default value to be used for a row if there is
+    #: no ``ROW`` record for that row.
+    #: From the *optional* ``DEFAULTROWHEIGHT`` record.
+    default_additional_space_above = None
+
+    #: Default value to be used for a row if there is
+    #: no ``ROW`` record for that row.
+    #: From the *optional* ``DEFAULTROWHEIGHT`` record.
+    default_additional_space_below = None
+
+    #: Visibility of the sheet:
+    #: ::
+    #:
+    #:   0 = visible
+    #:   1 = hidden (can be unhidden by user -- Format -> Sheet -> Unhide)
+    #:   2 = "very hidden" (can be unhidden only by VBA macro).
+    visibility = 0
+
+    #: A 256-element tuple corresponding to the contents of the GCW record for
+    #: this sheet. If no such record, treat as all bits zero.
+    #: Applies to BIFF4-7 only. See docs of the :class:`Colinfo` class for
+    #: discussion.
+    gcw = (0, ) * 256
+
+    #: A list of :class:`Hyperlink` objects corresponding to ``HLINK`` records
+    #: found in the worksheet.
+    #:
+    #: .. versionadded:: 0.7.2
+    hyperlink_list = []
+
+    #: A sparse mapping from ``(rowx, colx)`` to an item in
+    #: :attr:`~xlrd.sheet.Sheet.hyperlink_list`.
+    #: Cells not covered by a hyperlink are not mapped.
+    #: It is possible using the Excel UI to set up a hyperlink that
+    #: covers a larger-than-1x1 rectangle of cells.
+    #: Hyperlink rectangles may overlap (Excel doesn't check).
+    #: When a multiply-covered cell is clicked on, the hyperlink that is
+    #: activated
+    #: (and the one that is mapped here) is the last in
+    #: :attr:`~xlrd.sheet.Sheet.hyperlink_list`.
+    #:
+    #: .. versionadded:: 0.7.2
+    hyperlink_map = {}
+
+    #: A sparse mapping from ``(rowx, colx)`` to a :class:`Note` object.
+    #: Cells not containing a note ("comment") are not mapped.
+    #:
+    #: .. versionadded:: 0.7.2
+    cell_note_map = {}
+
+    #: Number of columns in left pane (frozen panes; for split panes, see
+    #: comments in code)
+    vert_split_pos = 0
+
+    #: Number of rows in top pane (frozen panes; for split panes, see comments
+    #: in code)
+    horz_split_pos = 0
+
+    #: Index of first visible row in bottom frozen/split pane
+    horz_split_first_visible = 0
+
+    #: Index of first visible column in right frozen/split pane
+    vert_split_first_visible = 0
+
+    #: Frozen panes: ignore it. Split panes: explanation and diagrams in
+    #: OOo docs.
+    split_active_pane = 0
+
+    #: Boolean specifying if a ``PANE`` record was present, ignore unless you're
+    #: ``xlutils.copy``
+    has_pane_record = 0
+
+    #: A list of the horizontal page breaks in this sheet.
+    #: Breaks are tuples in the form
+    #: ``(index of row after break, start col index, end col index)``.
+    #:
+    #: Populated only if ``open_workbook(..., formatting_info=True)``
+    #:
+    #: .. versionadded:: 0.7.2
+    horizontal_page_breaks = []
+
+    #: A list of the vertical page breaks in this sheet.
+    #: Breaks are tuples in the form
+    #: ``(index of col after break, start row index, end row index)``.
+    #:
+    #: Populated only if ``open_workbook(..., formatting_info=True)``
+    #:
+    #: .. versionadded:: 0.7.2
+    vertical_page_breaks = []
+
+    def __init__(self, book, position, name, number):
+        self.book = book
+        self.biff_version = book.biff_version
+        self._position = position
+        self.logfile = book.logfile
+        self.bt = array('B', [XL_CELL_EMPTY])
+        self.bf = array('h', [-1])
+        self.name = name
+        self.number = number
+        self.verbosity = book.verbosity
+        self.formatting_info = book.formatting_info
+        self.ragged_rows = book.ragged_rows
+        if self.ragged_rows:
+            self.put_cell = self.put_cell_ragged
+        else:
+            self.put_cell = self.put_cell_unragged
+        self._xf_index_to_xl_type_map = book._xf_index_to_xl_type_map
+        self.nrows = 0 # actual, including possibly empty cells
+        self.ncols = 0
+        self._maxdatarowx = -1 # highest rowx containing a non-empty cell
+        self._maxdatacolx = -1 # highest colx containing a non-empty cell
+        self._dimnrows = 0 # as per DIMENSIONS record
+        self._dimncols = 0
+        self._cell_values = []
+        self._cell_types = []
+        self._cell_xf_indexes = []
+        self.defcolwidth = None
+        self.standardwidth = None
+        self.default_row_height = None
+        self.default_row_height_mismatch = 0
+        self.default_row_hidden = 0
+        self.default_additional_space_above = 0
+        self.default_additional_space_below = 0
+        self.colinfo_map = {}
+        self.rowinfo_map = {}
+        self.col_label_ranges = []
+        self.row_label_ranges = []
+        self.merged_cells = []
+        self.rich_text_runlist_map = {}
+        self.horizontal_page_breaks = []
+        self.vertical_page_breaks = []
+        self._xf_index_stats = [0, 0, 0, 0]
+        self.visibility = book._sheet_visibility[number] # from BOUNDSHEET record
+        for attr, defval in _WINDOW2_options:
+            setattr(self, attr, defval)
+        self.first_visible_rowx = 0
+        self.first_visible_colx = 0
+        self.gridline_colour_index = 0x40
+        self.gridline_colour_rgb = None # pre-BIFF8
+        self.hyperlink_list = []
+        self.hyperlink_map = {}
+        self.cell_note_map = {}
+
+        # Values calculated by xlrd to predict the mag factors that
+        # will actually be used by Excel to display your worksheet.
+        # Pass these values to xlwt when writing XLS files.
+        # Warning 1: Behaviour of OOo Calc and Gnumeric has been observed to differ from Excel's.
+        # Warning 2: A value of zero means almost exactly what it says. Your sheet will be
+        # displayed as a very tiny speck on the screen. xlwt will reject attempts to set
+        # a mag_factor that is not (10 <= mag_factor <= 400).
+        self.cooked_page_break_preview_mag_factor = 60
+        self.cooked_normal_view_mag_factor = 100
+
+        # Values (if any) actually stored on the XLS file
+        self.cached_page_break_preview_mag_factor = 0  # default (60%), from WINDOW2 record
+        self.cached_normal_view_mag_factor = 0  # default (100%), from WINDOW2 record
+        self.scl_mag_factor = None # from SCL record
+
+        self._ixfe = None # BIFF2 only
+        self._cell_attr_to_xfx = {} # BIFF2.0 only
+
+        if self.biff_version >= 80:
+            self.utter_max_rows = 65536
+        else:
+            self.utter_max_rows = 16384
+        self.utter_max_cols = 256
+
+        self._first_full_rowx = -1
+
+        # self._put_cell_exceptions = 0
+        # self._put_cell_row_widenings = 0
+        # self._put_cell_rows_appended = 0
+        # self._put_cell_cells_appended = 0
+
+    def cell(self, rowx, colx):
+        """
+        :class:`Cell` object in the given row and column.
+        """
+        if self.formatting_info:
+            xfx = self.cell_xf_index(rowx, colx)
+        else:
+            xfx = None
+        return Cell(
+            self._cell_types[rowx][colx],
+            self._cell_values[rowx][colx],
+            xfx,
+        )
+
+    def cell_value(self, rowx, colx):
+        "Value of the cell in the given row and column."
+        return self._cell_values[rowx][colx]
+
+    def cell_type(self, rowx, colx):
+        """
+        Type of the cell in the given row and column.
+
+        Refer to the documentation of the :class:`Cell` class.
+        """
+        return self._cell_types[rowx][colx]
+
+    def cell_xf_index(self, rowx, colx):
+        """
+        XF index of the cell in the given row and column.
+        This is an index into :attr:`~xlrd.book.Book.xf_list`.
+
+        .. versionadded:: 0.6.1
+        """
+        self.req_fmt_info()
+        xfx = self._cell_xf_indexes[rowx][colx]
+        if xfx > -1:
+            self._xf_index_stats[0] += 1
+            return xfx
+        # Check for a row xf_index
+        try:
+            xfx = self.rowinfo_map[rowx].xf_index
+            if xfx > -1:
+                self._xf_index_stats[1] += 1
+                return xfx
+        except KeyError:
+            pass
+        # Check for a column xf_index
+        try:
+            xfx = self.colinfo_map[colx].xf_index
+            if xfx == -1: xfx = 15
+            self._xf_index_stats[2] += 1
+            return xfx
+        except KeyError:
+            # If all else fails, 15 is used as hardwired global default xf_index.
+            self._xf_index_stats[3] += 1
+            return 15
+
+    def row_len(self, rowx):
+        """
+        Returns the effective number of cells in the given row. For use with
+        ``open_workbook(ragged_rows=True)`` which is likely to produce rows
+        with fewer than :attr:`~Sheet.ncols` cells.
+
+        .. versionadded:: 0.7.2
+        """
+        return len(self._cell_values[rowx])
+
+    def row(self, rowx):
+        """
+        Returns a sequence of the :class:`Cell` objects in the given row.
+        """
+        return [
+            self.cell(rowx, colx)
+            for colx in xrange(len(self._cell_values[rowx]))
+        ]
+
+    def __getitem__(self, item):
+        """
+        Takes either rowindex or (rowindex, colindex) as an index,
+        and returns either row or cell respectively.
+        """
+        try:
+            rowix, colix = item
+        except TypeError:
+            # it's not a tuple (or of right size), let's try indexing as is
+            # if this is a problem, let this error propagate back
+            return self.row(item)
+        else:
+            return self.cell(rowix, colix)
+
+    def get_rows(self):
+        "Returns a generator for iterating through each row."
+        return (self.row(index) for index in range(self.nrows))
+
+    # makes `for row in sheet` natural and intuitive
+    __iter__ = get_rows
+
+    def row_types(self, rowx, start_colx=0, end_colx=None):
+        """
+        Returns a slice of the types of the cells in the given row.
+        """
+        if end_colx is None:
+            return self._cell_types[rowx][start_colx:]
+        return self._cell_types[rowx][start_colx:end_colx]
+
+    def row_values(self, rowx, start_colx=0, end_colx=None):
+        """
+        Returns a slice of the values of the cells in the given row.
+        """
+        if end_colx is None:
+            return self._cell_values[rowx][start_colx:]
+        return self._cell_values[rowx][start_colx:end_colx]
+
+    def row_slice(self, rowx, start_colx=0, end_colx=None):
+        """
+        Returns a slice of the :class:`Cell` objects in the given row.
+        """
+        nc = len(self._cell_values[rowx])
+        if start_colx < 0:
+            start_colx += nc
+            if start_colx < 0:
+                start_colx = 0
+        if end_colx is None or end_colx > nc:
+            end_colx = nc
+        elif end_colx < 0:
+            end_colx += nc
+        return [
+            self.cell(rowx, colx)
+            for colx in xrange(start_colx, end_colx)
+        ]
+
+    def col_slice(self, colx, start_rowx=0, end_rowx=None):
+        """
+        Returns a slice of the :class:`Cell` objects in the given column.
+        """
+        nr = self.nrows
+        if start_rowx < 0:
+            start_rowx += nr
+            if start_rowx < 0:
+                start_rowx = 0
+        if end_rowx is None or end_rowx > nr:
+            end_rowx = nr
+        elif end_rowx < 0:
+            end_rowx += nr
+        return [
+            self.cell(rowx, colx)
+            for rowx in xrange(start_rowx, end_rowx)
+        ]
+
+    def col_values(self, colx, start_rowx=0, end_rowx=None):
+        """
+        Returns a slice of the values of the cells in the given column.
+        """
+        nr = self.nrows
+        if start_rowx < 0:
+            start_rowx += nr
+            if start_rowx < 0:
+                start_rowx = 0
+        if end_rowx is None or end_rowx > nr:
+            end_rowx = nr
+        elif end_rowx < 0:
+            end_rowx += nr
+        return [
+            self._cell_values[rowx][colx]
+            for rowx in xrange(start_rowx, end_rowx)
+        ]
+
+    def col_types(self, colx, start_rowx=0, end_rowx=None):
+        """
+        Returns a slice of the types of the cells in the given column.
+        """
+        nr = self.nrows
+        if start_rowx < 0:
+            start_rowx += nr
+            if start_rowx < 0:
+                start_rowx = 0
+        if end_rowx is None or end_rowx > nr:
+            end_rowx = nr
+        elif end_rowx < 0:
+            end_rowx += nr
+        return [
+            self._cell_types[rowx][colx]
+            for rowx in xrange(start_rowx, end_rowx)
+        ]
+
+    col = col_slice
+
+    # === Following methods are used in building the worksheet.
+    # === They are not part of the API.
+
+    def tidy_dimensions(self):
+        if self.verbosity >= 3:
+            fprintf(
+                self.logfile,
+                "tidy_dimensions: nrows=%d ncols=%d \n",
+                self.nrows, self.ncols,
+            )
+        if 1 and self.merged_cells:
+            nr = nc = 0
+            umaxrows = self.utter_max_rows
+            umaxcols = self.utter_max_cols
+            for crange in self.merged_cells:
+                rlo, rhi, clo, chi = crange
+                if not (0 <= rlo < rhi <= umaxrows) or not (0 <= clo < chi <= umaxcols):
+                    fprintf(self.logfile,
+                        "*** WARNING: sheet #%d (%r), MERGEDCELLS bad range %r\n",
+                        self.number, self.name, crange)
+                if rhi > nr: nr = rhi
+                if chi > nc: nc = chi
+            if nc > self.ncols:
+                self.ncols = nc
+                self._first_full_rowx = -2
+            if nr > self.nrows:
+                # we put one empty cell at (nr-1,0) to make sure
+                # we have the right number of rows. The ragged rows
+                # will sort out the rest if needed.
+                self.put_cell(nr-1, 0, XL_CELL_EMPTY, UNICODE_LITERAL(''), -1)
+        if (self.verbosity >= 1 and
+                (self.nrows != self._dimnrows or self.ncols != self._dimncols)):
+            fprintf(
+                self.logfile,
+                "NOTE *** sheet %d (%r): DIMENSIONS R,C = %d,%d should be %d,%d\n",
+                self.number,
+                self.name,
+                self._dimnrows,
+                self._dimncols,
+                self.nrows,
+                self.ncols,
+            )
+        if not self.ragged_rows:
+            # fix ragged rows
+            ncols = self.ncols
+            s_cell_types = self._cell_types
+            s_cell_values = self._cell_values
+            s_cell_xf_indexes = self._cell_xf_indexes
+            s_fmt_info = self.formatting_info
+            # for rowx in xrange(self.nrows):
+            if self._first_full_rowx == -2:
+                ubound = self.nrows
+            else:
+                ubound = self._first_full_rowx
+            for rowx in xrange(ubound):
+                trow = s_cell_types[rowx]
+                rlen = len(trow)
+                nextra = ncols - rlen
+                if nextra > 0:
+                    s_cell_values[rowx][rlen:] = [UNICODE_LITERAL('')] * nextra
+                    trow[rlen:] = self.bt * nextra
+                    if s_fmt_info:
+                        s_cell_xf_indexes[rowx][rlen:] = self.bf * nextra
+
+    def put_cell_ragged(self, rowx, colx, ctype, value, xf_index):
+        if ctype is None:
+            # we have a number, so look up the cell type
+            ctype = self._xf_index_to_xl_type_map[xf_index]
+        assert 0 <= colx < self.utter_max_cols
+        assert 0 <= rowx < self.utter_max_rows
+        fmt_info = self.formatting_info
+
+        try:
+            nr = rowx + 1
+            if self.nrows < nr:
+
+                scta = self._cell_types.append
+                scva = self._cell_values.append
+                scxa = self._cell_xf_indexes.append
+                bt = self.bt
+                bf = self.bf
+                for _unused in xrange(self.nrows, nr):
+                    scta(bt * 0)
+                    scva([])
+                    if fmt_info:
+                        scxa(bf * 0)
+                self.nrows = nr
+
+            types_row = self._cell_types[rowx]
+            values_row = self._cell_values[rowx]
+            if fmt_info:
+                fmt_row = self._cell_xf_indexes[rowx]
+            ltr = len(types_row)
+            if colx >= self.ncols:
+                self.ncols = colx + 1
+            num_empty = colx - ltr
+            if not num_empty:
+                # most common case: colx == previous colx + 1
+                # self._put_cell_cells_appended += 1
+                types_row.append(ctype)
+                values_row.append(value)
+                if fmt_info:
+                    fmt_row.append(xf_index)
+                return
+            if num_empty > 0:
+                num_empty += 1
+                # self._put_cell_row_widenings += 1
+                # types_row.extend(self.bt * num_empty)
+                # values_row.extend([UNICODE_LITERAL('')] * num_empty)
+                # if fmt_info:
+                #     fmt_row.extend(self.bf * num_empty)
+                types_row[ltr:] = self.bt * num_empty
+                values_row[ltr:] = [UNICODE_LITERAL('')] * num_empty
+                if fmt_info:
+                    fmt_row[ltr:] = self.bf * num_empty
+            types_row[colx] = ctype
+            values_row[colx] = value
+            if fmt_info:
+                fmt_row[colx] = xf_index
+        except:
+            print("put_cell", rowx, colx, file=self.logfile)
+            raise
+
+    def put_cell_unragged(self, rowx, colx, ctype, value, xf_index):
+        if ctype is None:
+            # we have a number, so look up the cell type
+            ctype = self._xf_index_to_xl_type_map[xf_index]
+        # assert 0 <= colx < self.utter_max_cols
+        # assert 0 <= rowx < self.utter_max_rows
+        try:
+            self._cell_types[rowx][colx] = ctype
+            self._cell_values[rowx][colx] = value
+            if self.formatting_info:
+                self._cell_xf_indexes[rowx][colx] = xf_index
+        except IndexError:
+            # print >> self.logfile, "put_cell extending", rowx, colx
+            # self.extend_cells(rowx+1, colx+1)
+            # self._put_cell_exceptions += 1
+            nr = rowx + 1
+            nc = colx + 1
+            assert 1 <= nc <= self.utter_max_cols
+            assert 1 <= nr <= self.utter_max_rows
+            if nc > self.ncols:
+                self.ncols = nc
+                # The row self._first_full_rowx and all subsequent rows
+                # are guaranteed to have length == self.ncols. Thus the
+                # "fix ragged rows" section of the tidy_dimensions method
+                # doesn't need to examine them.
+                if nr < self.nrows:
+                    # cell data is not in non-descending row order *AND*
+                    # self.ncols has been bumped up.
+                    # This very rare case ruins this optimisation.
+                    self._first_full_rowx = -2
+                elif rowx > self._first_full_rowx > -2:
+                    self._first_full_rowx = rowx
+            if nr <= self.nrows:
+                # New cell is in an existing row, so extend that row (if necessary).
+                # Note that nr < self.nrows means that the cell data
+                # is not in ascending row order!!
+                trow = self._cell_types[rowx]
+                nextra = self.ncols - len(trow)
+                if nextra > 0:
+                    # self._put_cell_row_widenings += 1
+                    trow.extend(self.bt * nextra)
+                    if self.formatting_info:
+                        self._cell_xf_indexes[rowx].extend(self.bf * nextra)
+                    self._cell_values[rowx].extend([UNICODE_LITERAL('')] * nextra)
+            else:
+                scta = self._cell_types.append
+                scva = self._cell_values.append
+                scxa = self._cell_xf_indexes.append
+                fmt_info = self.formatting_info
+                nc = self.ncols
+                bt = self.bt
+                bf = self.bf
+                for _unused in xrange(self.nrows, nr):
+                    # self._put_cell_rows_appended += 1
+                    scta(bt * nc)
+                    scva([UNICODE_LITERAL('')] * nc)
+                    if fmt_info:
+                        scxa(bf * nc)
+                self.nrows = nr
+            # === end of code from extend_cells()
+            try:
+                self._cell_types[rowx][colx] = ctype
+                self._cell_values[rowx][colx] = value
+                if self.formatting_info:
+                    self._cell_xf_indexes[rowx][colx] = xf_index
+            except:
+                print("put_cell", rowx, colx, file=self.logfile)
+                raise
+        except:
+            print("put_cell", rowx, colx, file=self.logfile)
+            raise
+
+
+    # === Methods after this line neither know nor care about how cells are stored.
+
+    def read(self, bk):
+        global rc_stats
+        DEBUG = 0
+        blah = DEBUG or self.verbosity >= 2
+        blah_rows = DEBUG or self.verbosity >= 4
+        blah_formulas = 0 and blah
+        r1c1 = 0
+        oldpos = bk._position
+        bk._position = self._position
+        XL_SHRFMLA_ETC_ETC = (
+            XL_SHRFMLA, XL_ARRAY, XL_TABLEOP, XL_TABLEOP2,
+            XL_ARRAY2, XL_TABLEOP_B2,
+        )
+        self_put_cell = self.put_cell
+        local_unpack = unpack
+        bk_get_record_parts = bk.get_record_parts
+        bv = self.biff_version
+        fmt_info = self.formatting_info
+        do_sst_rich_text = fmt_info and bk._rich_text_runlist_map
+        rowinfo_sharing_dict = {}
+        txos = {}
+        eof_found = 0
+        while 1:
+            # if DEBUG: print "SHEET.READ: about to read from position %d" % bk._position
+            rc, data_len, data = bk_get_record_parts()
+            # if rc in rc_stats:
+            #     rc_stats[rc] += 1
+            # else:
+            #     rc_stats[rc] = 1
+            # if DEBUG: print "SHEET.READ: op 0x%04x, %d bytes %r" % (rc, data_len, data)
+            if rc == XL_NUMBER:
+                # [:14] in following stmt ignores extraneous rubbish at end of record.
+                # Sample file testEON-8.xls supplied by Jan Kraus.
+                rowx, colx, xf_index, d = local_unpack('<HHHd', data[:14])
+                # if xf_index == 0:
+                #     fprintf(self.logfile,
+                #         "NUMBER: r=%d c=%d xfx=%d %f\n", rowx, colx, xf_index, d)
+                self_put_cell(rowx, colx, None, d, xf_index)
+            elif rc == XL_LABELSST:
+                rowx, colx, xf_index, sstindex = local_unpack('<HHHi', data)
+                # print "LABELSST", rowx, colx, sstindex, bk._sharedstrings[sstindex]
+                self_put_cell(rowx, colx, XL_CELL_TEXT, bk._sharedstrings[sstindex], xf_index)
+                if do_sst_rich_text:
+                    runlist = bk._rich_text_runlist_map.get(sstindex)
+                    if runlist:
+                        self.rich_text_runlist_map[(rowx, colx)] = runlist
+            elif rc == XL_LABEL:
+                rowx, colx, xf_index = local_unpack('<HHH', data[0:6])
+                if bv < BIFF_FIRST_UNICODE:
+                    strg = unpack_string(data, 6, bk.encoding or bk.derive_encoding(), lenlen=2)
+                else:
+                    strg = unpack_unicode(data, 6, lenlen=2)
+                self_put_cell(rowx, colx, XL_CELL_TEXT, strg, xf_index)
+            elif rc == XL_RSTRING:
+                rowx, colx, xf_index = local_unpack('<HHH', data[0:6])
+                if bv < BIFF_FIRST_UNICODE:
+                    strg, pos = unpack_string_update_pos(data, 6, bk.encoding or bk.derive_encoding(), lenlen=2)
+                    nrt = BYTES_ORD(data[pos])
+                    pos += 1
+                    runlist = []
+                    for _unused in xrange(nrt):
+                        runlist.append(unpack('<BB', data[pos:pos+2]))
+                        pos += 2
+                    assert pos == len(data)
+                else:
+                    strg, pos = unpack_unicode_update_pos(data, 6, lenlen=2)
+                    nrt = unpack('<H', data[pos:pos+2])[0]
+                    pos += 2
+                    runlist = []
+                    for _unused in xrange(nrt):
+                        runlist.append(unpack('<HH', data[pos:pos+4]))
+                        pos += 4
+                    assert pos == len(data)
+                self_put_cell(rowx, colx, XL_CELL_TEXT, strg, xf_index)
+                self.rich_text_runlist_map[(rowx, colx)] = runlist
+            elif rc == XL_RK:
+                rowx, colx, xf_index = local_unpack('<HHH', data[:6])
+                d = unpack_RK(data[6:10])
+                self_put_cell(rowx, colx, None, d, xf_index)
+            elif rc == XL_MULRK:
+                mulrk_row, mulrk_first = local_unpack('<HH', data[0:4])
+                mulrk_last, = local_unpack('<H', data[-2:])
+                pos = 4
+                for colx in xrange(mulrk_first, mulrk_last+1):
+                    xf_index, = local_unpack('<H', data[pos:pos+2])
+                    d = unpack_RK(data[pos+2:pos+6])
+                    pos += 6
+                    self_put_cell(mulrk_row, colx, None, d, xf_index)
+            elif rc == XL_ROW:
+                # Version 0.6.0a3: ROW records are just not worth using (for memory allocation).
+                # Version 0.6.1: now used for formatting info.
+                if not fmt_info: continue
+                rowx, bits1, bits2 = local_unpack('<H4xH4xi', data[0:16])
+                if not(0 <= 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)
+                if r is None:
+                    rowinfo_sharing_dict[key] = r = Rowinfo()
+                    # Using upkbits() is far too slow on a file
+                    # with 30 sheets each with 10K rows :-(
+                    #    upkbits(r, bits1, (
+                    #        ( 0, 0x7FFF, 'height'),
+                    #        (15, 0x8000, 'has_default_height'),
+                    #        ))
+                    #    upkbits(r, bits2, (
+                    #        ( 0, 0x00000007, 'outline_level'),
+                    #        ( 4, 0x00000010, 'outline_group_starts_ends'),
+                    #        ( 5, 0x00000020, 'hidden'),
+                    #        ( 6, 0x00000040, 'height_mismatch'),
+                    #        ( 7, 0x00000080, 'has_default_xf_index'),
+                    #        (16, 0x0FFF0000, 'xf_index'),
+                    #        (28, 0x10000000, 'additional_space_above'),
+                    #        (29, 0x20000000, 'additional_space_below'),
+                    #        ))
+                    # So:
+                    r.height = bits1 & 0x7fff
+                    r.has_default_height = (bits1 >> 15) & 1
+                    r.outline_level = bits2 & 7
+                    r.outline_group_starts_ends = (bits2 >> 4) & 1
+                    r.hidden = (bits2 >> 5) & 1
+                    r.height_mismatch = (bits2 >> 6) & 1
+                    r.has_default_xf_index = (bits2 >> 7) & 1
+                    r.xf_index = (bits2 >> 16) & 0xfff
+                    r.additional_space_above = (bits2 >> 28) & 1
+                    r.additional_space_below = (bits2 >> 29) & 1
+                    if not r.has_default_xf_index:
+                        r.xf_index = -1
+                self.rowinfo_map[rowx] = r
+                if 0 and r.xf_index > -1:
+                    fprintf(self.logfile,
+                        "**ROW %d %d %d\n",
+                        self.number, rowx, r.xf_index)
+                if blah_rows:
+                    print('ROW', rowx, bits1, bits2, file=self.logfile)
+                    r.dump(self.logfile,
+                        header="--- sh #%d, rowx=%d ---" % (self.number, rowx))
+            elif rc in XL_FORMULA_OPCODES: # 06, 0206, 0406
+                # DEBUG = 1
+                # if DEBUG: print "FORMULA: rc: 0x%04x data: %r" % (rc, data)
+                if bv >= 50:
+                    rowx, colx, xf_index, result_str, flags = local_unpack('<HHH8sH', data[0:16])
+                elif bv >= 30:
+                    rowx, colx, xf_index, result_str, flags = local_unpack('<HHH8sH', data[0:16])
+                else: # BIFF2
+                    rowx, colx, cell_attr,  result_str, flags = local_unpack('<HH3s8sB', data[0:16])
+                    xf_index =  self.fixed_BIFF2_xfindex(cell_attr, rowx, colx)
+                if blah_formulas: # testing formula dumper
+                    #### XXXX FIXME
+                    fprintf(self.logfile, "FORMULA: rowx=%d colx=%d\n", rowx, colx)
+                    fmlalen = local_unpack("<H", data[20:22])[0]
+                    decompile_formula(bk, data[22:], fmlalen, FMLA_TYPE_CELL,
+                        browx=rowx, bcolx=colx, blah=1, r1c1=r1c1)
+                if result_str[6:8] == b"\xFF\xFF":
+                    first_byte = BYTES_ORD(result_str[0])
+                    if first_byte == 0:
+                        # need to read next record (STRING)
+                        gotstring = 0
+                        # if flags & 8:
+                        if 1: # "flags & 8" applies only to SHRFMLA
+                            # actually there's an optional SHRFMLA or ARRAY etc record to skip over
+                            rc2, data2_len, data2 = bk.get_record_parts()
+                            if rc2 == XL_STRING or rc2 == XL_STRING_B2:
+                                gotstring = 1
+                            elif rc2 == XL_ARRAY:
+                                row1x, rownx, col1x, colnx, array_flags, tokslen = \
+                                    local_unpack("<HHBBBxxxxxH", data2[:14])
+                                if blah_formulas:
+                                    fprintf(self.logfile, "ARRAY: %d %d %d %d %d\n",
+                                        row1x, rownx, col1x, colnx, array_flags)
+                                    # dump_formula(bk, data2[14:], tokslen, bv, reldelta=0, blah=1)
+                            elif rc2 == XL_SHRFMLA:
+                                row1x, rownx, col1x, colnx, nfmlas, tokslen = \
+                                    local_unpack("<HHBBxBH", data2[:10])
+                                if blah_formulas:
+                                    fprintf(self.logfile, "SHRFMLA (sub): %d %d %d %d %d\n",
+                                        row1x, rownx, col1x, colnx, nfmlas)
+                                    decompile_formula(bk, data2[10:], tokslen, FMLA_TYPE_SHARED,
+                                        blah=1, browx=rowx, bcolx=colx, r1c1=r1c1)
+                            elif rc2 not in XL_SHRFMLA_ETC_ETC:
+                                raise XLRDError(
+                                    "Expected SHRFMLA, ARRAY, TABLEOP* or STRING record; found 0x%04x" % rc2)
+                            # if DEBUG: print "gotstring:", gotstring
+                        # now for the STRING record
+                        if not gotstring:
+                            rc2, _unused_len, data2 = bk.get_record_parts()
+                            if rc2 not in (XL_STRING, XL_STRING_B2):
+                                raise XLRDError("Expected STRING record; found 0x%04x" % rc2)
+                        # if DEBUG: print "STRING: data=%r BIFF=%d cp=%d" % (data2, self.biff_version, bk.encoding)
+                        strg = self.string_record_contents(data2)
+                        self.put_cell(rowx, colx, XL_CELL_TEXT, strg, xf_index)
+                        # if DEBUG: print "FORMULA strg %r" % strg
+                    elif first_byte == 1:
+                        # boolean formula result
+                        value = BYTES_ORD(result_str[2])
+                        self_put_cell(rowx, colx, XL_CELL_BOOLEAN, value, xf_index)
+                    elif first_byte == 2:
+                        # Error in cell
+                        value = BYTES_ORD(result_str[2])
+                        self_put_cell(rowx, colx, XL_CELL_ERROR, value, xf_index)
+                    elif first_byte == 3:
+                        # empty ... i.e. empty (zero-length) string, NOT an empty cell.
+                        self_put_cell(rowx, colx, XL_CELL_TEXT, "", xf_index)
+                    else:
+                        raise XLRDError("unexpected special case (0x%02x) in FORMULA" % first_byte)
+                else:
+                    # it is a number
+                    d = local_unpack('<d', result_str)[0]
+                    self_put_cell(rowx, colx, None, d, xf_index)
+            elif rc == XL_BOOLERR:
+                rowx, colx, xf_index, value, is_err = local_unpack('<HHHBB', data[:8])
+                # Note OOo Calc 2.0 writes 9-byte BOOLERR records.
+                # OOo docs say 8. Excel writes 8.
+                cellty = (XL_CELL_BOOLEAN, XL_CELL_ERROR)[is_err]
+                # if DEBUG: print "XL_BOOLERR", rowx, colx, xf_index, value, is_err
+                self_put_cell(rowx, colx, cellty, value, xf_index)
+            elif rc == XL_COLINFO:
+                if not fmt_info: continue
+                c = Colinfo()
+                first_colx, last_colx, c.width, c.xf_index, flags \
+                    = local_unpack("<HHHHH", data[:10])
+                #### Colinfo.width is denominated in 256ths of a character,
+                #### *not* in characters.
+                if not(0 <= first_colx <= last_colx <= 256):
+                    # Note: 256 instead of 255 is a common mistake.
+                    # We silently ignore the non-existing 257th column in that case.
+                    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, (
+                    ( 0, 0x0001, 'hidden'),
+                    ( 1, 0x0002, 'bit1_flag'),
+                    # *ALL* colinfos created by Excel in "default" cases are 0x0002!!
+                    # 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
+                    if 0:
+                        fprintf(self.logfile,
+                            "**COL %d %d %d\n",
+                            self.number, colx, c.xf_index)
+                if blah:
+                    fprintf(
+                        self.logfile,
+                        "COLINFO sheet #%d cols %d-%d: wid=%d xf_index=%d flags=0x%04x\n",
+                        self.number, first_colx, last_colx, c.width, c.xf_index, flags,
+                    )
+                    c.dump(self.logfile, header='===')
+            elif rc == XL_DEFCOLWIDTH:
+                self.defcolwidth, = local_unpack("<H", data[:2])
+                if 0: print('DEFCOLWIDTH', self.defcolwidth, file=self.logfile)
+            elif rc == XL_STANDARDWIDTH:
+                if data_len != 2:
+                    print('*** ERROR *** STANDARDWIDTH', data_len, repr(data), file=self.logfile)
+                self.standardwidth, = local_unpack("<H", data[:2])
+                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] == b"\x20\x00"
+                iguff = unpack("<8i", data[2:34])
+                gcw = []
+                for bits in iguff:
+                    for j in xrange(32):
+                        gcw.append(bits & 1)
+                        bits >>= 1
+                self.gcw = tuple(gcw)
+                if 0:
+                    showgcw = "".join(map(lambda x: "F "[x], gcw)).rstrip().replace(' ', '.')
+                    print("GCW:", showgcw, file=self.logfile)
+            elif rc == XL_BLANK:
+                if not fmt_info: continue
+                rowx, colx, xf_index = local_unpack('<HHH', data[:6])
+                # if 0: print >> self.logfile, "BLANK", rowx, colx, xf_index
+                self_put_cell(rowx, colx, XL_CELL_BLANK, '', xf_index)
+            elif rc == XL_MULBLANK: # 00BE
+                if not fmt_info: continue
+                nitems = data_len >> 1
+                result = local_unpack("<%dH" % nitems, data)
+                rowx, mul_first = result[:2]
+                mul_last = result[-1]
+                # print >> self.logfile, "MULBLANK", rowx, mul_first, mul_last, data_len, nitems, mul_last + 4 - mul_first
+                assert nitems == mul_last + 4 - mul_first
+                pos = 2
+                for colx in xrange(mul_first, mul_last + 1):
+                    self_put_cell(rowx, colx, XL_CELL_BLANK, '', result[pos])
+                    pos += 1
+            elif rc == XL_DIMENSION or rc == XL_DIMENSION2:
+                if data_len == 0:
+                    # Four zero bytes after some other record. See github issue 64.
+                    continue
+                # if data_len == 10:
+                # Was crashing on BIFF 4.0 file w/o the two trailing unused bytes.
+                # Reported by Ralph Heimburger.
+                if bv < 80:
+                    dim_tuple = local_unpack('<HxxH', data[2:8])
+                else:
+                    dim_tuple = local_unpack('<ixxH', data[4:12])
+                self.nrows, self.ncols = 0, 0
+                self._dimnrows, self._dimncols = dim_tuple
+                if bv in (21, 30, 40) and self.book.xf_list and not self.book._xf_epilogue_done:
+                    self.book.xf_epilogue()
+                if blah:
+                    fprintf(
+                        self.logfile,
+                        "sheet %d(%r) DIMENSIONS: ncols=%d nrows=%d\n",
+                        self.number, self.name, self._dimncols, self._dimnrows
+                    )
+            elif rc == XL_HLINK:
+                self.handle_hlink(data)
+            elif rc == XL_QUICKTIP:
+                self.handle_quicktip(data)
+            elif rc == XL_EOF:
+                DEBUG = 0
+                if DEBUG: print("SHEET.READ: EOF", file=self.logfile)
+                eof_found = 1
+                break
+            elif rc == XL_OBJ:
+                # handle SHEET-level objects; note there's a separate Book.handle_obj
+                saved_obj = self.handle_obj(data)
+                if saved_obj: saved_obj_id = saved_obj.id
+                else: saved_obj_id = None
+            elif rc == XL_MSO_DRAWING:
+                self.handle_msodrawingetc(rc, data_len, data)
+            elif rc == XL_TXO:
+                txo = self.handle_txo(data)
+                if txo and saved_obj_id:
+                    txos[saved_obj_id] = txo
+                    saved_obj_id = None
+            elif rc == XL_NOTE:
+                self.handle_note(data, txos)
+            elif rc == XL_FEAT11:
+                self.handle_feat11(data)
+            elif rc in bofcodes: ##### EMBEDDED BOF #####
+                version, boftype = local_unpack('<HH', data[0:4])
+                if boftype != 0x20: # embedded chart
+                    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("---> found EOF", file=self.logfile)
+            elif rc == XL_COUNTRY:
+                bk.handle_country(data)
+            elif rc == XL_LABELRANGES:
+                pos = 0
+                pos = unpack_cell_range_address_list_update_pos(
+                    self.row_label_ranges, data, pos, bv, addr_size=8,
+                )
+                pos = unpack_cell_range_address_list_update_pos(
+                    self.col_label_ranges, data, pos, bv, addr_size=8,
+                )
+                assert pos == data_len
+            elif rc == XL_ARRAY:
+                row1x, rownx, col1x, colnx, array_flags, tokslen = \
+                    local_unpack("<HHBBBxxxxxH", data[:14])
+                if blah_formulas:
+                    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("<HHBBxBH", data[:10])
+                if blah_formulas:
+                    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:
+                if not fmt_info: continue
+                assert bv >= 80
+                num_CFs, needs_recalc, browx1, browx2, bcolx1, bcolx2 = \
+                    unpack("<6H", data[0:12])
+                if self.verbosity >= 1:
+                    fprintf(
+                        self.logfile,
+                        "\n*** WARNING: Ignoring CONDFMT (conditional formatting) record\n"
+                        "*** in Sheet %d (%r).\n"
+                        "*** %d CF record(s); needs_recalc_or_redraw = %d\n"
+                        "*** Bounding box is %s\n",
+                        self.number, self.name, num_CFs, needs_recalc,
+                        rangename2d(browx1, browx2+1, bcolx1, bcolx2+1),
+                    )
+                olist = [] # updated by the function
+                pos = unpack_cell_range_address_list_update_pos(
+                    olist, data, 12, bv, addr_size=8)
+                # print >> self.logfile, repr(result), len(result)
+                if self.verbosity >= 1:
+                    fprintf(
+                        self.logfile,
+                        "*** %d individual range(s):\n"
+                        "*** %s\n",
+                        len(olist),
+                        ", ".join(rangename2d(*coords) for coords in olist),
+                    )
+            elif rc == XL_CF:
+                if not fmt_info: continue
+                cf_type, cmp_op, sz1, sz2, flags = unpack("<BBHHi", data[0:10])
+                font_block = (flags >> 26) & 1
+                bord_block = (flags >> 28) & 1
+                patt_block = (flags >> 29) & 1
+                if self.verbosity >= 1:
+                    fprintf(
+                        self.logfile,
+                        "\n*** WARNING: Ignoring CF (conditional formatting) sub-record.\n"
+                        "*** cf_type=%d, cmp_op=%d, sz1=%d, sz2=%d, flags=0x%08x\n"
+                        "*** optional data blocks: font=%d, border=%d, pattern=%d\n",
+                        cf_type, cmp_op, sz1, sz2, flags,
+                        font_block, bord_block, patt_block,
+                    )
+                # hex_char_dump(data, 0, data_len, fout=self.logfile)
+                pos = 12
+                if font_block:
+                    (font_height, font_options, weight, escapement, underline,
+                     font_colour_index, two_bits, font_esc, font_underl) = unpack("<64x i i H H B 3x i 4x i i i 18x", data[pos:pos+118])
+                    font_style = (two_bits > 1) & 1
+                    posture = (font_options > 1) & 1
+                    font_canc = (two_bits > 7) & 1
+                    cancellation = (font_options > 7) & 1
+                    if self.verbosity >= 1:
+                        fprintf(
+                            self.logfile,
+                            "*** Font info: height=%d, weight=%d, escapement=%d,\n"
+                            "*** underline=%d, colour_index=%d, esc=%d, underl=%d,\n"
+                            "*** style=%d, posture=%d, canc=%d, cancellation=%d\n",
+                            font_height, weight, escapement, underline,
+                            font_colour_index, font_esc, font_underl,
+                            font_style, posture, font_canc, cancellation,
+                        )
+                    pos += 118
+                if bord_block:
+                    pos += 8
+                if patt_block:
+                    pos += 4
+                fmla1 = data[pos:pos+sz1]
+                pos += sz1
+                if blah and sz1:
+                    fprintf(self.logfile, "*** formula 1:\n")
+                    dump_formula(bk, fmla1, sz1, bv, reldelta=0, blah=1)
+                fmla2 = data[pos:pos+sz2]
+                pos += sz2
+                assert pos == data_len
+                if blah and sz2:
+                    fprintf(self.logfile, "*** formula 2:\n")
+                    dump_formula(bk, fmla2, sz2, bv, reldelta=0, blah=1)
+            elif rc == XL_DEFAULTROWHEIGHT:
+                if data_len == 4:
+                    bits, self.default_row_height = unpack("<HH", data[:4])
+                elif data_len == 2:
+                    self.default_row_height, = unpack("<H", data)
+                    bits = 0
+                    fprintf(self.logfile,
+                        "*** WARNING: DEFAULTROWHEIGHT record len is 2, "
+                        "should be 4; assuming BIFF2 format\n")
+                else:
+                    bits = 0
+                    fprintf(self.logfile,
+                        "*** WARNING: DEFAULTROWHEIGHT record len is %d, "
+                        "should be 4; ignoring this record\n",
+                        data_len)
+                self.default_row_height_mismatch = bits & 1
+                self.default_row_hidden = (bits >> 1) & 1
+                self.default_additional_space_above = (bits >> 2) & 1
+                self.default_additional_space_below = (bits >> 3) & 1
+            elif rc == XL_MERGEDCELLS:
+                if not fmt_info: continue
+                pos = unpack_cell_range_address_list_update_pos(
+                    self.merged_cells, data, 0, bv, addr_size=8)
+                if blah:
+                    fprintf(self.logfile,
+                        "MERGEDCELLS: %d ranges\n", (pos - 2) // 8)
+                assert pos == data_len, \
+                    "MERGEDCELLS: pos=%d data_len=%d" % (pos, data_len)
+            elif rc == XL_WINDOW2:
+                if bv >= 80 and data_len >= 14:
+                    (
+                        options,
+                        self.first_visible_rowx, self.first_visible_colx,
+                        self.gridline_colour_index,
+                        self.cached_page_break_preview_mag_factor,
+                        self.cached_normal_view_mag_factor
+                    ) = unpack("<HHHHxxHH", data[:14])
+                else:
+                    assert bv >= 30 # BIFF3-7
+                    (
+                        options,
+                        self.first_visible_rowx, self.first_visible_colx,
+                    ) = unpack("<HHH", data[:6])
+                    self.gridline_colour_rgb = unpack("<BBB", data[6:9])
+                    self.gridline_colour_index = nearest_colour_index(
+                        self.book.colour_map, self.gridline_colour_rgb, debug=0)
+                # options -- Bit, Mask, Contents:
+                # 0 0001H 0 = Show formula results 1 = Show formulas
+                # 1 0002H 0 = Do not show grid lines 1 = Show grid lines
+                # 2 0004H 0 = Do not show sheet headers 1 = Show sheet headers
+                # 3 0008H 0 = Panes are not frozen 1 = Panes are frozen (freeze)
+                # 4 0010H 0 = Show zero values as empty cells 1 = Show zero values
+                # 5 0020H 0 = Manual grid line colour 1 = Automatic grid line colour
+                # 6 0040H 0 = Columns from left to right 1 = Columns from right to left
+                # 7 0080H 0 = Do not show outline symbols 1 = Show outline symbols
+                # 8 0100H 0 = Keep splits if pane freeze is removed 1 = Remove splits if pane freeze is removed
+                # 9 0200H 0 = Sheet not selected 1 = Sheet selected (BIFF5-BIFF8)
+                # 10 0400H 0 = Sheet not visible 1 = Sheet visible (BIFF5-BIFF8)
+                # 11 0800H 0 = Show in normal view 1 = Show in page break preview (BIFF8)
+                # The freeze flag specifies, if a following PANE record (6.71) describes unfrozen or frozen panes.
+                for attr, _unused_defval in _WINDOW2_options:
+                    setattr(self, attr, options & 1)
+                    options >>= 1
+            elif rc == XL_SCL:
+                num, den = unpack("<HH", data)
+                result = 0
+                if den:
+                    result = (num * 100) // den
+                if not(10 <= result <= 400):
+                    if DEBUG or self.verbosity >= 0:
+                        print(
+                            "WARNING *** SCL rcd sheet %d: should have 0.1 <= num/den <= 4; got %d/%d"
+                            % (self.number, num, den),
+                            file=self.logfile,
+                        )
+                    result = 100
+                self.scl_mag_factor = result
+            elif rc == XL_PANE:
+                (
+                    self.vert_split_pos,
+                    self.horz_split_pos,
+                    self.horz_split_first_visible,
+                    self.vert_split_first_visible,
+                    self.split_active_pane,
+                ) = unpack("<HHHHB", data[:9])
+                self.has_pane_record = 1
+            elif rc == XL_HORIZONTALPAGEBREAKS:
+                if not fmt_info: continue
+                num_breaks, = local_unpack("<H", data[:2])
+                assert num_breaks * (2 + 4 * (bv >= 80)) + 2 == data_len
+                pos = 2
+                if bv < 80:
+                    while pos < data_len:
+                        self.horizontal_page_breaks.append((local_unpack("<H", data[pos:pos+2])[0], 0, 255))
+                        pos += 2
+                else:
+                    while pos < data_len:
+                        self.horizontal_page_breaks.append(local_unpack("<HHH", data[pos:pos+6]))
+                        pos += 6
+            elif rc == XL_VERTICALPAGEBREAKS:
+                if not fmt_info: continue
+                num_breaks, = local_unpack("<H", data[:2])
+                assert num_breaks * (2 + 4 * (bv >= 80)) + 2 == data_len
+                pos = 2
+                if bv < 80:
+                    while pos < data_len:
+                        self.vertical_page_breaks.append((local_unpack("<H", data[pos:pos+2])[0], 0, 65535))
+                        pos += 2
+                else:
+                    while pos < data_len:
+                        self.vertical_page_breaks.append(local_unpack("<HHH", data[pos:pos+6]))
+                        pos += 6
+            #### all of the following are for BIFF <= 4W
+            elif bv <= 45:
+                if rc == XL_FORMAT or rc == XL_FORMAT2:
+                    bk.handle_format(data, rc)
+                elif rc == XL_FONT or rc == XL_FONT_B3B4:
+                    bk.handle_font(data)
+                elif rc == XL_STYLE:
+                    if not self.book._xf_epilogue_done:
+                        self.book.xf_epilogue()
+                    bk.handle_style(data)
+                elif rc == XL_PALETTE:
+                    bk.handle_palette(data)
+                elif rc == XL_BUILTINFMTCOUNT:
+                    bk.handle_builtinfmtcount(data)
+                elif rc == XL_XF4 or rc == XL_XF3 or rc == XL_XF2: #### N.B. not XL_XF
+                    bk.handle_xf(data)
+                elif rc == XL_DATEMODE:
+                    bk.handle_datemode(data)
+                elif rc == XL_CODEPAGE:
+                    bk.handle_codepage(data)
+                elif rc == XL_FILEPASS:
+                    bk.handle_filepass(data)
+                elif rc == XL_WRITEACCESS:
+                    bk.handle_writeaccess(data)
+                elif rc == XL_IXFE:
+                    self._ixfe = local_unpack('<H', data)[0]
+                elif rc == XL_NUMBER_B2:
+                    rowx, colx, cell_attr, d = local_unpack('<HH3sd', data)
+                    self_put_cell(rowx, colx, None, d, self.fixed_BIFF2_xfindex(cell_attr, rowx, colx))
+                elif rc == XL_INTEGER:
+                    rowx, colx, cell_attr, d = local_unpack('<HH3sH', data)
+                    self_put_cell(rowx, colx, None, float(d), self.fixed_BIFF2_xfindex(cell_attr, rowx, colx))
+                elif rc == XL_LABEL_B2:
+                    rowx, colx, cell_attr = local_unpack('<HH3s', data[0:7])
+                    strg = unpack_string(data, 7, bk.encoding or bk.derive_encoding(), lenlen=1)
+                    self_put_cell(rowx, colx, XL_CELL_TEXT, strg, self.fixed_BIFF2_xfindex(cell_attr, rowx, colx))
+                elif rc == XL_BOOLERR_B2:
+                    rowx, colx, cell_attr, value, is_err = local_unpack('<HH3sBB', data)
+                    cellty = (XL_CELL_BOOLEAN, XL_CELL_ERROR)[is_err]
+                    # if DEBUG: print "XL_BOOLERR_B2", rowx, colx, cell_attr, value, is_err
+                    self_put_cell(rowx, colx, cellty, value, self.fixed_BIFF2_xfindex(cell_attr, rowx, colx))
+                elif rc == XL_BLANK_B2:
+                    if not fmt_info: continue
+                    rowx, colx, cell_attr = local_unpack('<HH3s', data[:7])
+                    self_put_cell(rowx, colx, XL_CELL_BLANK, '', self.fixed_BIFF2_xfindex(cell_attr, rowx, colx))
+                elif rc == XL_EFONT:
+                    bk.handle_efont(data)
+                elif rc == XL_ROW_B2:
+                    if not fmt_info: continue
+                    rowx, bits1, bits2 = local_unpack('<H4xH2xB', data[0:11])
+                    if not(0 <= 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('<H', data[16:18])[0]
+                        xf_index = self.fixed_BIFF2_xfindex(cell_attr=None, rowx=rowx, colx=-1, true_xfx=xfx)
+                    else:
+                        cell_attr = data[13:16]
+                        xf_index = self.fixed_BIFF2_xfindex(cell_attr, rowx, colx=-1)
+                    key = (bits1, bits2, xf_index)
+                    r = rowinfo_sharing_dict.get(key)
+                    if r is None:
+                        rowinfo_sharing_dict[key] = r = Rowinfo()
+                        r.height = bits1 & 0x7fff
+                        r.has_default_height = (bits1 >> 15) & 1
+                        r.has_default_xf_index = bits2 & 1
+                        r.xf_index = xf_index
+                        # r.outline_level = 0             # set in __init__
+                        # r.outline_group_starts_ends = 0 # set in __init__
+                        # r.hidden = 0                    # set in __init__
+                        # r.height_mismatch = 0           # set in __init__
+                        # r.additional_space_above = 0    # set in __init__
+                        # r.additional_space_below = 0    # set in __init__
+                    self.rowinfo_map[rowx] = r
+                    if 0 and r.xf_index > -1:
+                        fprintf(self.logfile,
+                            "**ROW %d %d %d\n",
+                            self.number, rowx, r.xf_index)
+                    if blah_rows:
+                        print('ROW_B2', rowx, bits1, file=self.logfile)
+                        r.dump(self.logfile,
+                            header="--- sh #%d, rowx=%d ---" % (self.number, rowx))
+                elif rc == XL_COLWIDTH: # BIFF2 only
+                    if not fmt_info: continue
+                    first_colx, last_colx, width\
+                        = local_unpack("<BBH", data[:4])
+                    if not(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 colx in self.colinfo_map:
+                            c = self.colinfo_map[colx]
+                        else:
+                            c = Colinfo()
+                            self.colinfo_map[colx] = c
+                        c.width = width
+                    if blah:
+                        fprintf(
+                            self.logfile,
+                            "COLWIDTH sheet #%d cols %d-%d: wid=%d\n",
+                            self.number, first_colx, last_colx, width,
+                        )
+                elif rc == XL_COLUMNDEFAULT: # BIFF2 only
+                    if not fmt_info: continue
+                    first_colx, last_colx = local_unpack("<HH", data[:4])
+                    #### Warning OOo docs wrong; first_colx <= colx < last_colx
+                    if blah:
+                        fprintf(
+                            self.logfile,
+                            "COLUMNDEFAULT sheet #%d cols in range(%d, %d)\n",
+                            self.number, first_colx, last_colx,
+                        )
+                    if not(0 <= first_colx < last_colx <= 256):
+                        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 colx in self.colinfo_map:
+                            c = self.colinfo_map[colx]
+                        else:
+                            c = Colinfo()
+                            self.colinfo_map[colx] = c
+                        c.xf_index = xf_index
+                elif rc == XL_WINDOW2_B2: # BIFF 2 only
+                    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 != b'\0'))
+                    (
+                        self.first_visible_rowx, self.first_visible_colx,
+                        self.automatic_grid_line_colour,
+                    ) = unpack("<HHB", data[5:10])
+                    self.gridline_colour_rgb = unpack("<BBB", data[10:13])
+                    self.gridline_colour_index = nearest_colour_index(
+                        self.book.colour_map, self.gridline_colour_rgb, debug=0)
+            else:
+                # if DEBUG: print "SHEET.READ: Unhandled record type %02x %d bytes %r" % (rc, data_len, data)
+                pass
+        if not eof_found:
+            raise XLRDError("Sheet %d (%r) missing EOF record"
+                % (self.number, self.name))
+        self.tidy_dimensions()
+        self.update_cooked_mag_factors()
+        bk._position = oldpos
+        return 1
+
+    def string_record_contents(self, data):
+        bv = self.biff_version
+        bk = self.book
+        lenlen = (bv >= 30) + 1
+        nchars_expected = unpack("<" + "BH"[lenlen - 1], data[:lenlen])[0]
+        offset = lenlen
+        if bv < 80:
+            enc = bk.encoding or bk.derive_encoding()
+        nchars_found = 0
+        result = UNICODE_LITERAL("")
+        while 1:
+            if bv >= 80:
+                flag = BYTES_ORD(data[offset]) & 1
+                enc = ("latin_1", "utf_16_le")[flag]
+                offset += 1
+            chunk = unicode(data[offset:], enc)
+            result += chunk
+            nchars_found += len(chunk)
+            if nchars_found == nchars_expected:
+                return result
+            if nchars_found > nchars_expected:
+                msg = ("STRING/CONTINUE: expected %d chars, found %d"
+                    % (nchars_expected, nchars_found))
+                raise XLRDError(msg)
+            rc, _unused_len, data = bk.get_record_parts()
+            if rc != XL_CONTINUE:
+                raise XLRDError(
+                    "Expected CONTINUE record; found record-type 0x%04X" % rc)
+            offset = 0
+
+    def update_cooked_mag_factors(self):
+        # Cached values are used ONLY for the non-active view mode.
+        # When the user switches to the non-active view mode,
+        # if the cached value for that mode is not valid,
+        # Excel pops up a window which says:
+        # "The number must be between 10 and 400. Try again by entering a number in this range."
+        # When the user hits OK, it drops into the non-active view mode
+        # but uses the magn from the active mode.
+        # NOTE: definition of "valid" depends on mode ... see below
+        blah = DEBUG or self.verbosity > 0
+        if self.show_in_page_break_preview:
+            if self.scl_mag_factor is None: # no SCL record
+                self.cooked_page_break_preview_mag_factor = 100 # Yes, 100, not 60, NOT a typo
+            else:
+                self.cooked_page_break_preview_mag_factor = self.scl_mag_factor
+            zoom = self.cached_normal_view_mag_factor
+            if not (10 <= zoom <=400):
+                if blah:
+                    print(
+                        "WARNING *** WINDOW2 rcd sheet %d: Bad cached_normal_view_mag_factor: %d"
+                        % (self.number, self.cached_normal_view_mag_factor),
+                        file=self.logfile,
+                    )
+                zoom = self.cooked_page_break_preview_mag_factor
+            self.cooked_normal_view_mag_factor = zoom
+        else:
+            # normal view mode
+            if self.scl_mag_factor is None: # no SCL record
+                self.cooked_normal_view_mag_factor = 100
+            else:
+                self.cooked_normal_view_mag_factor = self.scl_mag_factor
+            zoom = self.cached_page_break_preview_mag_factor
+            if not zoom:
+                # VALID, defaults to 60
+                zoom = 60
+            elif not (10 <= zoom <= 400):
+                if blah:
+                    print(
+                        "WARNING *** WINDOW2 rcd sheet %r: Bad cached_page_break_preview_mag_factor: %r"
+                        % (self.number, self.cached_page_break_preview_mag_factor),
+                        file=self.logfile,
+                    )
+                zoom = self.cooked_normal_view_mag_factor
+            self.cooked_page_break_preview_mag_factor = zoom
+
+    def fixed_BIFF2_xfindex(self, cell_attr, rowx, colx, true_xfx=None):
+        DEBUG = 0
+        blah = DEBUG or self.verbosity >= 2
+        if self.biff_version == 21:
+            if self.book.xf_list:
+                if true_xfx is not None:
+                    xfx = true_xfx
+                else:
+                    xfx = BYTES_ORD(cell_attr[0]) & 0x3F
+                if xfx == 0x3F:
+                    if self._ixfe is None:
+                        raise XLRDError("BIFF2 cell record has XF index 63 but no preceding IXFE record.")
+                    xfx = self._ixfe
+                    # OOo docs are capable of interpretation that each
+                    # cell record is preceded immediately by its own IXFE record.
+                    # Empirical evidence is that (sensibly) an IXFE record applies to all
+                    # following cell records until another IXFE comes along.
+                return xfx
+            # Have either Excel 2.0, or broken 2.1 w/o XF records -- same effect.
+            self.biff_version = self.book.biff_version = 20
+        #### check that XF slot in cell_attr is zero
+        xfx_slot = BYTES_ORD(cell_attr[0]) & 0x3F
+        assert xfx_slot == 0
+        xfx = self._cell_attr_to_xfx.get(cell_attr)
+        if xfx is not None:
+            return xfx
+        if blah:
+            fprintf(self.logfile, "New cell_attr %r at (%r, %r)\n", cell_attr, rowx, colx)
+        if not self.book.xf_list:
+            for xfx in xrange(16):
+                self.insert_new_BIFF20_xf(cell_attr=b"\x40\x00\x00", style=xfx < 15)
+        xfx = self.insert_new_BIFF20_xf(cell_attr=cell_attr)
+        return xfx
+
+    def insert_new_BIFF20_xf(self, cell_attr, style=0):
+        DEBUG = 0
+        blah = DEBUG or self.verbosity >= 2
+        book = self.book
+        xfx = len(book.xf_list)
+        xf = self.fake_XF_from_BIFF20_cell_attr(cell_attr, style)
+        xf.xf_index = xfx
+        book.xf_list.append(xf)
+        if blah:
+            xf.dump(self.logfile, header="=== Faked XF %d ===" % xfx, footer="======")
+        if xf.format_key not in book.format_map:
+            if xf.format_key:
+                msg = "ERROR *** XF[%d] unknown format key (%d, 0x%04x)\n"
+                fprintf(self.logfile, msg,
+                        xf.xf_index, xf.format_key, xf.format_key)
+            fmt = Format(xf.format_key, FUN, UNICODE_LITERAL("General"))
+            book.format_map[xf.format_key] = fmt
+            book.format_list.append(fmt)
+        cellty_from_fmtty = {
+            FNU: XL_CELL_NUMBER,
+            FUN: XL_CELL_NUMBER,
+            FGE: XL_CELL_NUMBER,
+            FDT: XL_CELL_DATE,
+            FTX: XL_CELL_NUMBER, # Yes, a number can be formatted as text.
+        }
+        fmt = book.format_map[xf.format_key]
+        cellty = cellty_from_fmtty[fmt.type]
+        self._xf_index_to_xl_type_map[xf.xf_index] = cellty
+        self._cell_attr_to_xfx[cell_attr] = xfx
+        return xfx
+
+    def fake_XF_from_BIFF20_cell_attr(self, cell_attr, style=0):
+        from .formatting import XF, XFAlignment, XFBorder, XFBackground, XFProtection
+        xf = XF()
+        xf.alignment = XFAlignment()
+        xf.alignment.indent_level = 0
+        xf.alignment.shrink_to_fit = 0
+        xf.alignment.text_direction = 0
+        xf.border = XFBorder()
+        xf.border.diag_up = 0
+        xf.border.diag_down = 0
+        xf.border.diag_colour_index = 0
+        xf.border.diag_line_style = 0 # no line
+        xf.background = XFBackground()
+        xf.protection = XFProtection()
+        (prot_bits, font_and_format, halign_etc) = unpack('<BBB', cell_attr)
+        xf.format_key = font_and_format & 0x3F
+        xf.font_index = (font_and_format & 0xC0) >> 6
+        upkbits(xf.protection, prot_bits, (
+            (6, 0x40, 'cell_locked'),
+            (7, 0x80, 'formula_hidden'),
+        ))
+        xf.alignment.hor_align = halign_etc & 0x07
+        for mask, side in ((0x08, 'left'), (0x10, 'right'), (0x20, 'top'), (0x40, 'bottom')):
+            if halign_etc & mask:
+                colour_index, line_style = 8, 1 # black, thin
+            else:
+                colour_index, line_style = 0, 0 # none, none
+            setattr(xf.border, side + '_colour_index', colour_index)
+            setattr(xf.border, side + '_line_style', line_style)
+        bg = xf.background
+        if halign_etc & 0x80:
+            bg.fill_pattern = 17
+        else:
+            bg.fill_pattern = 0
+        bg.background_colour_index = 9 # white
+        bg.pattern_colour_index = 8 # black
+        xf.parent_style_index = (0x0FFF, 0)[style]
+        xf.alignment.vert_align = 2 # bottom
+        xf.alignment.rotation = 0
+        attr_stems = [
+            'format',
+            'font',
+            'alignment',
+            'border',
+            'background',
+            'protection',
+        ]
+        for attr_stem in attr_stems:
+            attr = "_" + attr_stem + "_flag"
+            setattr(xf, attr, 1)
+        return xf
+
+    def req_fmt_info(self):
+        if not self.formatting_info:
+            raise XLRDError("Feature requires open_workbook(..., formatting_info=True)")
+
+    def computed_column_width(self, colx):
+        """
+        Determine column display width.
+
+        :param colx:
+          Index of the queried column, range 0 to 255.
+          Note that it is possible to find out the width that will be used to
+          display columns with no cell information e.g. column IV (colx=255).
+
+        :return:
+          The column width that will be used for displaying
+          the given column by Excel, in units of 1/256th of the width of a
+          standard character (the digit zero in the first font).
+
+        .. versionadded:: 0.6.1
+        """
+        self.req_fmt_info()
+        if self.biff_version >= 80:
+            colinfo = self.colinfo_map.get(colx, None)
+            if colinfo is not None:
+                return colinfo.width
+            if self.standardwidth is not None:
+                return self.standardwidth
+        elif self.biff_version >= 40:
+            if self.gcw[colx]:
+                if self.standardwidth is not None:
+                    return self.standardwidth
+            else:
+                colinfo = self.colinfo_map.get(colx, None)
+                if colinfo is not None:
+                    return colinfo.width
+        elif self.biff_version == 30:
+            colinfo = self.colinfo_map.get(colx, None)
+            if colinfo is not None:
+                return colinfo.width
+        # All roads lead to Rome and the DEFCOLWIDTH ...
+        if self.defcolwidth is not None:
+            return self.defcolwidth * 256
+        return 8 * 256 # 8 is what Excel puts in a DEFCOLWIDTH record
+
+    def handle_hlink(self, data):
+        # DEBUG = 1
+        if DEBUG: print("\n=== hyperlink ===", file=self.logfile)
+        record_size = len(data)
+        h = Hyperlink()
+        h.frowx, h.lrowx, h.fcolx, h.lcolx, guid0, dummy, options = unpack('<HHHH16s4si', data[:32])
+        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):
+            nb = unpack('<L', buf[ofs:ofs+4])[0] * 2
+            ofs += 4
+            uc = unicode(buf[ofs:ofs+nb], 'UTF-16le')[:-1]
+            ofs += nb
+            return uc, 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: fprintf(self.logfile, "clsid=%r\n",  clsid)
+            offset += 16
+            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 = UNICODE_LITERAL('url')
+                nbytes = unpack('<L', data[offset:offset + 4])[0]
+                offset += 4
+                h.url_or_path = unicode(data[offset:offset + nbytes], 'UTF-16le')
+                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:
+                    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 == b"\x03\x03\x00\x00\x00\x00\x00\x00\xC0\x00\x00\x00\x00\x00\x00\x46":
+                # file moniker
+                h.type = UNICODE_LITERAL('local file')
+                uplevels, nbytes = unpack("<Hi", data[offset:offset + 6])
+                offset += 6
+                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('<i', data[offset:offset + 4])[0]
+                if DEBUG: print("sz=%d" % sz, file=self.logfile)
+                offset += 4
+                if sz:
+                    xl = unpack('<i', data[offset:offset + 4])[0]
+                    offset += 4
+                    offset += 2 # "unknown byte sequence" MS: 0x0003
+                    extended_path = unicode(data[offset:offset + xl], 'UTF-16le') # not zero-terminated
+                    offset += xl
+                    h.url_or_path = extended_path
+                else:
+                    h.url_or_path = shortpath
+                    #### MS KLUDGE WARNING ####
+                    # The "shortpath" is bytes encoded in the **UNKNOWN** creator's "ANSI" encoding.
+            else:
+                fprintf(self.logfile, "*** unknown clsid %r\n", clsid)
+        elif options & 0x163 == 0x103: # UNC
+            h.type = UNICODE_LITERAL('unc')
+            h.url_or_path, offset = get_nul_terminated_unicode(data, offset)
+        elif options & 0x16B == 8:
+            h.type = UNICODE_LITERAL('workbook')
+        else:
+            h.type = UNICODE_LITERAL('unknown')
+
+        if options & 0x8: # has textmark
+            h.textmark, offset = get_nul_terminated_unicode(data, offset)
+
+        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:] == b'\x00\x00'
+        h.quicktip = unicode(data[10:-2], 'utf_16_le')
+
+    def handle_msodrawingetc(self, recid, data_len, data):
+        if not OBJ_MSO_DEBUG:
+            return
+        DEBUG = 1
+        if self.biff_version < 80:
+            return
+        o = MSODrawing()
+        pos = 0
+        while pos < data_len:
+            tmp, fbt, cb = unpack('<HHI', data[pos:pos+8])
+            ver = tmp & 0xF
+            inst = (tmp >> 4) & 0xFFF
+            if ver == 0xF:
+                ndb = 0 # container
+            else:
+                ndb = cb
+            if DEBUG:
+                hex_char_dump(data, pos, ndb + 8, base=0, fout=self.logfile)
+                fprintf(self.logfile,
+                    "fbt:0x%04X  inst:%d  ver:0x%X  cb:%d (0x%04X)\n",
+                    fbt, inst, ver, cb, cb)
+            if fbt == 0xF010: # Client Anchor
+                assert ndb == 18
+                (o.anchor_unk,
+                o.anchor_colx_lo, o.anchor_rowx_lo,
+                o.anchor_colx_hi, o.anchor_rowx_hi) = unpack('<Hiiii', data[pos+8:pos+8+ndb])
+            elif fbt == 0xF011: # Client Data
+                # must be followed by an OBJ record
+                assert cb == 0
+                assert pos + 8 == data_len
+            else:
+                pass
+            pos += ndb + 8
+        else:
+            # didn't break out of while loop
+            assert pos == data_len
+        if DEBUG:
+            o.dump(self.logfile, header="=== MSODrawing ===", footer= " ")
+
+
+    def handle_obj(self, data):
+        if self.biff_version < 80:
+            return None
+        o = MSObj()
+        data_len = len(data)
+        pos = 0
+        if OBJ_MSO_DEBUG:
+            fprintf(self.logfile, "... OBJ record len=%d...\n", data_len)
+        while pos < data_len:
+            ft, cb = unpack('<HH', data[pos:pos+4])
+            if OBJ_MSO_DEBUG:
+                fprintf(self.logfile, "pos=%d ft=0x%04X cb=%d\n", pos, ft, cb)
+                hex_char_dump(data, pos, cb + 4, base=0, fout=self.logfile)
+            if pos == 0 and not (ft == 0x15 and cb == 18):
+                if self.verbosity:
+                    fprintf(self.logfile, "*** WARNING Ignoring antique or corrupt OBJECT record\n")
+                return None
+            if ft == 0x15: # ftCmo ... s/b first
+                assert pos == 0
+                o.type, o.id, option_flags = unpack('<HHH', data[pos+4:pos+10])
+                upkbits(o, option_flags, (
+                    ( 0, 0x0001, 'locked'),
+                    ( 4, 0x0010, 'printable'),
+                    ( 8, 0x0100, 'autofilter'), # not documented in Excel 97 dev kit
+                    ( 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] == b'\0' * (data_len - pos):
+                    # ignore "optional reserved" data at end of record
+                    break
+                msg = "Unexpected data at end of OBJECT record"
+                fprintf(self.logfile, "*** ERROR %s\n" % msg)
+                hex_char_dump(data, pos, data_len - pos, base=0, fout=self.logfile)
+                raise XLRDError(msg)
+            elif ft == 0x0C: # Scrollbar
+                values = unpack('<5H', data[pos+8:pos+18])
+                for value, tag in zip(values, ('value', 'min', 'max', 'inc', 'page')):
+                    setattr(o, 'scrollbar_' + tag, value)
+            elif ft == 0x0D: # "Notes structure" [used for cell comments]
+                # not documented in Excel 97 dev kit
+                if OBJ_MSO_DEBUG: fprintf(self.logfile, "*** OBJ record has ft==0x0D 'notes' structure\n")
+            elif ft == 0x13: # list box data
+                if o.autofilter: # non standard exit. NOT documented
+                    break
+            else:
+                pass
+            pos += cb + 4
+        else:
+            # didn't break out of while loop
+            pass
+        if OBJ_MSO_DEBUG:
+            o.dump(self.logfile, header="=== MSOBj ===", footer= " ")
+        return o
+
+    def handle_note(self, data, txos):
+        if OBJ_MSO_DEBUG:
+            fprintf(self.logfile, '... NOTE record ...\n')
+            hex_char_dump(data, 0, len(data), base=0, fout=self.logfile)
+        o = Note()
+        data_len = len(data)
+        if self.biff_version < 80:
+            o.rowx, o.colx, expected_bytes = unpack('<HHH', data[:6])
+            nb = len(data) - 6
+            assert nb <= expected_bytes
+            pieces = [data[6:]]
+            expected_bytes -= nb
+            while expected_bytes > 0:
+                rc2, data2_len, data2 = self.book.get_record_parts()
+                assert rc2 == XL_NOTE
+                dummy_rowx, nb = unpack('<H2xH', data2[:6])
+                assert dummy_rowx == 0xFFFF
+                assert nb == data2_len - 6
+                pieces.append(data2[6:])
+                expected_bytes -= nb
+            assert expected_bytes == 0
+            enc = self.book.encoding or self.book.derive_encoding()
+            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 = UNICODE_LITERAL('')
+            o._object_id = None
+            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])
+        o.show = (option_flags >> 1) & 1
+        o.row_hidden = (option_flags >> 7) & 1
+        o.col_hidden = (option_flags >> 8) & 1
+        # XL97 dev kit book says NULL [sic] bytes padding between string count and string data
+        # to ensure that string is word-aligned. Appears to be nonsense.
+        o.author, endpos = unpack_unicode_update_pos(data, 8, lenlen=2)
+        # There is a random/undefined byte after the author string (not counted in the
+        # string length).
+        # Issue 4 on github: Google Spreadsheet doesn't write the undefined byte.
+        assert (data_len - endpos) in (0, 1)
+        if OBJ_MSO_DEBUG:
+            o.dump(self.logfile, header="=== Note ===", footer= " ")
+        txo = txos.get(o._object_id)
+        if txo:
+            o.text = txo.text
+            o.rich_text_runlist = txo.rich_text_runlist
+            self.cell_note_map[o.rowx, o.colx] = o
+
+    def handle_txo(self, data):
+        if self.biff_version < 80:
+            return
+        o = MSTxo()
+        fmt = '<HH6sHHH'
+        fmtsize = calcsize(fmt)
+        option_flags, o.rot, controlInfo, cchText, cbRuns, o.ifntEmpty = unpack(fmt, data[:fmtsize])
+        o.fmla = data[fmtsize:]
+        upkbits(o, option_flags, (
+            ( 3, 0x000E, 'horz_align'),
+            ( 6, 0x0070, 'vert_align'),
+            ( 9, 0x0200, 'lock_text'),
+            (14, 0x4000, 'just_last'),
+            (15, 0x8000, 'secret_edit'),
+        ))
+        totchars = 0
+        o.text = UNICODE_LITERAL('')
+        while totchars < cchText:
+            rc2, data2_len, data2 = self.book.get_record_parts()
+            assert rc2 == XL_CONTINUE
+            if OBJ_MSO_DEBUG:
+                hex_char_dump(data2, 0, data2_len, base=0, fout=self.logfile)
+            nb = BYTES_ORD(data2[0]) # 0 means latin1, 1 means utf_16_le
+            nchars = data2_len - 1
+            if nb:
+                assert nchars % 2 == 0
+                nchars //= 2
+            utext, endpos = unpack_unicode_update_pos(data2, 0, known_len=nchars)
+            assert endpos == data2_len
+            o.text += utext
+            totchars += nchars
+        o.rich_text_runlist = []
+        totruns = 0
+        while totruns < cbRuns: # counts of BYTES, not runs
+            rc3, data3_len, data3 = self.book.get_record_parts()
+            # print totruns, cbRuns, rc3, data3_len, repr(data3)
+            assert rc3 == XL_CONTINUE
+            assert data3_len % 8 == 0
+            for pos in xrange(0, data3_len, 8):
+                run = unpack('<HH4x', data3[pos:pos+8])
+                o.rich_text_runlist.append(run)
+                totruns += 8
+        # remove trailing entries that point to the end of the string
+        while o.rich_text_runlist and o.rich_text_runlist[-1][0] == cchText:
+            del o.rich_text_runlist[-1]
+        if OBJ_MSO_DEBUG:
+            o.dump(self.logfile, header="=== MSTxo ===", footer= " ")
+            print(o.rich_text_runlist, file=self.logfile)
+        return o
+
+    def handle_feat11(self, data):
+        if not OBJ_MSO_DEBUG:
+            return
+        # rt: Record type; this matches the BIFF rt in the first two bytes of the record; =0872h
+        # grbitFrt: FRT cell reference flag (see table below for details)
+        # Ref0: Range reference to a worksheet cell region if grbitFrt=1 (bitFrtRef). Otherwise blank.
+        # isf: Shared feature type index =5 for Table
+        # fHdr: =0 since this is for feat not feat header
+        # reserved0: Reserved for future use =0 for Table
+        # cref: Count of ref ranges this feature is on
+        # cbFeatData: Count of byte for the current feature data.
+        # reserved1: =0 currently not used
+        # Ref1: Repeat of Ref0. UNDOCUMENTED
+        rt, grbitFrt, Ref0, isf, fHdr, reserved0, cref, cbFeatData, reserved1, Ref1 = unpack('<HH8sHBiHiH8s', data[0:35])
+        assert reserved0 == 0
+        assert reserved1 == 0
+        assert isf == 5
+        assert rt == 0x872
+        assert fHdr == 0
+        assert Ref1 == Ref0
+        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
+        # idList: The ID of the Table (unique per worksheet)
+        # crwHeader: How many header/title rows the Table has at the top
+        # crwTotals: How many total rows the Table has at the bottom
+        # idFieldNext: Next id to try when assigning a unique id to a new field
+        # cbFSData: The size of the Fixed Data portion of the Table data structure.
+        # rupBuild: the rupBuild that generated the record
+        # unusedShort: UNUSED short that can be used later. The value is reserved during round-tripping.
+        # listFlags: Collection of bit flags: (see listFlags' bit setting table below for detail.)
+        # lPosStmCache: Table data stream position of cached data
+        # cbStmCache: Count of bytes of cached data
+        # cchStmCache: Count of characters of uncompressed cached data in the stream
+        # lem: Table edit mode (see List (Table) Editing Mode (lem) setting table below for details.)
+        # rgbHashParam: Hash value for SharePoint Table
+        # cchName: Count of characters in the Table name string rgbName
+        (lt, idList, crwHeader, crwTotals, idFieldNext, cbFSData,
+        rupBuild, unusedShort, listFlags, lPosStmCache, cbStmCache,
+        cchStmCache, lem, rgbHashParam, cchName) = unpack('<iiiiiiHHiiiii16sH', data[35:35+66])
+        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), 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
+
+
+class Note(BaseObject):
+    """
+    Represents a user "comment" or "note".
+    Note objects are accessible through :attr:`Sheet.cell_note_map`.
+
+    .. versionadded:: 0.7.2
+    """
+
+    #: Author of note
+    author = UNICODE_LITERAL('')
+
+    #: ``True`` if the containing column is hidden
+    col_hidden = 0
+
+    #: Column index
+    colx = 0
+
+    #: List of ``(offset_in_string, font_index)`` tuples.
+    #: Unlike :attr:`Sheet.rich_text_runlist_map`, the first offset should
+    #: always be 0.
+    rich_text_runlist = None
+
+    #: True if the containing row is hidden
+    row_hidden = 0
+
+    #: Row index
+    rowx = 0
+
+    #: True if note is always shown
+    show = 0
+
+    #: Text of the note
+    text = UNICODE_LITERAL('')
+
+
+class Hyperlink(BaseObject):
+    """
+    Contains the attributes of a hyperlink.
+    Hyperlink objects are accessible through :attr:`Sheet.hyperlink_list`
+    and :attr:`Sheet.hyperlink_map`.
+
+    .. versionadded:: 0.7.2
+    """
+
+    #: Index of first row
+    frowx = None
+
+    #: Index of last row
+    lrowx = None
+
+    #: Index of first column
+    fcolx = None
+
+    #: Index of last column
+    lcolx = None
+
+    #: Type of hyperlink. Unicode string, one of 'url', 'unc',
+    #: 'local file', 'workbook', 'unknown'
+    type = None
+
+    #: The URL or file-path, depending in the type. Unicode string, except
+    #: in the rare case of a local but non-existent file with non-ASCII
+    #: characters in the name, in which case only the "8.3" filename is
+    #: available, as a :class:`bytes` (3.x) or :class:`str` (2.x) string,
+    #: *with unknown encoding.*
+    url_or_path = None
+
+    #: Description.
+    #: This is displayed in the cell,
+    #: and should be identical to the cell value. Unicode string, or ``None``.
+    #: It seems impossible NOT to have a description created by the Excel UI.
+    desc = None
+
+    #: Target frame. Unicode string.
+    #:
+    #: .. note::
+    #:   No cases of this have been seen in the wild.
+    #:   It seems impossible to create one in the Excel UI.
+    target = None
+
+    #: The piece after the "#" in
+    #: "http://docs.python.org/library#struct_module", or the ``Sheet1!A1:Z99``
+    #: part when type is "workbook".
+    textmark = None
+
+    #: The text of the "quick tip" displayed when the cursor
+    #: hovers over the hyperlink.
+    quicktip = None
+
+# === helpers ===
+
+def unpack_RK(rk_str):
+    flags = BYTES_ORD(rk_str[0])
+    if flags & 2:
+        # There's a SIGNED 30-bit integer in there!
+        i,  = unpack('<i', rk_str)
+        i >>= 2 # div by 4 to drop the 2 flag bits
+        if flags & 1:
+            return i / 100.0
+        return float(i)
+    else:
+        # It's the most significant 30 bits of an IEEE 754 64-bit FP number
+        d, = unpack('<d', b'\0\0\0\0' + BYTES_LITERAL(chr(flags & 252)) + rk_str[1:4])
+        if flags & 1:
+            return d / 100.0
+        return d
+
+##### =============== Cell ======================================== #####
+
+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.
+}
+
+ctype_text = {
+    XL_CELL_EMPTY: 'empty',
+    XL_CELL_TEXT: 'text',
+    XL_CELL_NUMBER: 'number',
+    XL_CELL_DATE: 'xldate',
+    XL_CELL_BOOLEAN: 'bool',
+    XL_CELL_ERROR: 'error',
+    XL_CELL_BLANK: 'blank',
+}
+
+
+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
+
+        <table border="1" cellpadding="7">
+        <tr>
+        <th>Type symbol</th>
+        <th>Type number</th>
+        <th>Python value</th>
+        </tr>
+        <tr>
+        <td>XL_CELL_EMPTY</td>
+        <td align="center">0</td>
+        <td>empty string ''</td>
+        </tr>
+        <tr>
+        <td>XL_CELL_TEXT</td>
+        <td align="center">1</td>
+        <td>a Unicode string</td>
+        </tr>
+        <tr>
+        <td>XL_CELL_NUMBER</td>
+        <td align="center">2</td>
+        <td>float</td>
+        </tr>
+        <tr>
+        <td>XL_CELL_DATE</td>
+        <td align="center">3</td>
+        <td>float</td>
+        </tr>
+        <tr>
+        <td>XL_CELL_BOOLEAN</td>
+        <td align="center">4</td>
+        <td>int; 1 means TRUE, 0 means FALSE</td>
+        </tr>
+        <tr>
+        <td>XL_CELL_ERROR</td>
+        <td align="center">5</td>
+        <td>int representing internal Excel codes; for a text representation,
+        refer to the supplied dictionary error_text_from_code</td>
+        </tr>
+        <tr>
+        <td>XL_CELL_BLANK</td>
+        <td align="center">6</td>
+        <td>empty string ''. Note: this type will appear only when
+        open_workbook(..., formatting_info=True) is used.</td>
+        </tr>
+        </table>
+    """
+
+    __slots__ = ['ctype', 'value', 'xf_index']
+
+    def __init__(self, ctype, value, xf_index=None):
+        self.ctype = ctype
+        self.value = value
+        self.xf_index = xf_index
+
+    def __repr__(self):
+        if self.xf_index is None:
+            return "%s:%r" % (ctype_text[self.ctype], self.value)
+        else:
+            return "%s:%r (XF:%r)" % (ctype_text[self.ctype], self.value, self.xf_index)
+
+empty_cell = Cell(XL_CELL_EMPTY, UNICODE_LITERAL(''))
+
+##### =============== Colinfo and Rowinfo ============================== #####
+
+
+class Colinfo(BaseObject):
+    """
+    Width and default formatting information that applies to one or
+    more columns in a sheet. Derived from ``COLINFO`` records.
+
+    Here is the default hierarchy for width, according to the OOo docs:
+
+      In BIFF3, if a ``COLINFO`` record is missing for a column,
+      the width specified in the record ``DEFCOLWIDTH`` is used instead.
+
+      In BIFF4-BIFF7, the width set in this ``COLINFO`` record is only used,
+      if the corresponding bit for this column is cleared in the ``GCW``
+      record, otherwise the column width set in the ``DEFCOLWIDTH`` record
+      is used (the ``STANDARDWIDTH`` record is always ignored in this case [#f1]_).
+
+      In BIFF8, if a ``COLINFO`` record is missing for a column,
+      the width specified in the record ``STANDARDWIDTH`` is used.
+      If this ``STANDARDWIDTH`` record is also missing,
+      the column width of the record ``DEFCOLWIDTH`` is used instead.
+
+    .. [#f1] The docs on the ``GCW`` record say this:
+
+      If a bit is set, the corresponding column uses the width set in the
+      ``STANDARDWIDTH`` record. If a bit is cleared, the corresponding column
+      uses the width set in the ``COLINFO`` record for this column.
+
+      If a bit is set, and the worksheet does not contain the ``STANDARDWIDTH``
+      record, or if the bit is cleared, and the worksheet does not contain the
+      ``COLINFO`` record, the ``DEFCOLWIDTH`` record of the worksheet will be
+      used instead.
+
+    xlrd goes with the GCW version of the story.
+    Reference to the source may be useful: see
+    :meth:`Sheet.computed_column_width`.
+
+    .. versionadded:: 0.6.1
+    """
+
+    #: Width of the column in 1/256 of the width of the zero character,
+    #: using default font (first ``FONT`` record in the file).
+    width = 0
+
+    #: XF index to be used for formatting empty cells.
+    xf_index = -1
+
+    #: 1 = column is hidden
+    hidden = 0
+
+    #: Value of a 1-bit flag whose purpose is unknown
+    #: but is often seen set to 1
+    bit1_flag = 0
+
+    #: Outline level of the column, in ``range(7)``.
+    #: (0 = no outline)
+    outline_level = 0
+
+    #: 1 = column is collapsed
+    collapsed = 0
+
+_USE_SLOTS = 1
+
+
+class Rowinfo(BaseObject):
+    """
+    Height and default formatting information that applies to a row in a sheet.
+    Derived from ``ROW`` records.
+
+    .. versionadded:: 0.6.1
+    """
+
+    if _USE_SLOTS:
+        __slots__ = (
+            "height",
+            "has_default_height",
+            "outline_level",
+            "outline_group_starts_ends",
+            "hidden",
+            "height_mismatch",
+            "has_default_xf_index",
+            "xf_index",
+            "additional_space_above",
+            "additional_space_below",
+        )
+
+    def __init__(self):
+        #: Height of the row, in twips. One twip == 1/20 of a point.
+        self.height = None
+
+        #: 0 = Row has custom height; 1 = Row has default height.
+        self.has_default_height = None
+
+        #: Outline level of the row (0 to 7)
+        self.outline_level = None
+
+        #: 1 = Outline group starts or ends here (depending on where the
+        #: outline buttons are located, see ``WSBOOL`` record, which is not
+        #: parsed by xlrd), *and* is collapsed.
+        self.outline_group_starts_ends = None
+
+        #: 1 = Row is hidden (manually, or by a filter or outline group)
+        self.hidden = None
+
+        #: 1 = Row height and default font height do not match.
+        self.height_mismatch = None
+
+        #: 1 = the xf_index attribute is usable; 0 = ignore it.
+        self.has_default_xf_index = None
+
+        #: Index to default :class:`~xlrd.formatting.XF` record for empty cells
+        #: in this row. Don't use this if ``has_default_xf_index == 0``.
+        self.xf_index = None
+
+        #: This flag is set if the upper border of at least one cell in this
+        #: row or if the lower border of at least one cell in the row above is
+        #: formatted with a thick line style. Thin and medium line styles are
+        #: not taken into account.
+        self.additional_space_above = None
+
+        #: This flag is set if the lower border of at least one cell in this row
+        #: or if the upper border of at least one cell in the row below is
+        #: formatted with a medium or thick line style. Thin line styles are not
+        #: taken into account.
+        self.additional_space_below = None
+
+    def __getstate__(self):
+        return (
+            self.height,
+            self.has_default_height,
+            self.outline_level,
+            self.outline_group_starts_ends,
+            self.hidden,
+            self.height_mismatch,
+            self.has_default_xf_index,
+            self.xf_index,
+            self.additional_space_above,
+            self.additional_space_below,
+        )
+
+    def __setstate__(self, state):
+        (
+            self.height,
+            self.has_default_height,
+            self.outline_level,
+            self.outline_group_starts_ends,
+            self.hidden,
+            self.height_mismatch,
+            self.has_default_xf_index,
+            self.xf_index,
+            self.additional_space_above,
+            self.additional_space_below,
+        ) = state

+ 53 - 0
desktop/core/ext-py/xlrd-2.0.1/xlrd/timemachine.py

@@ -0,0 +1,53 @@
+##
+# <p>Copyright (c) 2006-2012 Stephen John Machin, Lingfo Pty Ltd</p>
+# <p>This module is part of the xlrd package, which is released under a BSD-style licence.</p>
+##
+
+# timemachine.py -- adaptation for single codebase.
+# Currently supported: 2.6 to 2.7, 3.2+
+# usage: from timemachine import *
+
+from __future__ import print_function
+
+import sys
+
+python_version = sys.version_info[:2] # e.g. version 2.6 -> (2, 6)
+
+if python_version >= (3, 0):
+    # Python 3
+    BYTES_LITERAL = lambda x: x.encode('latin1')
+    UNICODE_LITERAL = lambda x: x
+    BYTES_ORD = lambda byte: byte
+    from io import BytesIO as BYTES_IO
+    def fprintf(f, fmt, *vargs):
+        fmt = fmt.replace("%r", "%a")
+        if fmt.endswith('\n'):
+            print(fmt[:-1] % vargs, file=f)
+        else:
+            print(fmt % vargs, end=' ', file=f)
+    EXCEL_TEXT_TYPES = (str, bytes, bytearray) # xlwt: isinstance(obj, EXCEL_TEXT_TYPES)
+    REPR = ascii
+    xrange = range
+    unicode = lambda b, enc: b.decode(enc)
+    ensure_unicode = lambda s: s
+    unichr = chr
+else:
+    # Python 2
+    BYTES_LITERAL = lambda x: x
+    UNICODE_LITERAL = lambda x: x.decode('latin1')
+    BYTES_ORD = ord
+    from cStringIO import StringIO as BYTES_IO
+    def fprintf(f, fmt, *vargs):
+        if fmt.endswith('\n'):
+            print(fmt[:-1] % vargs, file=f)
+        else:
+            print(fmt % vargs, end=' ', file=f)
+    try:
+        EXCEL_TEXT_TYPES = basestring # xlwt: isinstance(obj, EXCEL_TEXT_TYPES)
+    except NameError:
+        EXCEL_TEXT_TYPES = (str, unicode)
+    REPR = repr
+    xrange = xrange
+    # following used only to overcome 2.x ElementTree gimmick which
+    # returns text as `str` if it's ascii, otherwise `unicode`
+    ensure_unicode = unicode # used only in xlsx.py

+ 248 - 0
desktop/core/ext-py/xlrd-2.0.1/xlrd/xldate.py

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

+ 11 - 0
desktop/core/ext-py/xlwt-1.3.0/.gitignore

@@ -0,0 +1,11 @@
+*.pyc
+/dist
+*.egg-info
+/build
+/tests/output
+.coverage
+/docs/_build
+.tox
+/bin
+/include
+/lib

+ 34 - 0
desktop/core/ext-py/xlwt-1.3.0/.travis.yml

@@ -0,0 +1,34 @@
+# for container-y goodness:
+sudo: false
+
+language: python
+
+python:
+  - "3.6"
+  - "3.5"
+  - "3.4"
+  - "3.3"
+  - "2.7"
+
+# command to install dependencies
+install: 
+- "pip install -U pip setuptools"
+- "pip install -Ur requirements.txt"
+
+# command to run tests, e.g. python setup.py test
+script: nosetests --with-cov --cov=xlwt
+
+after_success:
+  - coveralls
+
+deploy:
+  provider: pypi
+  user: chrisw
+  password:
+    secure: HZ9DRsLpeNMBZc3Sjj2X/KF/fJxaDjINqbeUzJ7FnqcJ5A5ECDHZ/2Bxx/crdpfXwqHDPEcZvatu+LhtpBFu+HCUFAc00V0qvmq34RZDes9WNmnh5Kx97kXxdN0scoM1V7JXeDffhEUZJkL6EQJfJqUWvl9nq4SnC5wOlJBR3QE=
+  on:
+    tags: true
+    repo: python-excel/xlwt
+    python: "3.5"
+  skip_cleanup: true
+  distributions: "sdist bdist_wheel"

+ 111 - 0
desktop/core/ext-py/xlwt-1.3.0/PKG-INFO

@@ -0,0 +1,111 @@
+Metadata-Version: 1.1
+Name: xlwt
+Version: 1.3.0
+Summary: Library to create spreadsheet files compatible with MS Excel 97/2000/XP/2003 XLS files, on any platform, with Python 2.6, 2.7, 3.3+
+Home-page: http://www.python-excel.org/
+Author: John Machin
+Author-email: sjmachin@lexicon.net
+License: BSD
+Download-URL: https://pypi.python.org/pypi/xlwt
+Description: |Travis|_ |Coveralls|_ |Docs|_ |PyPI|_
+        
+        .. |Travis| image:: https://api.travis-ci.org/python-excel/xlwt.svg?branch=master
+        .. _Travis: https://travis-ci.org/python-excel/xlwt
+        
+        .. |Coveralls| image:: https://coveralls.io/repos/python-excel/xlwt/badge.svg?branch=master
+        .. _Coveralls: https://coveralls.io/r/python-excel/xlwt?branch=master
+        
+        .. |Docs| image:: https://readthedocs.org/projects/xlwt/badge/?version=latest
+        .. _Docs: https://xlwt.readthedocs.org/en/latest/
+        
+        .. |PyPI| image:: https://badge.fury.io/py/xlwt.svg
+        .. _PyPI: https://badge.fury.io/py/xlwt
+            
+        xlwt
+        ====
+        
+        This is a library for developers to use to generate
+        spreadsheet files compatible with Microsoft Excel versions 95 to 2003.
+        
+        The package itself is pure Python with no dependencies on modules or packages
+        outside the standard Python distribution.
+        
+        Please read this before using this package:
+        https://groups.google.com/d/msg/python-excel/P6TjJgFVjMI/g8d0eWxTBQAJ
+        
+        Installation
+        ============
+        
+        Do the following in your virtualenv::
+        
+          pip install xlwt
+        
+        Quick start
+        ===========
+        
+        .. code-block:: python
+        
+            import xlwt
+            from datetime import datetime
+        
+            style0 = xlwt.easyxf('font: name Times New Roman, color-index red, bold on',
+                num_format_str='#,##0.00')
+            style1 = xlwt.easyxf(num_format_str='D-MMM-YY')
+        
+            wb = xlwt.Workbook()
+            ws = wb.add_sheet('A Test Sheet')
+        
+            ws.write(0, 0, 1234.56, style0)
+            ws.write(1, 0, datetime.now(), style1)
+            ws.write(2, 0, 1)
+            ws.write(2, 1, 1)
+            ws.write(2, 2, xlwt.Formula("A3+B3"))
+        
+            wb.save('example.xls')
+        
+        
+        Documentation
+        =============
+        
+        Documentation can be found in the ``docs`` directory of the xlwt package.
+        If these aren't sufficient, please consult the code in the
+        examples directory and the source code itself.
+        
+        The latest documentation can also be found at:
+        https://xlwt.readthedocs.org/en/latest/
+        
+        Problems?
+        =========
+        Try the following in this order:
+        
+        - Read the source
+        
+        - Ask a question on https://groups.google.com/group/python-excel/
+        
+        Acknowledgements
+        ================
+        
+        xlwt is a fork of the pyExcelerator package, which was developed by
+        Roman V. Kiseliov. This product includes software developed by
+        Roman V. Kiseliov <roman@kiseliov.ru>.
+        
+        xlwt uses ANTLR v 2.7.7 to generate its formula compiler.
+        
+Keywords: xls excel spreadsheet workbook worksheet pyExcelerator
+Platform: Platform Independent
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
+Classifier: Topic :: Database
+Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6

+ 83 - 0
desktop/core/ext-py/xlwt-1.3.0/README.rst

@@ -0,0 +1,83 @@
+|Travis|_ |Coveralls|_ |Docs|_ |PyPI|_
+
+.. |Travis| image:: https://api.travis-ci.org/python-excel/xlwt.svg?branch=master
+.. _Travis: https://travis-ci.org/python-excel/xlwt
+
+.. |Coveralls| image:: https://coveralls.io/repos/python-excel/xlwt/badge.svg?branch=master
+.. _Coveralls: https://coveralls.io/r/python-excel/xlwt?branch=master
+
+.. |Docs| image:: https://readthedocs.org/projects/xlwt/badge/?version=latest
+.. _Docs: https://xlwt.readthedocs.org/en/latest/
+
+.. |PyPI| image:: https://badge.fury.io/py/xlwt.svg
+.. _PyPI: https://badge.fury.io/py/xlwt
+    
+xlwt
+====
+
+This is a library for developers to use to generate
+spreadsheet files compatible with Microsoft Excel versions 95 to 2003.
+
+The package itself is pure Python with no dependencies on modules or packages
+outside the standard Python distribution.
+
+Please read this before using this package:
+https://groups.google.com/d/msg/python-excel/P6TjJgFVjMI/g8d0eWxTBQAJ
+
+Installation
+============
+
+Do the following in your virtualenv::
+
+  pip install xlwt
+
+Quick start
+===========
+
+.. code-block:: python
+
+    import xlwt
+    from datetime import datetime
+
+    style0 = xlwt.easyxf('font: name Times New Roman, color-index red, bold on',
+        num_format_str='#,##0.00')
+    style1 = xlwt.easyxf(num_format_str='D-MMM-YY')
+
+    wb = xlwt.Workbook()
+    ws = wb.add_sheet('A Test Sheet')
+
+    ws.write(0, 0, 1234.56, style0)
+    ws.write(1, 0, datetime.now(), style1)
+    ws.write(2, 0, 1)
+    ws.write(2, 1, 1)
+    ws.write(2, 2, xlwt.Formula("A3+B3"))
+
+    wb.save('example.xls')
+
+
+Documentation
+=============
+
+Documentation can be found in the ``docs`` directory of the xlwt package.
+If these aren't sufficient, please consult the code in the
+examples directory and the source code itself.
+
+The latest documentation can also be found at:
+https://xlwt.readthedocs.org/en/latest/
+
+Problems?
+=========
+Try the following in this order:
+
+- Read the source
+
+- Ask a question on https://groups.google.com/group/python-excel/
+
+Acknowledgements
+================
+
+xlwt is a fork of the pyExcelerator package, which was developed by
+Roman V. Kiseliov. This product includes software developed by
+Roman V. Kiseliov <roman@kiseliov.ru>.
+
+xlwt uses ANTLR v 2.7.7 to generate its formula compiler.

+ 153 - 0
desktop/core/ext-py/xlwt-1.3.0/docs/Makefile

@@ -0,0 +1,153 @@
+# 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 <target>' where <target> 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."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/xlwt.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/xlwt.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/xlwt"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/xlwt"
+	@echo "# devhelp"
+
+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."

+ 29 - 0
desktop/core/ext-py/xlwt-1.3.0/docs/api.rst

@@ -0,0 +1,29 @@
+API Reference
+=============
+
+.. automodule:: xlwt.Workbook
+   :members:
+
+.. automodule:: xlwt.Worksheet
+   :members:
+
+.. automodule:: xlwt.Row
+   :members:
+
+.. automodule:: xlwt.Column
+   :members:
+
+.. automodule:: xlwt.Cell
+   :members:
+
+.. automodule:: xlwt.Formatting
+   :members:
+
+.. automodule:: xlwt.Style
+   :members:
+
+.. automodule:: xlwt.Bitmap
+   :members:
+
+.. automodule:: xlwt.CompoundDoc
+   :members:

+ 173 - 0
desktop/core/ext-py/xlwt-1.3.0/docs/changes.rst

@@ -0,0 +1,173 @@
+Changes
+=======
+
+.. currentmodule:: xlwt
+
+1.3.0 (22 August 2017)
+----------------------
+
+- Officially support Python 3.6, drop support for 2.6.
+
+- Fix bytes/string type mismatch in :func:`upack2rt` on python 3.
+
+- Packaging and code style tweaks.
+
+- Use generator expressions to avoid unnecessary lists in memory.
+
+Thanks to the following for their contributions to this release:
+
+- Jon Dufresne
+- Bill Adams
+
+1.2.0 (4 January 2017)
+----------------------
+
+- Remove ``LOCALE`` from regular expression that caused
+  :class:`DeprecationWarning` that become an exception in Python 3.6
+
+- Add :meth:`Workbook.sheet_index` helper.
+
+- :meth:`Workbook.get_sheet` now takes either a string name or an integer
+  index.
+
+1.1.2 (9 June 2016)
+-------------------
+
+- Fix failure in style compression under Python 3.
+
+- Officially support Python 3.5
+
+- Documentation tweaks.
+
+1.1.1 (2 June 2016)
+-------------------
+
+- Fix release problems.
+
+1.1.0 (2 June 2016)
+-------------------
+
+- Fix SST BIFF record in Python 3.
+
+- Fix for writing :class:`ExternSheetRecord` in Python 3.
+
+- Add the ability to insert bitmap images from buffers as well as files.
+
+- Official support for Python 3.5.
+
+Thanks to "thektulu" and Lele Gaifax for the Python 3 fixes.
+Thanks to Ross Golder for the support for inserting images from buffers.
+
+1.0.0 (15 April 2015)
+---------------------
+
+- Python 3 support.
+
+- Initial set of unit tests.
+
+- An initial set of Sphinx documentation.
+
+- Move to setuptools for packaging.
+
+- Wire up Travis, Coveralls and ReadTheDocs.
+
+- Allow longs as row indexes.
+
+Big thanks to Thomas Kluyver for his work on Python 3 support, Manfred Moitzi
+for donating his unit tests.
+
+Belated thanks to Landon Jurgens for his help on converting the documentation
+to Sphinx.
+
+0.7.5 (5 April 2013)
+--------------------
+
+- Fixes a bug that could cause a corrupt SST in .xls files written by a
+  wide-unicode Python build.
+
+- A :class:`ValueError` is now raised immediately if an attempt is made to set
+  column width to other than an int in ``range(65536)``
+
+- Added the ability to set a custom RGB colour in the palette to use for
+  colours. Thanks to Alan Rotman for the work, although this could really
+  use an example in the examples folder...
+
+- Fixed an issue trying to set a diagonal border using easyxf. Thanks to
+  Neil Etheridge for the fix.
+
+- Fixed a regression from 0.7.2 when writing sheets with frozen panes.
+
+0.7.4 (13 April 2012)
+---------------------
+
+- Python 2.3 to 2.7 are now the officially supported versions, no Python
+  3 yet, sorry.
+
+- The ``datemode`` in an xlwt :class:`Workbook` can be set to 1904 by doing
+  ``workbook.dates_1904 = 1`` and is written to the output file. However the
+  datemode was not being reflected in conversions from
+  :class:`datetime.datetime` and :class:`datetime.date` objects to floats for
+  output, resulting in dates that were 4 years too high when seen in Excel.
+
+0.7.3 (21 February 2012)
+------------------------
+
+- Added user_set and best_fit attributes to Column class.
+
+- Fixed an ``[Errno 0] Error`` raised when :meth:`Worksheet.flush_row_data` was
+  called after :meth:`Workbook.save`
+
+- Fixed an error on Windows that occurred when writing large blocks to
+  files.
+
+- Added the ability to write rich text cells
+
+- Fixed a bug when writing ``MULBLANK`` records on big-endian platforms.
+
+- allow the ``active_pane`` on worksheets to be specified
+
+- added support for zoom (magn) factors and improved possibilities when
+  generating split panes
+
+0.7.2 (1 June 2009)
+-------------------
+
+- Added function Utils.rowcol_pair_to_cellrange.
+  ``(0, 0, 65535, 255) -> "A1:IV65536"``
+
+- Removed :class:`Worksheet` property ``show_empty_as_zero``,
+  and added attribute :attr:`~Worksheet.show_zero_values`
+  (default: ``1 == True``).
+
+- Fixed formula code generation problem with formulas
+  including MAX/SUM/etc functions with arguments like A1+123.
+
+- Added .pattern_examples.xls and put a pointer to it
+  in the easyxf part of Style.py.
+
+- Fixed Row.set_cell_formula() bug introduced in 0.7.1.
+
+- Fixed bug(?) with SCL/magnification handling causing(?) Excel
+  to raise a dialogue box if sheet is set to open in page preview mode
+  and user then switches to normal view.
+
+- Added color and colour as synonyms for font.colour_index in easyxf.
+
+- Removed unused attribute Row.__has_default_format.
+
+0.7.1 (4 March 2009)
+--------------------
+
+See source control for changes made.
+
+0.7.0 (19 September 2008)
+-------------------------
+
+- Fixed more bugs and added more various new bits of functionality
+
+0.7.0a4 (8 October 2007)
+------------------------
+
+- fork of pyExcelerator, released to python-excel.
+
+- Fixed various bugs in pyExcelerator and added various new bits of functionality

+ 22 - 0
desktop/core/ext-py/xlwt-1.3.0/docs/conf.py

@@ -0,0 +1,22 @@
+import datetime
+import os, pkginfo
+
+on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
+pkg_info = pkginfo.Develop(os.path.join(os.path.dirname(__file__), os.pardir))
+
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']
+source_suffix = '.rst'
+master_doc = 'index'
+project = u'xlwt'
+copyright = '2002-%s xlwt contributors' % datetime.datetime.now().year
+version = release = pkg_info.version
+exclude_patterns = ['_build']
+pygments_style = 'sphinx'
+
+if on_rtd:
+    html_theme = 'default'
+else:
+    html_theme = 'classic'
+
+htmlhelp_basename = project+'doc'
+intersphinx_mapping = {'python': ('https://docs.python.org', None)}

+ 65 - 0
desktop/core/ext-py/xlwt-1.3.0/docs/development.rst

@@ -0,0 +1,65 @@
+Development
+===========
+
+.. highlight:: bash
+
+This package is developed using continuous integration which can be
+found here:
+
+https://travis-ci.org/python-excel/xlwt
+
+If you wish to contribute to this project, then you should fork the
+repository found here:
+
+https://github.com/python-excel/xlwt
+
+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 -Ur requirements.txt
+  $ bin/pip install -e .
+
+Running the tests
+-----------------
+
+Once you've set up a virtualenv, the tests can be run as follows::
+
+  $ bin/nosetests
+
+To run tests on all the versions of Python that are supported, you can do::
+
+  $ bin/tox
+
+If you change the supported python versions in ``.travis.yml``, please remember
+to do the following to update ``tox.ini``::
+
+  $ bin/panci --to=tox .travis.yml > tox.ini
+
+Building the documentation
+--------------------------
+
+The Sphinx documentation is built by doing the following, having activated
+the virtualenv above, from the directory containing setup.py::
+
+  $ cd docs
+  $ make html
+
+Making a release
+----------------
+
+To make a release, just update the version in ``xlwt/__init__.py``,
+update the change log, tag it
+and push to https://github.com/python-excel/xlwt
+and Travis CI should take care of the rest.
+
+Once the above is done, make sure to go to
+https://readthedocs.org/projects/xlwt/versions/
+and make sure the new release is marked as an Active Version.

+ 35 - 0
desktop/core/ext-py/xlwt-1.3.0/docs/index.rst

@@ -0,0 +1,35 @@
+xlwt documentation
+==================
+
+xlwt is a library for writing data and formatting information to older Excel
+files (ie: .xls)
+
+Documentation is sparse, please see the API reference or code for help:
+
+.. toctree::
+   :maxdepth: 1
+
+   api.rst
+
+Perhaps more useful is to consult the `tutorial`__ and the examples in the
+`examples` folder of the distribution.
+
+__ https://github.com/python-excel/tutorial
+
+For details of how to install the package or get involved in its
+development, please see the sections below:
+
+.. toctree::
+   :maxdepth: 1
+
+   installation.rst
+   development.rst
+   changes.rst
+   licenses.rst
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`

+ 18 - 0
desktop/core/ext-py/xlwt-1.3.0/docs/installation.rst

@@ -0,0 +1,18 @@
+Installation Instructions
+=========================
+
+If you want to experiment with xlwt, the easiest way to
+install it is to do the following in a virtualenv::
+
+  pip install xlwt
+
+If your package uses setuptools and you decide to use xlwt,
+then you should add it as a requirement by adding an ``install_requires``
+parameter in your call to ``setup`` as follows:
+
+.. code-block:: python
+
+    setup(
+        # other stuff here
+        install_requires=['xlwt'],
+        )

+ 118 - 0
desktop/core/ext-py/xlwt-1.3.0/docs/licenses.rst

@@ -0,0 +1,118 @@
+Licenses
+========
+
+xlwt has various licenses that apply to the different parts of it, they are
+listed below:
+
+The license for the work John Machin has done since xlwt was created::
+
+    Portions copyright (c) 2007, 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.
+
+The licensing for the unit tests added as part of the work for Python 3
+compatibility is as follows::
+
+  Author:  mozman --<mozman@gmx.at>
+  Purpose: test_mini
+  Created: 03.12.2010
+  Copyright (C) 2010, Manfred Moitzi
+  License: BSD licence
+
+The license for pyExcelerator, from which xlwt was forked::
+
+      Copyright (C) 2005 Roman V. Kiseliov
+      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. All advertising materials mentioning features or use of this
+         software must display the following acknowledgment:
+         "This product includes software developed by
+          Roman V. Kiseliov <roman@kiseliov.ru>."
+
+      4. Redistributions of any form whatsoever must retain the following
+         acknowledgment:
+         "This product includes software developed by
+          Roman V. Kiseliov <roman@kiseliov.ru>."
+
+      THIS SOFTWARE IS PROVIDED BY Roman V. Kiseliov ``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 Roman V. Kiseliov 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.
+
+    Roman V. Kiseliov
+    Russia
+    Kursk
+    Libknecht St., 4
+
+    +7(0712)56-09-83
+
+    <roman@kiseliov.ru>
+
+Portions of xlwt.Utils are based on pyXLWriter which is licensed as follows::
+
+ Copyright (c) 2004 Evgeny Filatov <fufff@users.sourceforge.net>
+ Copyright (c) 2002-2004 John McNamara (Perl Spreadsheet::WriteExcel)
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
+ General Public License for more details:
+
+ https://www.gnu.org/licenses/lgpl.html
+
+pyXLWriter also makes reference to the PERL Spreadsheet::WriteExcel as follows::
+ 
+  This module was written/ported from PERL Spreadsheet::WriteExcel module
+  The author of the PERL Spreadsheet::WriteExcel module is John McNamara
+  <jmcnamara@cpan.org>
+

+ 190 - 0
desktop/core/ext-py/xlwt-1.3.0/docs/make.bat

@@ -0,0 +1,190 @@
+@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 ^<target^>` where ^<target^> 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" == "qthelp" (
+	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\xlwt.qhcp
+	echo.To view the help file:
+	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\xlwt.ghc
+	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

+ 36 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/big-16Mb.py

@@ -0,0 +1,36 @@
+from __future__ import print_function
+#!/usr/bin/env python
+# tries stress SST, SAT and MSAT
+
+from time import *
+from xlwt.Workbook import *
+from xlwt.Style import *
+
+style = XFStyle()
+
+wb = Workbook()
+ws0 = wb.add_sheet('0')
+
+colcount = 200 + 1
+rowcount = 6000 + 1
+
+t0 = time()
+print("\nstart: %s" % ctime(t0))
+
+print("Filling...")
+for col in xrange(colcount):
+    print("[%d]" % col, end=' ') 
+    for row in xrange(rowcount):
+        #ws0.write(row, col, "BIG(%d, %d)" % (row, col))
+        ws0.write(row, col, "BIG")
+
+t1 = time() - t0
+print("\nsince starting elapsed %.2f s" % (t1))
+
+print("Storing...")
+wb.save('big-16Mb.xls')
+
+t2 = time() - t0
+print("since starting elapsed %.2f s" % (t2))
+
+

+ 31 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/big-35Mb.py

@@ -0,0 +1,31 @@
+from __future__ import print_function
+#!/usr/bin/env python
+# tries stress SST, SAT and MSAT
+
+from time import *
+from xlwt import *
+
+style = XFStyle()
+
+wb = Workbook()
+ws0 = wb.add_sheet('0')
+
+colcount = 200 + 1
+rowcount = 6000 + 1
+
+t0 = time()
+
+for col in xrange(colcount):
+    for row in xrange(rowcount):
+        ws0.write(row, col, "BIG(%d, %d)" % (row, col))
+
+t1 = time() - t0
+print("\nsince starting elapsed %.2f s" % (t1))
+
+print("Storing...")
+wb.save('big-35Mb.xls')
+
+t2 = time() - t0
+print("since starting elapsed %.2f s" % (t2))
+
+

+ 36 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/blanks.py

@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+font0 = Font()
+font0.name = 'Times New Roman'
+font0.struck_out = True
+font0.bold = True
+
+style0 = XFStyle()
+style0.font = font0
+
+
+wb = Workbook()
+ws0 = wb.add_sheet('0')
+
+ws0.write(1, 1, 'Test', style0)
+
+for i in range(0, 0x53):
+    borders = Borders()
+    borders.left = i
+    borders.right = i
+    borders.top = i
+    borders.bottom = i
+
+    style = XFStyle()
+    style.borders = borders
+
+    ws0.write(i, 2, '', style)
+    ws0.write(i, 3, hex(i), style0)
+
+ws0.write_merge(5, 8, 6, 10, "")
+
+wb.save('blanks.xls')

+ 19 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/col_width.py

@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+__rev_id__ = """$Id$"""
+
+
+from xlwt import *
+
+w = Workbook()
+ws = w.add_sheet('Hey, Dude')
+
+for i in range(6, 80):
+    fnt = Font()
+    fnt.height = i*20
+    style = XFStyle()
+    style.font = fnt
+    ws.write(1, i, 'Test')
+    ws.col(i).width = 0x0d00 + i
+w.save('col_width.xls')

+ 10 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/country.py

@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# -*- coding: windows-1252 -*-
+# Copyright (C) 2007 John Machin
+
+from xlwt import *
+
+w = Workbook()
+w.country_code = 61
+ws = w.add_sheet('AU')
+w.save('country.xls')

+ 37 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/dates.py

@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+from datetime import datetime
+
+w = Workbook()
+ws = w.add_sheet('Hey, Dude')
+
+fmts = [
+    'M/D/YY',
+    'D-MMM-YY',
+    'D-MMM',
+    'MMM-YY',
+    'h:mm AM/PM',
+    'h:mm:ss AM/PM',
+    'h:mm',
+    'h:mm:ss',
+    'M/D/YY h:mm',
+    'mm:ss',
+    '[h]:mm:ss',
+    'mm:ss.0',
+]
+
+i = 0
+for fmt in fmts:
+    ws.write(i, 0, fmt)
+
+    style = XFStyle()
+    style.num_format_str = fmt
+
+    ws.write(i, 4, datetime.now(), style)
+
+    i += 1
+
+w.save('dates.xls')

+ 38 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/format.py

@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+font0 = Font()
+font0.name = 'Times New Roman'
+font0.struck_out = True
+font0.bold = True
+
+style0 = XFStyle()
+style0.font = font0
+
+
+wb = Workbook()
+ws0 = wb.add_sheet('0')
+
+ws0.write(1, 1, 'Test', style0)
+
+for i in range(0, 0x53):
+    fnt = Font()
+    fnt.name = 'Arial'
+    fnt.colour_index = i
+    fnt.outline = True
+
+    borders = Borders()
+    borders.left = i
+
+    style = XFStyle()
+    style.font = fnt
+    style.borders = borders
+
+    ws0.write(i, 2, 'colour', style)
+    ws0.write(i, 3, hex(i), style0)
+
+
+wb.save('format.xls')

+ 35 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/formula_names.py

@@ -0,0 +1,35 @@
+from __future__ import print_function
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+from xlwt.ExcelFormulaParser import FormulaParseException
+
+w = Workbook()
+ws = w.add_sheet('F')
+
+## This example is a little silly since the formula building is
+## so simplistic that it often fails because the generated text
+## has the wrong number of parameters for the function being
+## tested.
+
+i = 0
+succeed_count = 0
+fail_count = 0
+for n in sorted(ExcelMagic.std_func_by_name):
+    ws.write(i, 0, n)
+    text = n + "($A$1)"
+    try:
+        formula = Formula(text)
+    except FormulaParseException as e:
+        print("Could not parse %r: %s" % (text,e.args[0]))
+        fail_count += 1
+    else:
+        ws.write(i, 3, formula)
+        succeed_count += 1
+    i += 1
+
+w.save('formula_names.xls')
+
+print("succeeded with %i functions, failed with %i" % (succeed_count,fail_count))

+ 47 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/formulas.py

@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+w = Workbook()
+ws = w.add_sheet('F')
+
+ws.write(0, 0, Formula("-(1+1)"))
+ws.write(1, 0, Formula("-(1+1)/(-2-2)"))
+ws.write(2, 0, Formula("-(134.8780789+1)"))
+ws.write(3, 0, Formula("-(134.8780789e-10+1)"))
+ws.write(4, 0, Formula("-1/(1+1)+9344"))
+
+ws.write(0, 1, Formula("-(1+1)"))
+ws.write(1, 1, Formula("-(1+1)/(-2-2)"))
+ws.write(2, 1, Formula("-(134.8780789+1)"))
+ws.write(3, 1, Formula("-(134.8780789e-10+1)"))
+ws.write(4, 1, Formula("-1/(1+1)+9344"))
+
+ws.write(0, 2, Formula("A1*B1"))
+ws.write(1, 2, Formula("A2*B2"))
+ws.write(2, 2, Formula("A3*B3"))
+ws.write(3, 2, Formula("A4*B4*sin(pi()/4)"))
+ws.write(4, 2, Formula("A5%*B5*pi()/1000"))
+
+##############
+## NOTE: parameters are separated by semicolon!!!
+##############
+
+
+ws.write(5, 2, Formula("C1+C2+C3+C4+C5/(C1+C2+C3+C4/(C1+C2+C3+C4/(C1+C2+C3+C4)+C5)+C5)-20.3e-2"))
+ws.write(5, 3, Formula("C1^2"))
+ws.write(6, 2, Formula("SUM(C1;C2;;;;;C3;;;C4)"))
+ws.write(6, 3, Formula("SUM($A$1:$C$5)"))
+
+ws.write(7, 0, Formula('"lkjljllkllkl"'))
+ws.write(7, 1, Formula('"yuyiyiyiyi"'))
+ws.write(7, 2, Formula('A8 & B8 & A8'))
+ws.write(8, 2, Formula('now()'))
+
+ws.write(10, 2, Formula('TRUE'))
+ws.write(11, 2, Formula('FALSE'))
+ws.write(12, 3, Formula('IF(A1>A2;3;"hkjhjkhk")'))
+
+w.save('formulas.xls')

+ 28 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/hyperlinks.py

@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+f = Font()
+f.height = 20*72
+f.name = 'Verdana'
+f.bold = True
+f.underline = Font.UNDERLINE_DOUBLE
+f.colour_index = 4
+
+h_style = XFStyle()
+h_style.font = f
+
+w = Workbook()
+ws = w.add_sheet('F')
+
+##############
+## NOTE: parameters are separated by semicolon!!!
+##############
+
+n = "HYPERLINK"
+ws.write_merge(1, 1, 1, 10, Formula(n + '("https://www.irs.gov/pub/irs-pdf/f1000.pdf";"f1000.pdf")'), h_style)
+ws.write_merge(2, 2, 2, 25, Formula(n + '("mailto:roman.kiseliov@gmail.com?subject=pyExcelerator-feedback&Body=Hello,%20Roman!";"pyExcelerator-feedback")'), h_style)
+
+w.save("hyperlinks.xls")

+ 16 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/image.py

@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+w = Workbook()
+ws = w.add_sheet('Image')
+ws.insert_bitmap('python.bmp', 2, 2)
+
+# Also works if you already have the image bitmap data in memory...
+with open ("python.bmp", "r") as bmpfile:
+    bmpdata = bmpfile.read()
+    ws.insert_bitmap_data(bmpdata, 10, 2)
+
+w.save('image.xls')

+ 27 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/image_chg_col_wid.py

@@ -0,0 +1,27 @@
+# This demonstrates the effect of changing the column width
+# when inserting a picture/image.
+
+import xlwt
+w = xlwt.Workbook()
+ws = w.add_sheet('Image')
+
+ws.write(0, 2, "chg wid: none")
+ws.insert_bitmap('python.bmp', 2, 2)
+
+ws.write(0, 4, "chg wid: after")
+ws.insert_bitmap('python.bmp', 2, 4)
+ws.col(4).width = 20 * 256
+
+ws.write(0, 6, "chg wid: before")
+ws.col(6).width = 20 * 256
+ws.insert_bitmap('python.bmp', 2, 6)
+
+ws.write(0, 8, "chg wid: after")
+ws.insert_bitmap('python.bmp', 2, 8)
+ws.col(5).width = 8 * 256
+
+ws.write(0, 10, "chg wid: before")
+ws.col(10).width = 8 * 256
+ws.insert_bitmap('python.bmp', 2, 10)
+
+w.save('image_chg_col_wid.xls')

+ 39 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/merged.py

@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+fnt = Font()
+fnt.name = 'Arial'
+fnt.colour_index = 4
+fnt.bold = True
+
+borders = Borders()
+borders.left = 6
+borders.right = 6
+borders.top = 6
+borders.bottom = 6
+
+al = Alignment()
+al.horz = Alignment.HORZ_CENTER
+al.vert = Alignment.VERT_CENTER
+
+style = XFStyle()
+style.font = fnt
+style.borders = borders
+style.alignment = al
+
+
+wb = Workbook()
+ws0 = wb.add_sheet('sheet0')
+ws1 = wb.add_sheet('sheet1')
+ws2 = wb.add_sheet('sheet2')
+
+for i in range(0, 0x200, 2):
+    ws0.write_merge(i, i+1, 1, 5, 'test %d' % i, style)
+    ws1.write_merge(i, i, 1, 7, 'test %d' % i, style)
+    ws2.write_merge(i, i+1, 1, 7 + (i%10), 'test %d' % i, style)
+
+
+wb.save('merged.xls')

+ 30 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/merged0.py

@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+wb = Workbook()
+ws0 = wb.add_sheet('sheet0')
+
+
+fnt = Font()
+fnt.name = 'Arial'
+fnt.colour_index = 4
+fnt.bold = True
+
+borders = Borders()
+borders.left = 6
+borders.right = 6
+borders.top = 6
+borders.bottom = 6
+
+style = XFStyle()
+style.font = fnt
+style.borders = borders
+
+ws0.write_merge(3, 3, 1, 5, 'test1', style)
+ws0.write_merge(4, 10, 1, 5, 'test2', style)
+ws0.col(1).width = 0x0d00
+
+wb.save('merged0.xls')

+ 102 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/merged1.py

@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+wb = Workbook()
+ws0 = wb.add_sheet('sheet0')
+
+fnt1 = Font()
+fnt1.name = 'Verdana'
+fnt1.bold = True
+fnt1.height = 18*0x14
+
+pat1 = Pattern()
+pat1.pattern = Pattern.SOLID_PATTERN
+pat1.pattern_fore_colour = 0x16
+
+brd1 = Borders()
+brd1.left = 0x06
+brd1.right = 0x06
+brd1.top = 0x06
+brd1.bottom = 0x06
+
+fnt2 = Font()
+fnt2.name = 'Verdana'
+fnt2.bold = True
+fnt2.height = 14*0x14
+
+brd2 = Borders()
+brd2.left = 0x01
+brd2.right = 0x01
+brd2.top = 0x01
+brd2.bottom = 0x01
+
+pat2 = Pattern()
+pat2.pattern = Pattern.SOLID_PATTERN
+pat2.pattern_fore_colour = 0x01F
+
+fnt3 = Font()
+fnt3.name = 'Verdana'
+fnt3.bold = True
+fnt3.italic = True
+fnt3.height = 12*0x14
+
+brd3 = Borders()
+brd3.left = 0x07
+brd3.right = 0x07
+brd3.top = 0x07
+brd3.bottom = 0x07
+
+fnt4 = Font()
+
+al1 = Alignment()
+al1.horz = Alignment.HORZ_CENTER
+al1.vert = Alignment.VERT_CENTER
+
+al2 = Alignment()
+al2.horz = Alignment.HORZ_RIGHT
+al2.vert = Alignment.VERT_CENTER
+
+al3 = Alignment()
+al3.horz = Alignment.HORZ_LEFT
+al3.vert = Alignment.VERT_CENTER
+
+style1 = XFStyle()
+style1.font = fnt1
+style1.alignment = al1
+style1.pattern = pat1
+style1.borders = brd1
+
+style2 = XFStyle()
+style2.font = fnt2
+style2.alignment = al1
+style2.pattern = pat2
+style2.borders = brd2
+
+style3 = XFStyle()
+style3.font = fnt3
+style3.alignment = al1
+style3.pattern = pat2
+style3.borders = brd3
+
+price_style = XFStyle()
+price_style.font = fnt4
+price_style.alignment = al2
+price_style.borders = brd3
+price_style.num_format_str = '_(#,##0.00_) "money"'
+
+ware_style = XFStyle()
+ware_style.font = fnt4
+ware_style.alignment = al3
+ware_style.borders = brd3
+
+
+ws0.merge(3, 3, 1, 5, style1)
+ws0.merge(4, 10, 1, 6, style2)
+ws0.merge(14, 16, 1, 7, style3)
+ws0.col(1).width = 0x0d00
+
+
+wb.save('merged1.xls')

+ 9 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/mini.py

@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+w = Workbook()
+ws = w.add_sheet('xlwt was here')
+w.save('mini.xls')

+ 60 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/num_formats.py

@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+w = Workbook()
+ws = w.add_sheet('Hey, Dude')
+
+fmts = [
+    'general',
+    '0',
+    '0.00',
+    '#,##0',
+    '#,##0.00',
+    '"$"#,##0_);("$"#,##',
+    '"$"#,##0_);[Red]("$"#,##',
+    '"$"#,##0.00_);("$"#,##',
+    '"$"#,##0.00_);[Red]("$"#,##',
+    '0%',
+    '0.00%',
+    '0.00E+00',
+    '# ?/?',
+    '# ??/??',
+    'M/D/YY',
+    'D-MMM-YY',
+    'D-MMM',
+    'MMM-YY',
+    'h:mm AM/PM',
+    'h:mm:ss AM/PM',
+    'h:mm',
+    'h:mm:ss',
+    'M/D/YY h:mm',
+    '_(#,##0_);(#,##0)',
+    '_(#,##0_);[Red](#,##0)',
+    '_(#,##0.00_);(#,##0.00)',
+    '_(#,##0.00_);[Red](#,##0.00)',
+    '_("$"* #,##0_);_("$"* (#,##0);_("$"* "-"_);_(@_)',
+    '_(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)',
+    '_("$"* #,##0.00_);_("$"* (#,##0.00);_("$"* "-"??_);_(@_)',
+    '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)',
+    'mm:ss',
+    '[h]:mm:ss',
+    'mm:ss.0',
+    '##0.0E+0',
+    '@'   
+]
+
+i = 0
+for fmt in fmts:
+    ws.write(i, 0, fmt)
+
+    style = XFStyle()
+    style.num_format_str = fmt
+
+    ws.write(i, 4, -1278.9078, style)
+
+    i += 1
+
+w.save('num_formats.xls')

+ 25 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/numbers_demo.py

@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+w = Workbook()
+ws = w.add_sheet('Hey, Dude')
+
+ws.write(0, 0, 1)
+ws.write(1, 0, 1.23)
+ws.write(2, 0, 12345678)
+ws.write(3, 0, 123456.78)
+
+ws.write(0, 1, -1)
+ws.write(1, 1, -1.23)
+ws.write(2, 1, -12345678)
+ws.write(3, 1, -123456.78)
+
+ws.write(0, 2, -17867868678687.0)
+ws.write(1, 2, -1.23e-5)
+ws.write(2, 2, -12345678.90780980)
+ws.write(3, 2, -123456.78)
+
+w.save('numbers.xls')

+ 113 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/outline.py

@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+fnt = Font()
+fnt.name = 'Arial'
+fnt.colour_index = 4
+fnt.bold = True
+
+borders = Borders()
+borders.left = 6
+borders.right = 6
+borders.top = 6
+borders.bottom = 6
+
+style = XFStyle()
+style.font = fnt
+style.borders = borders
+
+wb = Workbook()
+
+ws0 = wb.add_sheet('Rows Outline')
+
+ws0.write_merge(1, 1, 1, 5, 'test 1', style)
+ws0.write_merge(2, 2, 1, 4, 'test 1', style)
+ws0.write_merge(3, 3, 1, 3, 'test 2', style)
+ws0.write_merge(4, 4, 1, 4, 'test 1', style)
+ws0.write_merge(5, 5, 1, 4, 'test 3', style)
+ws0.write_merge(6, 6, 1, 5, 'test 1', style)
+ws0.write_merge(7, 7, 1, 5, 'test 4', style)
+ws0.write_merge(8, 8, 1, 4, 'test 1', style)
+ws0.write_merge(9, 9, 1, 3, 'test 5', style)
+
+ws0.row(1).level = 1
+ws0.row(2).level = 1
+ws0.row(3).level = 2
+ws0.row(4).level = 2
+ws0.row(5).level = 2
+ws0.row(6).level = 2
+ws0.row(7).level = 2
+ws0.row(8).level = 1
+ws0.row(9).level = 1
+
+
+ws1 = wb.add_sheet('Columns Outline')
+
+ws1.write_merge(1, 1, 1, 5, 'test 1', style)
+ws1.write_merge(2, 2, 1, 4, 'test 1', style)
+ws1.write_merge(3, 3, 1, 3, 'test 2', style)
+ws1.write_merge(4, 4, 1, 4, 'test 1', style)
+ws1.write_merge(5, 5, 1, 4, 'test 3', style)
+ws1.write_merge(6, 6, 1, 5, 'test 1', style)
+ws1.write_merge(7, 7, 1, 5, 'test 4', style)
+ws1.write_merge(8, 8, 1, 4, 'test 1', style)
+ws1.write_merge(9, 9, 1, 3, 'test 5', style)
+
+ws1.col(1).level = 1
+ws1.col(2).level = 1
+ws1.col(3).level = 2
+ws1.col(4).level = 2
+ws1.col(5).level = 2
+ws1.col(6).level = 2
+ws1.col(7).level = 2
+ws1.col(8).level = 1
+ws1.col(9).level = 1
+
+
+ws2 = wb.add_sheet('Rows and Columns Outline')
+
+ws2.write_merge(1, 1, 1, 5, 'test 1', style)
+ws2.write_merge(2, 2, 1, 4, 'test 1', style)
+ws2.write_merge(3, 3, 1, 3, 'test 2', style)
+ws2.write_merge(4, 4, 1, 4, 'test 1', style)
+ws2.write_merge(5, 5, 1, 4, 'test 3', style)
+ws2.write_merge(6, 6, 1, 5, 'test 1', style)
+ws2.write_merge(7, 7, 1, 5, 'test 4', style)
+ws2.write_merge(8, 8, 1, 4, 'test 1', style)
+ws2.write_merge(9, 9, 1, 3, 'test 5', style)
+
+ws2.row(1).level = 1
+ws2.row(2).level = 1
+ws2.row(3).level = 2
+ws2.row(4).level = 2
+ws2.row(5).level = 2
+ws2.row(6).level = 2
+ws2.row(7).level = 2
+ws2.row(8).level = 1
+ws2.row(9).level = 1
+
+ws2.write_merge(1, 1, 1, 5, 'test 1', style)
+ws2.write_merge(2, 2, 1, 4, 'test 1', style)
+ws2.write_merge(3, 3, 1, 3, 'test 2', style)
+ws2.write_merge(4, 4, 1, 4, 'test 1', style)
+ws2.write_merge(5, 5, 1, 4, 'test 3', style)
+ws2.write_merge(6, 6, 1, 5, 'test 1', style)
+ws2.write_merge(7, 7, 1, 5, 'test 4', style)
+ws2.write_merge(8, 8, 1, 4, 'test 1', style)
+ws2.write_merge(9, 9, 1, 3, 'test 5', style)
+
+ws2.col(1).level = 1
+ws2.col(2).level = 1
+ws2.col(3).level = 2
+ws2.col(4).level = 2
+ws2.col(5).level = 2
+ws2.col(6).level = 2
+ws2.col(7).level = 2
+ws2.col(8).level = 1
+ws2.col(9).level = 1
+
+
+wb.save('outline.xls')

+ 58 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/panes.py

@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+w = Workbook()
+ws1 = w.add_sheet('sheet 1')
+ws2 = w.add_sheet('sheet 2')
+ws3 = w.add_sheet('sheet 3')
+ws4 = w.add_sheet('sheet 4')
+ws5 = w.add_sheet('sheet 5')
+ws6 = w.add_sheet('sheet 6')
+
+for i in range(0x100):
+    ws1.write(i//0x10, i%0x10, i)
+
+for i in range(0x100):
+    ws2.write(i//0x10, i%0x10, i)
+
+for i in range(0x100):
+    ws3.write(i//0x10, i%0x10, i)
+
+for i in range(0x100):
+    ws4.write(i//0x10, i%0x10, i)
+
+for i in range(0x100):
+    ws5.write(i//0x10, i%0x10, i)
+
+for i in range(0x100):
+    ws6.write(i//0x10, i%0x10, i)
+
+ws1.panes_frozen = True
+ws1.horz_split_pos = 2
+
+ws2.panes_frozen = True
+ws2.vert_split_pos = 2
+
+ws3.panes_frozen = True
+ws3.horz_split_pos = 1
+ws3.vert_split_pos = 1
+
+ws4.panes_frozen = False
+ws4.horz_split_pos = 12
+ws4.horz_split_first_visible = 2
+
+ws5.panes_frozen = False
+ws5.vert_split_pos = 40
+ws4.vert_split_first_visible = 2
+
+ws6.panes_frozen = False
+ws6.horz_split_pos = 12
+ws4.horz_split_first_visible = 2
+ws6.vert_split_pos = 40
+ws4.vert_split_first_visible = 2
+
+w.save('panes.xls')
+

+ 60 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/panes2.py

@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# -*- coding: ascii -*-
+# portions Copyright (C) 2005 Kiseliov Roman
+
+import xlwt
+
+w = xlwt.Workbook()
+sheets = [w.add_sheet('sheet ' + str(sheetx+1)) for sheetx in range(7)]
+ws1, ws2, ws3, ws4, ws5, ws6, ws7 = sheets
+for sheet in sheets:
+    for i in range(0x100):
+        sheet.write(i // 0x10, i % 0x10, i)
+
+H = 1
+V = 2
+HF = H + 2
+VF = V + 2
+
+ws1.panes_frozen = True
+ws1.horz_split_pos = H
+ws1.horz_split_first_visible = HF
+
+ws2.panes_frozen = True
+ws2.vert_split_pos = V
+ws2.vert_split_first_visible = VF
+
+ws3.panes_frozen = True
+ws3.horz_split_pos = H
+ws3.vert_split_pos = V
+ws3.horz_split_first_visible = HF
+ws3.vert_split_first_visible = VF
+
+H = 10
+V = 12
+HF = H + 2
+VF = V + 2
+
+ws4.panes_frozen = False
+ws4.horz_split_pos = H * 12.75 # rows
+ws4.horz_split_first_visible = HF
+
+ws5.panes_frozen = False
+ws5.vert_split_pos = V * 8.43 # rows
+ws5.vert_split_first_visible = VF
+
+ws6.panes_frozen = False
+ws6.horz_split_pos = H * 12.75 # rows
+ws6.horz_split_first_visible = HF
+ws6.vert_split_pos = V * 8.43 # cols
+ws6.vert_split_first_visible = VF
+
+ws7.split_position_units_are_twips = True
+ws7.panes_frozen = False
+ws7.horz_split_pos = H * 250 + 240 # twips
+ws7.horz_split_first_visible = HF
+ws7.vert_split_pos = V * 955 + 410 # twips
+ws7.vert_split_first_visible = VF
+
+w.save('panes2.xls')
+

+ 42 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/panes3.py

@@ -0,0 +1,42 @@
+from xlwt import Workbook
+from xlwt.BIFFRecords import PanesRecord
+w = Workbook()
+
+# do each of the 4 scenarios with each of the 4 possible
+# active pane settings
+
+for px,py in (
+    (0,0),   # no split
+    (0,10),  # horizontal split
+    (10,0),  # vertical split
+    (10,10), # both split
+    ):
+    
+    for active in range(4):
+
+        # 0 - logical bottom-right pane
+        # 1 - logical top-right pane
+        # 2 - logical bottom-left pane
+        # 3 - logical top-left pane
+
+        # only set valid values:
+        if active not in PanesRecord.valid_active_pane.get(
+            (int(px > 0),int(py > 0))
+            ):
+            continue
+
+        sheet = w.add_sheet('px-%i py-%i active-%i' %(
+                px,py,active
+                ))
+
+        for rx in range(20):
+            for cx in range(20):
+                sheet.write(rx,cx,'R%iC%i'%(rx,cx))
+
+        sheet.panes_frozen = False
+        sheet.vert_split_pos = px * 8.43
+        sheet.horz_split_pos = py * 12.75
+        sheet.active_pane = active
+
+w.save('panes3.xls')
+

+ 12 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/parse-fmla.py

@@ -0,0 +1,12 @@
+from xlwt import ExcelFormulaParser, ExcelFormula
+import sys
+
+f = ExcelFormula.Formula(
+""" -((1.80 + 2.898 * 1)/(1.80 + 2.898))*
+AVERAGE((1.80 + 2.898 * 1)/(1.80 + 2.898); 
+        (1.80 + 2.898 * 1)/(1.80 + 2.898); 
+        (1.80 + 2.898 * 1)/(1.80 + 2.898)) + 
+SIN(PI()/4)""")
+
+#for t in f.rpn():
+#    print "%15s %15s" % (ExcelFormulaParser.PtgNames[t[0]], t[1])

BIN
desktop/core/ext-py/xlwt-1.3.0/examples/pattern_examples.xls


+ 122 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/protection.py

@@ -0,0 +1,122 @@
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+fnt = Font()
+fnt.name = 'Arial'
+fnt.colour_index = 4
+fnt.bold = True
+
+borders = Borders()
+borders.left = 6
+borders.right = 6
+borders.top = 6
+borders.bottom = 6
+
+style = XFStyle()
+style.font = fnt
+style.borders = borders
+
+wb = Workbook()
+
+ws0 = wb.add_sheet('Rows Outline')
+
+ws0.write_merge(1, 1, 1, 5, 'test 1', style)
+ws0.write_merge(2, 2, 1, 4, 'test 1', style)
+ws0.write_merge(3, 3, 1, 3, 'test 2', style)
+ws0.write_merge(4, 4, 1, 4, 'test 1', style)
+ws0.write_merge(5, 5, 1, 4, 'test 3', style)
+ws0.write_merge(6, 6, 1, 5, 'test 1', style)
+ws0.write_merge(7, 7, 1, 5, 'test 4', style)
+ws0.write_merge(8, 8, 1, 4, 'test 1', style)
+ws0.write_merge(9, 9, 1, 3, 'test 5', style)
+
+ws0.row(1).level = 1
+ws0.row(2).level = 1
+ws0.row(3).level = 2
+ws0.row(4).level = 2
+ws0.row(5).level = 2
+ws0.row(6).level = 2
+ws0.row(7).level = 2
+ws0.row(8).level = 1
+ws0.row(9).level = 1
+
+
+ws1 = wb.add_sheet('Columns Outline')
+
+ws1.write_merge(1, 1, 1, 5, 'test 1', style)
+ws1.write_merge(2, 2, 1, 4, 'test 1', style)
+ws1.write_merge(3, 3, 1, 3, 'test 2', style)
+ws1.write_merge(4, 4, 1, 4, 'test 1', style)
+ws1.write_merge(5, 5, 1, 4, 'test 3', style)
+ws1.write_merge(6, 6, 1, 5, 'test 1', style)
+ws1.write_merge(7, 7, 1, 5, 'test 4', style)
+ws1.write_merge(8, 8, 1, 4, 'test 1', style)
+ws1.write_merge(9, 9, 1, 3, 'test 5', style)
+
+ws1.col(1).level = 1
+ws1.col(2).level = 1
+ws1.col(3).level = 2
+ws1.col(4).level = 2
+ws1.col(5).level = 2
+ws1.col(6).level = 2
+ws1.col(7).level = 2
+ws1.col(8).level = 1
+ws1.col(9).level = 1
+
+
+ws2 = wb.add_sheet('Rows and Columns Outline')
+
+ws2.write_merge(1, 1, 1, 5, 'test 1', style)
+ws2.write_merge(2, 2, 1, 4, 'test 1', style)
+ws2.write_merge(3, 3, 1, 3, 'test 2', style)
+ws2.write_merge(4, 4, 1, 4, 'test 1', style)
+ws2.write_merge(5, 5, 1, 4, 'test 3', style)
+ws2.write_merge(6, 6, 1, 5, 'test 1', style)
+ws2.write_merge(7, 7, 1, 5, 'test 4', style)
+ws2.write_merge(8, 8, 1, 4, 'test 1', style)
+ws2.write_merge(9, 9, 1, 3, 'test 5', style)
+
+ws2.row(1).level = 1
+ws2.row(2).level = 1
+ws2.row(3).level = 2
+ws2.row(4).level = 2
+ws2.row(5).level = 2
+ws2.row(6).level = 2
+ws2.row(7).level = 2
+ws2.row(8).level = 1
+ws2.row(9).level = 1
+
+ws2.col(1).level = 1
+ws2.col(2).level = 1
+ws2.col(3).level = 2
+ws2.col(4).level = 2
+ws2.col(5).level = 2
+ws2.col(6).level = 2
+ws2.col(7).level = 2
+ws2.col(8).level = 1
+ws2.col(9).level = 1
+
+
+ws0.protect = True
+ws0.wnd_protect = True
+ws0.obj_protect = True
+ws0.scen_protect = True
+ws0.password = "123456"
+
+ws1.protect = True
+ws1.wnd_protect = True
+ws1.obj_protect = True
+ws1.scen_protect = True
+ws1.password = "abcdefghij"
+
+ws2.protect = True
+ws2.wnd_protect = True
+ws2.obj_protect = True
+ws2.scen_protect = True
+ws2.password = "ok"
+
+wb.protect = True
+wb.wnd_protect = True
+wb.obj_protect = True
+wb.save('protection.xls')

BIN
desktop/core/ext-py/xlwt-1.3.0/examples/python.bmp


+ 17 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/row_styles.py

@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+w = Workbook()
+ws = w.add_sheet('Hey, Dude')
+
+for i in range(6, 80):
+    fnt = Font()
+    fnt.height = i*20
+    style = XFStyle()
+    style.font = fnt
+    ws.write(i, 1, 'Test')
+    ws.row(i).set_style(style)
+w.save('row_styles.xls')

+ 18 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/row_styles_empty.py

@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+__rev_id__ = """$Id$"""
+
+
+from pyExcelerator import *
+
+w = Workbook()
+ws = w.add_sheet('Hey, Dude')
+
+for i in range(6, 80):
+    fnt = Font()
+    fnt.height = i*20
+    style = XFStyle()
+    style.font = fnt
+    ws.row(i).set_style(style)
+w.save('row_styles_empty.xls')

+ 24 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/simple.py

@@ -0,0 +1,24 @@
+import xlwt
+from datetime import datetime
+
+font0 = xlwt.Font()
+font0.name = 'Times New Roman'
+font0.colour_index = 2
+font0.bold = True
+
+style0 = xlwt.XFStyle()
+style0.font = font0
+
+style1 = xlwt.XFStyle()
+style1.num_format_str = 'D-MMM-YY'
+
+wb = xlwt.Workbook()
+ws = wb.add_sheet('A Test Sheet')
+
+ws.write(0, 0, 'Test', style0)
+ws.write(1, 0, datetime.now(), style1)
+ws.write(2, 0, 1)
+ws.write(2, 1, 1)
+ws.write(2, 2, xlwt.Formula("A3+B3"))
+
+wb.save('example.xls')

+ 52 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/sst.py

@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+font0 = Formatting.Font()
+font0.name = 'Arial'
+font1 = Formatting.Font()
+font1.name = 'Arial Cyr'
+font2 = Formatting.Font()
+font2.name = 'Times New Roman'
+font3 = Formatting.Font()
+font3.name = 'Courier New Cyr'
+
+num_format0 = '0.00000'
+num_format1 = '0.000000'
+num_format2 = '0.0000000'
+num_format3 = '0.00000000'
+
+st0 = XFStyle()
+st1 = XFStyle()
+st2 = XFStyle()
+st3 = XFStyle()
+st4 = XFStyle()
+
+st0.font = font0
+st0.num_format = num_format0
+
+st1.font = font1
+st1.num_format = num_format1
+
+st2.font = font2
+st2.num_format = num_format2
+
+st3.font = font3
+st3.num_format = num_format3
+
+wb = Workbook()
+
+wb.add_style(st0)
+wb.add_style(st1)
+wb.add_style(st2)
+wb.add_style(st3)
+
+ws0 = wb.add_sheet('0')
+ws0.write(0, 0, 'Olya'*0x4000, st0)
+
+#for i in range(0, 0x10):
+#    ws0.write(i, 2, ('%d'%i)*0x4000, st1)
+    
+wb.save('sst.xls')

+ 12 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/unicode0.py

@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+import xlwt
+
+# Strings passed to (for example) Worksheet.write can be unicode objects,
+# or str (8-bit) objects, which are then decoded into unicode.
+# The encoding to be used defaults to 'ascii'. This can be overridden
+# when the Workbook instance is created:
+
+book = xlwt.Workbook(encoding='cp1251')
+sheet = book.add_sheet('cp1251-demo')
+sheet.write(0, 0, '\xce\xeb\xff')
+book.save('unicode0.xls')

+ 28 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/unicode1.py

@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+
+w = Workbook()
+ws1 = w.add_sheet(u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK SMALL LETTER BETA}\N{GREEK SMALL LETTER GAMMA}')
+
+ws1.write(0, 0, u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK SMALL LETTER BETA}\N{GREEK SMALL LETTER GAMMA}')
+ws1.write(1, 1, u'\N{GREEK SMALL LETTER DELTA}x = 1 + \N{GREEK SMALL LETTER DELTA}')
+
+ws1.write(2,0, u'A\u2262\u0391.')     # RFC2152 example
+ws1.write(3,0, u'Hi Mom -\u263a-!')   # RFC2152 example
+ws1.write(4,0, u'\u65E5\u672C\u8A9E') # RFC2152 example
+ws1.write(5,0, u'Item 3 is \u00a31.') # RFC2152 example
+ws1.write(8,0, u'\N{INTEGRAL}')       # RFC2152 example
+
+w.add_sheet(u'A\u2262\u0391.')     # RFC2152 example
+w.add_sheet(u'Hi Mom -\u263a-!')   # RFC2152 example
+one_more_ws = w.add_sheet(u'\u65E5\u672C\u8A9E') # RFC2152 example
+w.add_sheet(u'Item 3 is \u00a31.') # RFC2152 example
+
+one_more_ws.write(0, 0, u'\u2665\u2665')
+
+w.add_sheet(u'\N{GREEK SMALL LETTER ETA WITH TONOS}')
+w.save('unicode1.xls')
+

+ 28 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/unicode2.py

@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# -*- coding: windows-1251 -*-
+# Copyright (C) 2005 Kiseliov Roman
+
+from xlwt import *
+from xlwt.compat import PY3
+
+w = Workbook()
+ws1 = w.add_sheet(u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK SMALL LETTER BETA}\N{GREEK SMALL LETTER GAMMA}\u2665\u041e\u041b\u042f\u2665')
+
+fnt = Font()
+fnt.height = 26*20
+style = XFStyle()
+style.font = fnt
+
+unichr = chr if PY3 else unichr
+
+for i in range(0xD800):
+    ws1.write(i//0x10, i%0x10, unichr(i), style)
+
+for i in range(0xD800, 0xE000):
+    ws1.write(i//0x10, i%0x10, "Surrogate", style)
+
+for i in range(0xE000, 0x10000):
+    ws1.write(i//0x10, i%0x10, unichr(i), style)
+
+w.save('unicode2.xls')
+

+ 156 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/wsprops.py

@@ -0,0 +1,156 @@
+from __future__ import print_function
+props = \
+[
+        'name',
+        'parent',
+        'rows',
+        'cols',
+        'merged_ranges',
+        'bmp_rec',
+        'show_formulas',
+        'show_grid',
+        'show_headers',
+        'panes_frozen',
+        'show_empty_as_zero',
+        'auto_colour_grid',
+        'cols_right_to_left',
+        'show_outline',
+        'remove_splits',
+        'selected',
+        'hidden',
+        'page_preview',
+        'first_visible_row',
+        'first_visible_col',
+        'grid_colour',
+        'preview_magn',
+        'normal_magn',
+        'row_gut_width',
+        'col_gut_height',
+        'show_auto_page_breaks',
+        'dialogue_sheet',
+        'auto_style_outline',
+        'outline_below',
+        'outline_right',
+        'fit_num_pages',
+        'show_row_outline',
+        'show_col_outline',
+        'alt_expr_eval',
+        'alt_formula_entries',
+        'row_default_height',
+        'col_default_width',
+        'calc_mode',
+        'calc_count',
+        'RC_ref_mode',
+        'iterations_on',
+        'delta',
+        'save_recalc',
+        'print_headers',
+        'print_grid',
+        'grid_set',
+        'vert_page_breaks',
+        'horz_page_breaks',
+        'header_str',
+        'footer_str',
+        'print_centered_vert',
+        'print_centered_horz',
+        'left_margin',
+        'right_margin',
+        'top_margin',
+        'bottom_margin',
+        'paper_size_code',
+        'print_scaling',
+        'start_page_number',
+        'fit_width_to_pages',
+        'fit_height_to_pages',
+        'print_in_rows',
+        'portrait',
+        'print_not_colour',
+        'print_draft',
+        'print_notes',
+        'print_notes_at_end',
+        'print_omit_errors',
+        'print_hres',
+        'print_vres',
+        'header_margin',
+        'footer_margin',
+        'copies_num',
+]
+
+from xlwt import *
+
+wb = Workbook()
+ws = wb.add_sheet('sheet')
+
+print(ws.name)
+print(ws.parent)
+print(ws.rows)
+print(ws.cols)
+print(ws.merged_ranges)
+print(ws.bmp_rec)
+print(ws.show_formulas)
+print(ws.show_grid)
+print(ws.show_headers)
+print(ws.panes_frozen)
+print(ws.show_empty_as_zero)
+print(ws.auto_colour_grid)
+print(ws.cols_right_to_left)
+print(ws.show_outline)
+print(ws.remove_splits)
+print(ws.selected)
+# print ws.hidden
+print(ws.page_preview)
+print(ws.first_visible_row)
+print(ws.first_visible_col)
+print(ws.grid_colour)
+print(ws.preview_magn)
+print(ws.normal_magn)
+#print ws.row_gut_width
+#print ws.col_gut_height
+print(ws.show_auto_page_breaks)
+print(ws.dialogue_sheet)
+print(ws.auto_style_outline)
+print(ws.outline_below)
+print(ws.outline_right)
+print(ws.fit_num_pages)
+print(ws.show_row_outline)
+print(ws.show_col_outline)
+print(ws.alt_expr_eval)
+print(ws.alt_formula_entries)
+print(ws.row_default_height)
+print(ws.col_default_width)
+print(ws.calc_mode)
+print(ws.calc_count)
+print(ws.RC_ref_mode)
+print(ws.iterations_on)
+print(ws.delta)
+print(ws.save_recalc)
+print(ws.print_headers)
+print(ws.print_grid)
+#print ws.grid_set
+print(ws.vert_page_breaks)
+print(ws.horz_page_breaks)
+print(ws.header_str)
+print(ws.footer_str)
+print(ws.print_centered_vert)
+print(ws.print_centered_horz)
+print(ws.left_margin)
+print(ws.right_margin)
+print(ws.top_margin)
+print(ws.bottom_margin)
+print(ws.paper_size_code)
+print(ws.print_scaling)
+print(ws.start_page_number)
+print(ws.fit_width_to_pages)
+print(ws.fit_height_to_pages)
+print(ws.print_in_rows)
+print(ws.portrait)
+print(ws.print_colour)
+print(ws.print_draft)
+print(ws.print_notes)
+print(ws.print_notes_at_end)
+print(ws.print_omit_errors)
+print(ws.print_hres)
+print(ws.print_vres)
+print(ws.header_margin)
+print(ws.footer_margin)
+print(ws.copies_num)

+ 46 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/xlwt_easyxf_simple_demo.py

@@ -0,0 +1,46 @@
+
+# Write an XLS file with a single worksheet, containing
+# a heading row and some rows of data.
+
+import xlwt
+import datetime
+ezxf = xlwt.easyxf
+
+def write_xls(file_name, sheet_name, headings, data, heading_xf, data_xfs):
+    book = xlwt.Workbook()
+    sheet = book.add_sheet(sheet_name)
+    rowx = 0
+    for colx, value in enumerate(headings):
+        sheet.write(rowx, colx, value, heading_xf)
+    sheet.set_panes_frozen(True) # frozen headings instead of split panes
+    sheet.set_horz_split_pos(rowx+1) # in general, freeze after last heading row
+    sheet.set_remove_splits(True) # if user does unfreeze, don't leave a split there
+    for row in data:
+        rowx += 1
+        for colx, value in enumerate(row):
+            sheet.write(rowx, colx, value, data_xfs[colx])
+    book.save(file_name)
+
+if __name__ == '__main__':
+    import sys
+    mkd = datetime.date
+    hdngs = ['Date', 'Stock Code', 'Quantity', 'Unit Price', 'Value', 'Message']
+    kinds =  'date    text          int         price         money    text'.split()
+    data = [
+        [mkd(2007, 7, 1), 'ABC', 1000, 1.234567, 1234.57, ''],
+        [mkd(2007, 12, 31), 'XYZ', -100, 4.654321, -465.43, 'Goods returned'],
+        ] + [
+            [mkd(2008, 6, 30), 'PQRCD', 100, 2.345678, 234.57, ''],
+        ] * 100
+
+    heading_xf = ezxf('font: bold on; align: wrap on, vert centre, horiz center')
+    kind_to_xf_map = {
+        'date': ezxf(num_format_str='yyyy-mm-dd'),
+        'int': ezxf(num_format_str='#,##0'),
+        'money': ezxf('font: italic on; pattern: pattern solid, fore-colour grey25',
+            num_format_str='$#,##0.00'),
+        'price': ezxf(num_format_str='#0.000000'),
+        'text': ezxf(),
+        }
+    data_xfs = [kind_to_xf_map[k] for k in kinds]
+    write_xls('xlwt_easyxf_simple_demo.xls', 'Demo', hdngs, data, heading_xf, data_xfs)

+ 14 - 0
desktop/core/ext-py/xlwt-1.3.0/examples/zoom_magnification.py

@@ -0,0 +1,14 @@
+import xlwt
+book = xlwt.Workbook()
+for magn in (0, 60, 100, 75, 150):
+    for preview in (False, True):
+        sheet = book.add_sheet('magn%d%s' % (magn, "np"[preview]))
+        if preview:
+            sheet.preview_magn = magn
+        else:
+            sheet.normal_magn = magn
+        sheet.page_preview = preview
+        for rowx in range(100):
+            sheet.write(rowx, 0, "Some text")
+book.save("zoom_magnification.xls")
+

+ 15 - 0
desktop/core/ext-py/xlwt-1.3.0/requirements.txt

@@ -0,0 +1,15 @@
+# for testing
+nose
+nose_fixes
+nose-cov
+coveralls
+tox
+# for docs
+sphinx
+pkginfo
+six
+# for build
+panci
+setuptools-git
+wheel
+twine

+ 7 - 0
desktop/core/ext-py/xlwt-1.3.0/setup.cfg

@@ -0,0 +1,7 @@
+[bdist_wheel]
+universal = 1
+
+[egg_info]
+tag_build = 
+tag_date = 0
+

+ 52 - 0
desktop/core/ext-py/xlwt-1.3.0/setup.py

@@ -0,0 +1,52 @@
+import os
+from setuptools import setup, find_packages
+from xlwt import __VERSION__
+
+DESCRIPTION = (
+    'Library to create spreadsheet files compatible with '
+    'MS Excel 97/2000/XP/2003 XLS files, '
+    'on any platform, with Python 2.6, 2.7, 3.3+'
+    )
+
+CLASSIFIERS = [
+    'Operating System :: OS Independent',
+    'Programming Language :: Python',
+    'License :: OSI Approved :: BSD License',
+    'Development Status :: 5 - Production/Stable',
+    'Intended Audience :: Developers',
+    'Topic :: Software Development :: Libraries :: Python Modules',
+    'Topic :: Office/Business :: Financial :: Spreadsheet',
+    'Topic :: Database',
+    'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries',
+    'Programming Language :: Python :: 2',
+    'Programming Language :: Python :: 2.7',
+    'Programming Language :: Python :: 3',
+    'Programming Language :: Python :: 3.3',
+    'Programming Language :: Python :: 3.4',
+    'Programming Language :: Python :: 3.5',
+    'Programming Language :: Python :: 3.6',
+    ]
+
+KEYWORDS = (
+    'xls excel spreadsheet workbook worksheet pyExcelerator'
+    )
+
+setup(
+    name='xlwt',
+    version=__VERSION__,
+    maintainer='John Machin',
+    maintainer_email='sjmachin@lexicon.net',
+    url='http://www.python-excel.org/',
+    download_url='https://pypi.python.org/pypi/xlwt',
+    description=DESCRIPTION,
+    long_description=open(os.path.join(
+        os.path.dirname(__file__), 'README.rst')
+    ).read(),
+    license='BSD',
+    platforms='Platform Independent',
+    keywords=KEYWORDS,
+    classifiers=CLASSIFIERS,
+    packages=find_packages(),
+    zip_safe=False,
+    include_package_data=True
+)

BIN
desktop/core/ext-py/xlwt-1.3.0/tests/bitmaps.xls


BIN
desktop/core/ext-py/xlwt-1.3.0/tests/less_simple.xls


BIN
desktop/core/ext-py/xlwt-1.3.0/tests/mini.xls


BIN
desktop/core/ext-py/xlwt-1.3.0/tests/python.bmp


BIN
desktop/core/ext-py/xlwt-1.3.0/tests/simple.xls


+ 24 - 0
desktop/core/ext-py/xlwt-1.3.0/tests/test_biff_records.py

@@ -0,0 +1,24 @@
+# coding:utf-8
+
+from io import BytesIO
+import unittest
+
+import xlwt
+
+class TestSharedStringTable(unittest.TestCase):
+    def test_shared_string_table(self):
+        expected_result = b'\xfc\x00\x11\x00\x01\x00\x00\x00\x01\x00\x00\x00\x03\x00\x01\x1e\x04;\x04O\x04'
+        string_record = xlwt.BIFFRecords.SharedStringTable(encoding='cp1251')
+        string_record.add_str(u'Оля')
+        self.assertEqual(expected_result, string_record.get_biff_record())
+
+class TestIntersheetsRef(unittest.TestCase):
+    def test_intersheets_ref(self):
+        book = xlwt.Workbook()
+        sheet_a = book.add_sheet('A')
+        sheet_a.write(0, 0, 'A1')
+        sheet_a.write(0, 1, 'A2')
+        sheet_b = book.add_sheet('B')
+        sheet_b.write(0, 0, xlwt.Formula("'A'!$A$1&'A'!$A$2"))
+        out = BytesIO()
+        book.save(out)

+ 41 - 0
desktop/core/ext-py/xlwt-1.3.0/tests/test_bitmaps.py

@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+import sys
+import os
+import unittest
+import filecmp
+from datetime import datetime
+
+from utils import in_tst_dir, in_tst_output_dir
+
+import xlwt
+
+class TestBitmaps(unittest.TestCase):
+    def test_create_bitmap_from_memory(self):
+        book = xlwt.Workbook()
+        sheet = book.add_sheet('bitmap test')
+        
+        with open ("tests/python.bmp", "rb") as bmpfile:
+            bmpdata = bmpfile.read()
+            sheet.insert_bitmap_data(bmpdata, 1, 1)
+        
+        book.save(in_tst_output_dir('bitmaps.xls'))
+        
+        self.assertTrue(filecmp.cmp(in_tst_dir('bitmaps.xls'),
+                                    in_tst_output_dir('bitmaps.xls'),
+                                    shallow=False))
+
+    def test_create_bitmap_from_file(self):
+        book = xlwt.Workbook()
+        sheet = book.add_sheet('bitmap test')
+        
+        sheet.insert_bitmap("tests/python.bmp", 1, 1)
+        
+        book.save(in_tst_output_dir('bitmaps.xls'))
+        
+        self.assertTrue(filecmp.cmp(in_tst_dir('bitmaps.xls'),
+                                    in_tst_output_dir('bitmaps.xls'),
+                                    shallow=False))
+
+if __name__=='__main__':
+    unittest.main()

+ 30 - 0
desktop/core/ext-py/xlwt-1.3.0/tests/test_by_name_functions.py

@@ -0,0 +1,30 @@
+import unittest
+import xlwt
+
+class TestByName(unittest.TestCase):
+    def setUp(self):
+        self.wb = xlwt.Workbook()
+        self.wb.add_sheet('Plan1')    
+        self.wb.add_sheet('Plan2')
+        self.wb.add_sheet('Plan3')
+        self.wb.add_sheet('Plan4')
+
+    def test_sheet_index(self):
+        'Return sheet index by sheet name'
+        idx = self.wb.sheet_index('Plan3')
+        self.assertEqual(2, idx)
+
+    def test_get_by_name(self):
+        'Get sheet by name'
+        ws = self.wb.get_sheet('Plan2')
+        self.assertEqual('Plan2', ws.name)
+    
+    def test_get_by_index(self):
+        'Get sheet by index'
+        ws = self.wb.get_sheet(1)
+        self.assertEqual('Plan2', ws.name)
+    
+    def test_invalid_sheet_parameter(self):
+        'Raises exception when sheet is not string or integer'
+        self.assertRaises(Exception, self.wb.get_sheet, 1.1)
+

Разлика између датотеке није приказан због своје велике величине
+ 4 - 0
desktop/core/ext-py/xlwt-1.3.0/tests/test_compound_doc.py


+ 55 - 0
desktop/core/ext-py/xlwt-1.3.0/tests/test_easyxf.py

@@ -0,0 +1,55 @@
+import unittest
+import filecmp
+import datetime
+
+from utils import in_tst_dir, in_tst_output_dir
+
+import xlwt as xlwt
+ezxf = xlwt.easyxf
+
+def write_xls(file_name, sheet_name, headings, data, heading_xf, data_xfs):
+    book = xlwt.Workbook()
+    sheet = book.add_sheet(sheet_name)
+    rowx = 0
+    for colx, value in enumerate(headings):
+        sheet.write(rowx, colx, value, heading_xf)
+    sheet.set_panes_frozen(True) # frozen headings instead of split panes
+    sheet.set_horz_split_pos(rowx+1) # in general, freeze after last heading row
+    sheet.set_remove_splits(True) # if user does unfreeze, don't leave a split there
+    for row in data:
+        rowx += 1
+        for colx, value in enumerate(row):
+            sheet.write(rowx, colx, value, data_xfs[colx])
+    book.save(file_name)
+
+EXAMPLE_XLS = 'xlwt_easyxf_simple_demo.xls'
+
+class TestUnicode0(unittest.TestCase):
+    def create_example_xls(self, filename):
+        mkd = datetime.date
+        hdngs = ['Date', 'Stock Code', 'Quantity', 'Unit Price', 'Value', 'Message']
+        kinds =  'date    text          int         price         money    text'.split()
+        data = [
+            [mkd(2007, 7, 1), 'ABC', 1000, 1.234567, 1234.57, ''],
+            [mkd(2007, 12, 31), 'XYZ', -100, 4.654321, -465.43, 'Goods returned'],
+            ] + [
+                [mkd(2008, 6, 30), 'PQRCD', 100, 2.345678, 234.57, ''],
+            ] * 100
+
+        heading_xf = ezxf('font: bold on; align: wrap on, vert centre, horiz center')
+        kind_to_xf_map = {
+            'date': ezxf(num_format_str='yyyy-mm-dd'),
+            'int': ezxf(num_format_str='#,##0'),
+            'money': ezxf('font: italic on; pattern: pattern solid, fore-colour grey25',
+                num_format_str='$#,##0.00'),
+            'price': ezxf(num_format_str='#0.000000'),
+            'text': ezxf(),
+            }
+        data_xfs = [kind_to_xf_map[k] for k in kinds]
+        write_xls(filename, 'Demo', hdngs, data, heading_xf, data_xfs)
+
+    def test_example_xls(self):
+        self.create_example_xls(in_tst_output_dir(EXAMPLE_XLS))
+        self.assertTrue(filecmp.cmp(in_tst_dir(EXAMPLE_XLS),
+                                    in_tst_output_dir(EXAMPLE_XLS),
+                                    shallow=False))

+ 21 - 0
desktop/core/ext-py/xlwt-1.3.0/tests/test_mini.py

@@ -0,0 +1,21 @@
+import sys
+import os
+import unittest
+import filecmp
+
+from utils import in_tst_dir, in_tst_output_dir
+
+import xlwt
+
+def from_tst_dir(filename):
+    return os.path.join(os.path.dirname(os.path.abspath(__file__)), filename)
+
+class TestMini(unittest.TestCase):
+    def test_create_mini_xls(self):
+        book = xlwt.Workbook()
+        sheet = book.add_sheet('xlwt was here')
+        book.save(in_tst_output_dir('mini.xls'))
+
+        self.assertTrue(filecmp.cmp(in_tst_dir('mini.xls'),
+                                    in_tst_output_dir('mini.xls'),
+                                    shallow=False))

+ 67 - 0
desktop/core/ext-py/xlwt-1.3.0/tests/test_simple.py

@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+import sys
+import os
+import unittest
+import filecmp
+from datetime import datetime
+
+from utils import in_tst_dir, in_tst_output_dir
+
+import xlwt
+
+LOREM_IPSUM = u'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
+
+
+class TestSimple(unittest.TestCase):
+
+    def create_simple_xls(self, **kw):
+        font0 = xlwt.Font()
+        font0.name = 'Times New Roman'
+        font0.colour_index = 2
+        font0.bold = True
+
+        style0 = xlwt.XFStyle()
+        style0.font = font0
+
+        style1 = xlwt.XFStyle()
+        style1.num_format_str = 'D-MMM-YY'
+
+        wb = xlwt.Workbook(**kw)
+        ws = wb.add_sheet('A Test Sheet')
+
+        ws.write(0, 0, 'Test', style0)
+        ws.write(1, 0, datetime(2010, 12, 5), style1)
+        ws.write(2, 0, 1)
+        ws.write(2, 1, 1)
+        ws.write(2, 2, xlwt.Formula("A3+B3"))
+        return wb, ws
+
+    def test_create_simple_xls(self):
+        wb, _ = self.create_simple_xls()
+        wb.save(in_tst_output_dir('simple.xls'))
+        self.assertTrue(filecmp.cmp(in_tst_dir('simple.xls'),
+                                    in_tst_output_dir('simple.xls'),
+                                    shallow=False))
+
+    def test_create_less_simple_xls(self):
+        wb, ws = self.create_simple_xls()
+        more_content=[
+            [
+                u'A{0}'.format(i),
+                u'Zażółć gęślą jaźń {0} {1}'.format(i, LOREM_IPSUM),
+            ]
+            for idx, i in enumerate(range(1000, 1050))
+        ]
+        for r_idx, content_row in enumerate(more_content, 3):
+            for c_idx, cell in enumerate(content_row):
+                ws.write(r_idx, c_idx, cell)
+        wb.save(in_tst_output_dir('less_simple.xls'))
+        self.assertTrue(filecmp.cmp(in_tst_dir('less_simple.xls'),
+                                    in_tst_output_dir('less_simple.xls'),
+                                    shallow=False))
+
+    def test_font_compression(self):
+        wb, ws = self.create_simple_xls(style_compression = 2)
+        wb.save(in_tst_output_dir('simple.xls'), )
+

+ 40 - 0
desktop/core/ext-py/xlwt-1.3.0/tests/test_unicode1.py

@@ -0,0 +1,40 @@
+import os
+import unittest
+import filecmp
+
+import xlwt
+
+from utils import in_tst_dir, in_tst_output_dir
+
+def create_example_xls(filename):
+    w = xlwt.Workbook()
+    ws1 = w.add_sheet(u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK SMALL LETTER BETA}\N{GREEK SMALL LETTER GAMMA}')
+
+    ws1.write(0, 0, u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK SMALL LETTER BETA}\N{GREEK SMALL LETTER GAMMA}')
+    ws1.write(1, 1, u'\N{GREEK SMALL LETTER DELTA}x = 1 + \N{GREEK SMALL LETTER DELTA}')
+
+    ws1.write(2,0, u'A\u2262\u0391.')     # RFC2152 example
+    ws1.write(3,0, u'Hi Mom -\u263a-!')   # RFC2152 example
+    ws1.write(4,0, u'\u65E5\u672C\u8A9E') # RFC2152 example
+    ws1.write(5,0, u'Item 3 is \u00a31.') # RFC2152 example
+    ws1.write(8,0, u'\N{INTEGRAL}')       # RFC2152 example
+
+    w.add_sheet(u'A\u2262\u0391.')     # RFC2152 example
+    w.add_sheet(u'Hi Mom -\u263a-!')   # RFC2152 example
+    one_more_ws = w.add_sheet(u'\u65E5\u672C\u8A9E') # RFC2152 example
+    w.add_sheet(u'Item 3 is \u00a31.') # RFC2152 example
+
+    one_more_ws.write(0, 0, u'\u2665\u2665')
+
+    w.add_sheet(u'\N{GREEK SMALL LETTER ETA WITH TONOS}')
+    w.save(filename)
+
+EXAMPLE_XLS = 'unicode1.xls'
+
+class TestUnicode1(unittest.TestCase):
+
+    def test_example_xls(self):
+        create_example_xls(in_tst_output_dir(EXAMPLE_XLS))
+        self.assertTrue(filecmp.cmp(in_tst_dir(EXAMPLE_XLS),
+                                    in_tst_output_dir(EXAMPLE_XLS),
+                                    shallow=False))

Разлика између датотеке није приказан због своје велике величине
+ 30 - 0
desktop/core/ext-py/xlwt-1.3.0/tests/test_unicodeutils.py


BIN
desktop/core/ext-py/xlwt-1.3.0/tests/unicode1.xls


+ 11 - 0
desktop/core/ext-py/xlwt-1.3.0/tests/utils.py

@@ -0,0 +1,11 @@
+import os
+from os.path import join, dirname, abspath, isdir
+
+def in_tst_dir(filename):
+    return join(dirname(abspath(__file__)), filename)
+
+def in_tst_output_dir(filename):
+    output_dir = join(dirname(abspath(__file__)), 'output')
+    if not isdir(output_dir):
+        os.mkdir(output_dir, 0o755)
+    return join(output_dir, filename)

BIN
desktop/core/ext-py/xlwt-1.3.0/tests/xlwt_easyxf_simple_demo.xls


+ 13 - 0
desktop/core/ext-py/xlwt-1.3.0/tox.ini

@@ -0,0 +1,13 @@
+# Tox (http://tox.testrun.org/) is a tool for running tests
+# in multiple virtualenvs. This configuration file will run the
+# test suite on all supported python versions. To use it, "pip install tox"
+# and then run "tox" from this directory.
+
+[tox]
+envlist = py36, py35, py34, py33, py27, py26
+
+[testenv]
+commands = 
+    pip install -Ur requirements.txt
+    nosetests --with-cov --cov=xlwt
+        

+ 2457 - 0
desktop/core/ext-py/xlwt-1.3.0/xlwt/BIFFRecords.py

@@ -0,0 +1,2457 @@
+# -*- coding: cp1252 -*-
+from struct import pack
+from .UnicodeUtils import upack1, upack2, upack2rt
+from .compat import basestring, unicode, unicode_type, xrange, iteritems
+
+class SharedStringTable(object):
+    _SST_ID = 0x00FC
+    _CONTINUE_ID = 0x003C
+
+    def __init__(self, encoding):
+        self.encoding = encoding
+        self._str_indexes = {}
+        self._rt_indexes = {}
+        self._tally = []
+        self._add_calls = 0
+        # Following 3 attrs are used for temporary storage in the
+        # get_biff_record() method and methods called by it. The pseudo-
+        # initialisation here is for documentation purposes only.
+        self._sst_record = None
+        self._continues = None
+        self._current_piece = None
+
+    def add_str(self, s):
+        if self.encoding != 'ascii' and not isinstance(s, unicode_type):
+            s = unicode(s, self.encoding)
+        self._add_calls += 1
+        if s not in self._str_indexes:
+            idx = len(self._str_indexes) + len(self._rt_indexes)
+            self._str_indexes[s] = idx
+            self._tally.append(1)
+        else:
+            idx = self._str_indexes[s]
+            self._tally[idx] += 1
+        return idx
+	
+    def add_rt(self, rt):
+        rtList = []
+        for s, xf in rt:
+            if self.encoding != 'ascii' and not isinstance(s, unicode_type):
+                s = unicode(s, self.encoding)
+            rtList.append((s, xf))
+        rt = tuple(rtList)
+        self._add_calls += 1
+        if rt not in self._rt_indexes:
+            idx = len(self._str_indexes) + len(self._rt_indexes)
+            self._rt_indexes[rt] = idx
+            self._tally.append(1)
+        else:
+            idx = self._rt_indexes[rt]
+            self._tally[idx] += 1
+        return idx
+
+    def del_str(self, idx):
+        # This is called when we are replacing the contents of a string cell.
+        # handles both regular and rt strings
+        assert self._tally[idx] > 0
+        self._tally[idx] -= 1
+        self._add_calls -= 1
+
+    def str_index(self, s):
+        return self._str_indexes[s]
+
+    def rt_index(self, rt):
+        return self._rt_indexes[rt]
+
+    def get_biff_record(self):
+        self._sst_record = b''
+        self._continues = [None, None]
+        self._current_piece = pack('<II', 0, 0)
+        data = [(idx, s) for s, idx in iteritems(self._str_indexes)]
+        data.extend((idx, s) for s, idx in iteritems(self._rt_indexes))
+        data.sort() # in index order
+        for idx, s in data:
+            if self._tally[idx] == 0:
+                s = u''
+            if isinstance(s, basestring):
+                self._add_to_sst(s)
+            else:
+                self._add_rt_to_sst(s)
+        del data
+        self._new_piece()
+        self._continues[0] = pack('<2HII', self._SST_ID, len(self._sst_record), self._add_calls, len(self._str_indexes) + len(self._rt_indexes))
+        self._continues[1] = self._sst_record[8:]
+        self._sst_record = None
+        self._current_piece = None
+        result = b''.join(self._continues)
+        self._continues = None
+        return result
+
+
+    def _add_to_sst(self, s):
+        u_str = upack2(s, self.encoding)
+
+        is_unicode_str = u_str[2] == b'\x01'[0]
+        if is_unicode_str:
+            atom_len = 5 # 2 byte -- len,
+                         # 1 byte -- options,
+                         # 2 byte -- 1st sym
+        else:
+            atom_len = 4 # 2 byte -- len,
+                         # 1 byte -- options,
+                         # 1 byte -- 1st sym
+
+        self._save_atom(u_str[0:atom_len])
+        self._save_splitted(u_str[atom_len:], is_unicode_str)
+	
+    def _add_rt_to_sst(self, rt):
+        rt_str, rt_fr = upack2rt(rt, self.encoding)
+        is_unicode_str = rt_str[2] == b'\x09'[0]
+        if is_unicode_str:
+            atom_len = 7 # 2 byte -- len,
+                         # 1 byte -- options,
+                         # 2 byte -- number of rt runs
+                         # 2 byte -- 1st sym
+        else:
+            atom_len = 6 # 2 byte -- len,
+                         # 1 byte -- options,
+                         # 2 byte -- number of rt runs
+                         # 1 byte -- 1st sym
+        self._save_atom(rt_str[0:atom_len])
+        self._save_splitted(rt_str[atom_len:], is_unicode_str)
+        for i in range(0, len(rt_fr), 4):
+            self._save_atom(rt_fr[i:i+4])
+
+    def _new_piece(self):
+        if self._sst_record == b'':
+            self._sst_record = self._current_piece
+        else:
+            curr_piece_len = len(self._current_piece)
+            self._continues.append(pack('<2H%ds'%curr_piece_len, self._CONTINUE_ID, curr_piece_len, self._current_piece))
+        self._current_piece = b''
+
+    def _save_atom(self, s):
+        atom_len = len(s)
+        free_space = 0x2020 - len(self._current_piece)
+        if free_space < atom_len:
+            self._new_piece()
+        self._current_piece += s
+
+    def _save_splitted(self, s, is_unicode_str):
+        i = 0
+        str_len = len(s)
+        while i < str_len:
+            piece_len = len(self._current_piece)
+            free_space = 0x2020 - piece_len
+            tail_len = str_len - i
+            need_more_space = free_space < tail_len
+
+            if not need_more_space:
+                atom_len = tail_len
+            else:
+                if is_unicode_str:
+                    atom_len = free_space & 0xFFFE
+                else:
+                    atom_len = free_space
+
+            self._current_piece += s[i:i+atom_len]
+
+            if need_more_space:
+                self._new_piece()
+                if is_unicode_str:
+                    self._current_piece += b'\x01'
+                else:
+                    self._current_piece += b'\x00'
+
+            i += atom_len
+
+
+class BiffRecord(object):
+
+    _rec_data = b'' # class attribute; child classes need to set this.
+
+    def get_rec_header(self):
+        return pack('<2H', self._REC_ID, len(self._rec_data))
+
+    # Not over-ridden by any child classes, never called (except by "get"; see below).
+    # def get_rec_data(self):
+    #     return self._rec_data
+
+    def get(self):
+        # data = self.get_rec_data()
+        data = self._rec_data
+        if len(data) > 0x2020: # limit for BIFF7/8
+            chunks = []
+            pos = 0
+            while pos < len(data):
+                chunk_pos = pos + 0x2020
+                chunk = data[pos:chunk_pos]
+                chunks.append(chunk)
+                pos = chunk_pos
+            continues = pack('<2H', self._REC_ID, len(chunks[0])) + chunks[0]
+            for chunk in chunks[1:]:
+                continues += pack('<2H%ds'%len(chunk), 0x003C, len(chunk), chunk)
+                # 0x003C -- CONTINUE record id
+            return continues
+        else:
+            return self.get_rec_header() + data
+
+
+class Biff8BOFRecord(BiffRecord):
+    """
+    Offset Size Contents
+    0      2    Version, contains 0600H for BIFF8 and BIFF8X
+    2      2    Type of the following data:
+                  0005H = Workbook globals
+                  0006H = Visual Basic module
+                  0010H = Worksheet
+                  0020H = Chart
+                  0040H = Macro sheet
+                  0100H = Workspace file
+    4      2    Build identifier
+    6      2    Build year
+    8      4    File history flags
+    12     4    Lowest Excel version that can read all records in this file
+    """
+    _REC_ID      = 0x0809
+    # stream types
+    BOOK_GLOBAL = 0x0005
+    VB_MODULE   = 0x0006
+    WORKSHEET   = 0x0010
+    CHART       = 0x0020
+    MACROSHEET  = 0x0040
+    WORKSPACE   = 0x0100
+
+    def __init__(self, rec_type):
+        version  = 0x0600
+        build    = 0x0DBB
+        year     = 0x07CC
+        file_hist_flags = 0x00
+        ver_can_read    = 0x06
+
+        self._rec_data = pack('<4H2I', version, rec_type, build, year, file_hist_flags, ver_can_read)
+
+
+class InteraceHdrRecord(BiffRecord):
+    _REC_ID = 0x00E1
+
+    def __init__(self):
+        self._rec_data = pack('BB', 0xB0, 0x04)
+
+
+class InteraceEndRecord(BiffRecord):
+    _REC_ID = 0x00E2
+
+    def __init__(self):
+        self._rec_data = b''
+
+
+class MMSRecord(BiffRecord):
+    _REC_ID = 0x00C1
+
+    def __init__(self):
+        self._rec_data = pack('<H', 0x00)
+
+
+class WriteAccessRecord(BiffRecord):
+    """
+    This record is part of the file protection. It contains the name of the
+    user  that  has  saved  the  file. The user name is always stored as an
+    equal-sized  string.  All  unused  characters after the name are filled
+    with space characters. It is not required to write the mentioned string
+    length. Every other length will be accepted too.
+    """
+    _REC_ID = 0x005C
+
+    def __init__(self, owner):
+        uowner = owner[0:0x30]
+        uowner_len = len(uowner)
+        if isinstance(uowner, unicode_type):
+            uowner = uowner.encode('ascii')  # probably not ascii, but play it safe until we know more
+        self._rec_data = pack('%ds%ds' % (uowner_len, 0x70 - uowner_len), uowner, b' '*(0x70 - uowner_len))
+
+
+class DSFRecord(BiffRecord):
+    """
+    This  record  specifies  if the file contains an additional BIFF5/BIFF7
+    workbook stream.
+    Record DSF, BIFF8:
+    Offset Size Contents
+    0        2     0 = Only the BIFF8 Workbook stream is present
+                   1 = Additional BIFF5/BIFF7 Book stream is in the file
+    A  double  stream file can be read by Excel 5.0 and Excel 95, and still
+    contains  all  new  features  added to BIFF8 (which are left out in the
+    BIFF5/BIFF7 Book stream).
+    """
+    _REC_ID = 0x0161
+
+    def __init__(self):
+        self._rec_data = pack('<H', 0x00)
+
+
+class TabIDRecord(BiffRecord):
+    _REC_ID = 0x013D
+
+    def __init__(self, sheetcount):
+        for i in range(sheetcount):
+            self._rec_data += pack('<H', i+1)
+
+
+class FnGroupCountRecord(BiffRecord):
+    _REC_ID = 0x009C
+
+    def __init__(self):
+        self._rec_data = pack('BB', 0x0E, 0x00)
+
+
+class WindowProtectRecord(BiffRecord):
+    """
+    This record is part of the worksheet/workbook protection. It determines
+    whether  the window configuration of this document is protected. Window
+    protection is not active, if this record is omitted.
+    """
+    _REC_ID = 0x0019
+
+    def __init__(self, wndprotect):
+        self._rec_data = pack('<H', wndprotect)
+
+
+class ObjectProtectRecord(BiffRecord):
+    """
+    This record is part of the worksheet/workbook protection.
+    It determines whether the objects of the current sheet are protected.
+    Object protection is not active, if this record is omitted.
+    """
+    _REC_ID = 0x0063
+
+
+    def __init__(self, objprotect):
+        self._rec_data = pack('<H', objprotect)
+
+
+class ScenProtectRecord(BiffRecord):
+    """
+    This record is part of the worksheet/workbook protection. It
+    determines whether the scenarios of the current sheet are protected.
+    Scenario protection is not active, if this record is omitted.
+    """
+    _REC_ID = 0x00DD
+
+
+    def __init__(self, scenprotect):
+        self._rec_data = pack('<H', scenprotect)
+
+
+class ProtectRecord(BiffRecord):
+    """
+    This  record is part of the worksheet/workbook protection. It specifies
+    whether  a  worksheet  or a workbook is protected against modification.
+    Protection is not active, if this record is omitted.
+    """
+
+    _REC_ID = 0x0012
+
+    def __init__(self, protect):
+        self._rec_data = pack('<H', protect)
+
+
+class PasswordRecord(BiffRecord):
+    """
+    This record is part of the worksheet/workbook protection. It
+    stores a 16-bit hash value, calculated from the worksheet or workbook
+    protection password.
+    """
+    _REC_ID = 0x0013
+    def passwd_hash(self, plaintext):
+        """
+        Based on the algorithm provided by Daniel Rentz of OpenOffice.
+        """
+        if plaintext == "":
+            return 0
+
+        passwd_hash = 0x0000
+        for i, char in enumerate(plaintext):
+            c = ord(char) << (i + 1)
+            low_15 = c & 0x7fff
+            high_15 = c & 0x7fff << 15
+            high_15 = high_15 >> 15
+            c = low_15 | high_15
+            passwd_hash ^= c
+        passwd_hash ^= len(plaintext)
+        passwd_hash ^= 0xCE4B
+        return passwd_hash
+
+    def __init__(self, passwd = ""):
+        self._rec_data = pack('<H', self.passwd_hash(passwd))
+
+
+class Prot4RevRecord(BiffRecord):
+    _REC_ID = 0x01AF
+
+    def __init__(self):
+        self._rec_data = pack('<H', 0x00)
+
+
+class Prot4RevPassRecord(BiffRecord):
+    _REC_ID = 0x01BC
+
+    def __init__(self):
+        self._rec_data = pack('<H', 0x00)
+
+
+class BackupRecord(BiffRecord):
+    """
+    This  record  contains  a Boolean value determining whether Excel makes
+    a backup of the file while saving.
+    """
+    _REC_ID = 0x0040
+
+    def __init__(self, backup):
+        self._rec_data = pack('<H', backup)
+
+class HideObjRecord(BiffRecord):
+    """
+    This record specifies whether and how to show objects in the workbook.
+
+    Record HIDEOBJ, BIFF3-BIFF8:
+    Offset  Size    Contents
+    0       2       Viewing mode for objects:
+                        0 = Show all objects
+                        1 = Show placeholders
+                        2 = Do not show objects
+    """
+    _REC_ID = 0x008D
+
+    def __init__(self):
+        self._rec_data = pack('<H', 0x00)
+
+
+
+class RefreshAllRecord(BiffRecord):
+    """
+    """
+
+    _REC_ID = 0x01B7
+
+    def __init__(self):
+        self._rec_data = pack('<H', 0x00)
+
+
+class BookBoolRecord(BiffRecord):
+    """
+    This record contains a Boolean value determining whether to save values
+    linked  from external workbooks (CRN records and XCT records). In BIFF3
+    and BIFF4 this option is stored in the WSBOOL record.
+
+    Record BOOKBOOL, BIFF5-BIFF8:
+
+    Offset  Size    Contents
+    0       2       0 = Save external linked values;
+                    1 = Do not save external linked values
+    """
+
+    _REC_ID = 0x00DA
+
+    def __init__(self):
+        self._rec_data = pack('<H', 0x00)
+
+
+class CountryRecord(BiffRecord):
+    """
+    This   record   stores  two  Windows  country  identifiers.  The  first
+    represents  the  user  interface language of the Excel version that has
+    saved  the file, and the second represents the system regional settings
+    at the time the file was saved.
+
+    Record COUNTRY, BIFF3-BIFF8:
+
+    Offset  Size    Contents
+    0       2       Windows country identifier of the user interface language of Excel
+    2       2       Windows country identifier of the system regional settings
+
+    The  following  table  shows most of the used country identifiers. Most
+    of  these  identifiers  are  equal to the international country calling
+    codes.
+
+    1   USA
+    2   Canada
+    7   Russia
+    """
+
+    _REC_ID = 0x008C
+
+    def __init__(self, ui_id, sys_settings_id):
+        self._rec_data = pack('<2H', ui_id, sys_settings_id)
+
+
+class UseSelfsRecord(BiffRecord):
+    """
+    This  record  specifies if the formulas in the workbook can use natural
+    language  formulas.  This  type  of  formula can refer to cells by its
+    content or the content of the column or row header cell.
+
+    Record USESELFS, BIFF8:
+
+    Offset  Size    Contents
+    0       2       0 = Do not use natural language formulas
+                    1 = Use natural language formulas
+
+    """
+
+    _REC_ID = 0x0160
+
+    def __init__(self):
+        self._rec_data = pack('<H', 0x01)
+
+
+class EOFRecord(BiffRecord):
+    _REC_ID = 0x000A
+
+    def __init__(self):
+        self._rec_data = b''
+
+
+class DateModeRecord(BiffRecord):
+    """
+    This  record  specifies  the  base date for displaying date values. All
+    dates  are  stored as count of days past this base date. In BIFF2-BIFF4
+    this   record  is  part  of  the  Calculation  Settings  Block.
+    In BIFF5-BIFF8 it is stored in the Workbook Globals Substream.
+
+    Record DATEMODE, BIFF2-BIFF8:
+
+    Offset  Size    Contents
+    0       2       0 = Base is 1899-Dec-31 (the cell = 1 represents 1900-Jan-01)
+                    1 = Base is 1904-Jan-01 (the cell = 1 represents 1904-Jan-02)
+    """
+    _REC_ID = 0x0022
+
+    def __init__(self, from1904):
+        if from1904:
+            self._rec_data = pack('<H', 1)
+        else:
+            self._rec_data = pack('<H', 0)
+
+
+class PrecisionRecord(BiffRecord):
+    """
+    This record stores if formulas use the real cell values for calculation
+    or  the  values  displayed  on  the screen. In BIFF2- BIFF4 this record
+    is  part of the Calculation Settings Block. In BIFF5-BIFF8 it is stored
+    in the Workbook Globals Substream.
+
+    Record PRECISION, BIFF2-BIFF8:
+
+    Offset  Size    Contents
+    0       2       0 = Use displayed values;
+                    1 = Use real cell values
+    """
+    _REC_ID = 0x000E
+
+    def __init__(self, use_real_values):
+        if use_real_values:
+            self._rec_data = pack('<H', 1)
+        else:
+            self._rec_data = pack('<H', 0)
+
+
+class CodepageBiff8Record(BiffRecord):
+    """
+    This record stores the text encoding used to write byte strings, stored
+    as MS Windows code page identifier. The CODEPAGE record in BIFF8 always
+    contains  the  code  page  1200  (UTF-16).  Therefore  it is not
+    possible  to  obtain the encoding used for a protection password (it is
+    not UTF-16).
+
+    Record CODEPAGE, BIFF2-BIFF8:
+
+    Offset  Size    Contents
+    0       2       Code page identifier used for byte string text encoding:
+                      016FH = 367 = ASCII
+                      01B5H = 437 = IBM PC CP-437 (US)
+                      02D0H = 720 = IBM PC CP-720 (OEM Arabic)
+                      02E1H = 737 = IBM PC CP-737 (Greek)
+                      0307H = 775 = IBM PC CP-775 (Baltic)
+                      0352H = 850 = IBM PC CP-850 (Latin I)
+                      0354H = 852 = IBM PC CP-852 (Latin II (Central European))
+                      0357H = 855 = IBM PC CP-855 (Cyrillic)
+                      0359H = 857 = IBM PC CP-857 (Turkish)
+                      035AH = 858 = IBM PC CP-858 (Multilingual Latin I with Euro)
+                      035CH = 860 = IBM PC CP-860 (Portuguese)
+                      035DH = 861 = IBM PC CP-861 (Icelandic)
+                      035EH = 862 = IBM PC CP-862 (Hebrew)
+                      035FH = 863 = IBM PC CP-863 (Canadian (French))
+                      0360H = 864 = IBM PC CP-864 (Arabic)
+                      0361H = 865 = IBM PC CP-865 (Nordic)
+                      0362H = 866 = IBM PC CP-866 (Cyrillic (Russian))
+                      0365H = 869 = IBM PC CP-869 (Greek (Modern))
+                      036AH = 874 = Windows CP-874 (Thai)
+                      03A4H = 932 = Windows CP-932 (Japanese Shift-JIS)
+                      03A8H = 936 = Windows CP-936 (Chinese Simplified GBK)
+                      03B5H = 949 = Windows CP-949 (Korean (Wansung))
+                      03B6H = 950 = Windows CP-950 (Chinese Traditional BIG5)
+                      04B0H = 1200 = UTF-16 (BIFF8)
+                      04E2H = 1250 = Windows CP-1250 (Latin II) (Central European)
+                      04E3H = 1251 = Windows CP-1251 (Cyrillic)
+                      04E4H = 1252 = Windows CP-1252 (Latin I) (BIFF4-BIFF7)
+                      04E5H = 1253 = Windows CP-1253 (Greek)
+                      04E6H = 1254 = Windows CP-1254 (Turkish)
+                      04E7H = 1255 = Windows CP-1255 (Hebrew)
+                      04E8H = 1256 = Windows CP-1256 (Arabic)
+                      04E9H = 1257 = Windows CP-1257 (Baltic)
+                      04EAH = 1258 = Windows CP-1258 (Vietnamese)
+                      0551H = 1361 = Windows CP-1361 (Korean (Johab))
+                      2710H = 10000 = Apple Roman
+                      8000H = 32768 = Apple Roman
+                      8001H = 32769 = Windows CP-1252 (Latin I) (BIFF2-BIFF3)
+    """
+    _REC_ID = 0x0042
+    UTF_16 = 0x04B0
+
+    def __init__(self):
+        self._rec_data = pack('<H', self.UTF_16)
+
+class Window1Record(BiffRecord):
+    """
+    Offset Size Contents
+    0      2    Horizontal position of the document window (in twips = 1/20 of a point)
+    2      2    Vertical position of the document window (in twips = 1/20 of a point)
+    4      2    Width of the document window (in twips = 1/20 of a point)
+    6      2    Height of the document window (in twips = 1/20 of a point)
+    8      2    Option flags:
+                  Bits  Mask  Contents
+                  0     0001H 0 = Window is visible 1 = Window is hidden
+                  1     0002H 0 = Window is open 1 = Window is minimised
+                  3     0008H 0 = Horizontal scroll bar hidden 1 = Horizontal scroll bar visible
+                  4     0010H 0 = Vertical scroll bar hidden 1 = Vertical scroll bar visible
+                  5     0020H 0 = Worksheet tab bar hidden 1 = Worksheet tab bar visible
+    10     2    Index to active (displayed) worksheet
+    12     2    Index of first visible tab in the worksheet tab bar
+    14     2    Number of selected worksheets (highlighted in the worksheet tab bar)
+    16     2    Width of worksheet tab bar (in 1/1000 of window width). The remaining space is used by the
+                horizontal scrollbar.
+    """
+    _REC_ID = 0x003D
+    # flags
+
+    def __init__(self,
+                 hpos_twips, vpos_twips,
+                 width_twips, height_twips,
+                 flags,
+                 active_sheet,
+                 first_tab_index, selected_tabs, tab_width):
+        self._rec_data = pack('<9H', hpos_twips, vpos_twips,
+                                      width_twips, height_twips,
+                                      flags,
+                                      active_sheet,
+                                      first_tab_index, selected_tabs, tab_width)
+
+class FontRecord(BiffRecord):
+    """
+    WARNING
+        The font with index 4 is omitted in all BIFF versions.
+        This means the first four fonts have zero-based indexes, and
+        the fifth font and all following fonts are referenced with one-based
+        indexes.
+
+    Offset Size Contents
+    0      2    Height of the font (in twips = 1/20 of a point)
+    2      2    Option flags:
+                  Bit Mask    Contents
+                  0   0001H   1 = Characters are bold (redundant, see below)
+                  1   0002H   1 = Characters are italic
+                  2   0004H   1 = Characters are underlined (redundant, see below)
+                  3   0008H   1 = Characters are struck out
+                        0010H 1 = Outline
+                        0020H  1 = Shadow
+    4     2     Colour index
+    6     2     Font weight (100-1000).
+                Standard values are 0190H (400) for normal text and 02BCH
+                (700) for bold text.
+    8     2     Escapement type:
+                  0000H = None
+                  0001H = Superscript
+                  0002H = Subscript
+    10    1     Underline type:
+                  00H = None
+                  01H = Single
+                  21H = Single accounting
+                  02H = Double
+                  22H = Double accounting
+    11    1     Font family:
+                  00H = None (unknown or don't care)
+                  01H = Roman (variable width, serifed)
+                  02H = Swiss (variable width, sans-serifed)
+                  03H = Modern (fixed width, serifed or sans-serifed)
+                  04H = Script (cursive)
+                  05H = Decorative (specialised, i.e. Old English, Fraktur)
+    12    1     Character set:
+                  00H = 0 = ANSI Latin
+                  01H = 1 = System default
+                  02H = 2 = Symbol
+                  4DH = 77 = Apple Roman
+                  80H = 128 = ANSI Japanese Shift-JIS
+                  81H = 129 = ANSI Korean (Hangul)
+                  82H = 130 = ANSI Korean (Johab)
+                  86H = 134 = ANSI Chinese Simplified GBK
+                  88H = 136 = ANSI Chinese Traditional BIG5
+                  A1H = 161 = ANSI Greek
+                  A2H = 162 = ANSI Turkish
+                  A3H = 163 = ANSI Vietnamese
+                  B1H = 177 = ANSI Hebrew
+                  B2H = 178 = ANSI Arabic
+                  BAH = 186 = ANSI Baltic
+                  CCH = 204 = ANSI Cyrillic
+                  DEH = 222 = ANSI Thai
+                  EEH = 238 = ANSI Latin II (Central European)
+                  FFH = 255 = OEM Latin I
+    13    1     Not used
+    14    var.  Font name:
+                  BIFF5/BIFF7: Byte string, 8-bit string length
+                  BIFF8: Unicode string, 8-bit string length
+    The boldness and underline flags are still set in the options field,
+    but not used on reading the font. Font weight and underline type
+    are specified in separate fields instead.
+    """
+    _REC_ID = 0x0031
+
+    def __init__(self,
+                    height, options, colour_index, weight, escapement,
+                    underline, family, charset,
+                    name):
+        uname = upack1(name)
+        uname_len = len(uname)
+
+        self._rec_data = pack('<5H4B%ds' % uname_len, height, options, colour_index, weight, escapement,
+                                                underline, family, charset, 0x00,
+                                                uname)
+
+class NumberFormatRecord(BiffRecord):
+    """
+    Record FORMAT, BIFF8:
+    Offset  Size    Contents
+    0       2       Format index used in other records
+    2       var.    Number format string (Unicode string, 16-bit string length)
+
+    From  BIFF5  on,  the built-in number formats will be omitted. The built-in
+    formats  are  dependent  on  the current regional settings of the operating
+    system.  The following table shows which number formats are used by default
+    in  a  US-English  environment.  All indexes from 0 to 163 are reserved for
+    built-in formats. The first user-defined format starts at 164.
+
+    The built-in number formats, BIFF5-BIFF8
+
+    Index   Type        Format string
+    0       General     General
+    1       Decimal     0
+    2       Decimal     0.00
+    3       Decimal     #,##0
+    4       Decimal     #,##0.00
+    5       Currency    "$"#,##0_);("$"#,##
+    6       Currency    "$"#,##0_);[Red]("$"#,##
+    7       Currency    "$"#,##0.00_);("$"#,##
+    8       Currency    "$"#,##0.00_);[Red]("$"#,##
+    9       Percent     0%
+    10      Percent     0.00%
+    11      Scientific  0.00E+00
+    12      Fraction    # ?/?
+    13      Fraction    # ??/??
+    14      Date        M/D/YY
+    15      Date        D-MMM-YY
+    16      Date        D-MMM
+    17      Date        MMM-YY
+    18      Time        h:mm AM/PM
+    19      Time        h:mm:ss AM/PM
+    20      Time        h:mm
+    21      Time        h:mm:ss
+    22      Date/Time   M/D/YY h:mm
+    37      Account     _(#,##0_);(#,##0)
+    38      Account     _(#,##0_);[Red](#,##0)
+    39      Account     _(#,##0.00_);(#,##0.00)
+    40      Account     _(#,##0.00_);[Red](#,##0.00)
+    41      Currency    _("$"* #,##0_);_("$"* (#,##0);_("$"* "-"_);_(@_)
+    42      Currency    _(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)
+    43      Currency    _("$"* #,##0.00_);_("$"* (#,##0.00);_("$"* "-"??_);_(@_)
+    44      Currency    _(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)
+    45      Time        mm:ss
+    46      Time        [h]:mm:ss
+    47      Time        mm:ss.0
+    48      Scientific  ##0.0E+0
+    49      Text        @
+    """
+    _REC_ID = 0x041E
+
+    def __init__(self, idx, fmtstr):
+        ufmtstr = upack2(fmtstr)
+        ufmtstr_len = len(ufmtstr)
+
+        self._rec_data = pack('<H%ds' % ufmtstr_len, idx, ufmtstr)
+
+
+class XFRecord(BiffRecord):
+    """
+    XF Substructures
+    -------------------------------------------------------------------------
+    XF_TYPE_PROT  XF Type and Cell Protection (3 Bits), BIFF3-BIFF8
+    These 3 bits are part of a specific data byte.
+    Bit Mask    Contents
+    0   01H     1 = Cell is locked
+    1   02H     1 = Formula is hidden
+    2   04H     0 = Cell XF; 1 = Style XF
+
+    XF_USED_ATTRIB   Attributes   Used  from  Parent  Style  XF  (6  Bits),
+    BIFF3-BIFF8  Each  bit  describes  the  validity  of  a  specific group
+    of  attributes.  In  cell XFs a cleared bit means the attributes of the
+    parent  style XF are used (but only if the attributes are valid there),
+    a  set  bit  means  the  attributes  of  this XF are used. In style XFs
+    a cleared bit means the attribute setting is valid, a set bit means the
+    attribute should be ignored.
+    Bit Mask    Contents
+    0   01H     Flag for number format
+    1   02H     Flag for font
+    2   04H     Flag for horizontal and vertical alignment, text wrap, indentation, orientation, rotation, and
+                text direction
+    3   08H     Flag for border lines
+    4   10H     Flag for background area style
+    5   20H     Flag for cell protection (cell locked and formula hidden)
+
+    XF_HOR_ALIGN  Horizontal Alignment (3 Bits), BIFF2-BIFF8 The horizontal
+    alignment consists of 3 bits and is part of a specific data byte.
+    Value   Horizontal alignment
+    00H     General
+    01H     Left
+    02H     Centred
+    03H     Right
+    04H     Filled
+    05H     Justified (BIFF4-BIFF8X)
+    06H     Centred across selection (BIFF4-BIFF8X)
+    07H     Distributed (BIFF8X)
+
+    XF_VERT_ALIGN Vertical Alignment (2 or 3 Bits), BIFF4-BIFF8
+    The vertical alignment consists of 2 bits (BIFF4) or 3 bits (BIFF5-BIFF8)
+    and is part of a specific data byte. Vertical alignment is not available
+    in BIFF2 and BIFF3.
+    Value   Vertical alignment
+    00H     Top
+    01H     Centred
+    02H     Bottom
+    03H     Justified (BIFF5-BIFF8X)
+    04H     Distributed (BIFF8X)
+
+    XF_ORIENTATION  Text  Orientation  (2  Bits),  BIFF4-BIFF7  In the BIFF
+    versions  BIFF4-BIFF7,  text  can  be  rotated  in  steps of 90 degrees
+    or  stacked.  The  orientation  mode  consists of 2 bits and is part of
+    a specific data byte. In BIFF8 a rotation angle occurs instead of these
+    flags.
+    Value   Text orientation
+    00H     Not rotated
+    01H     Letters are stacked top-to-bottom, but not rotated
+    02H     Text is rotated 90 degrees counterclockwise
+    03H     Text is rotated 90 degrees clockwise
+
+    XF_ROTATION Text Rotation Angle (1 Byte), BIFF8
+    Value   Text rotation
+    0       Not rotated
+    1-90    1 to 90 degrees counterclockwise
+    91-180  1 to 90 degrees clockwise
+    255     Letters are stacked top-to-bottom, but not rotated
+
+    XF_BORDER_34  Cell  Border  Style  (4  Bytes), BIFF3-BIFF4 Cell borders
+    contain a line style and a line colour for each line of the border.
+    Bit     Mask        Contents
+    2-0     00000007H   Top line style
+    7-3     000000F8H   Colour index for top line colour
+    10-8    00000700H   Left line style
+    15-11   0000F800H   Colour index for left line colour
+    18-16   00070000H   Bottom line style
+    23-19   00F80000H   Colour index for bottom line colour
+    26-24   07000000H   Right line style
+    31-27   F8000000H   Colour index for right line colour
+
+    XF_AREA_34  Cell  Background  Area  Style (2 Bytes), BIFF3-BIFF4 A cell
+    background  area  style  contains  an area pattern and a foreground and
+    background colour.
+    Bit     Mask    Contents
+    5-0     003FH   Fill pattern
+    10-6    07C0H   Colour index for pattern colour
+    15-11   F800H   Colour index for pattern background
+ ---------------------------------------------------------------------------------------------
+    Record XF, BIFF8:
+    Offset      Size    Contents
+    0           2       Index to FONT record
+    2           2       Index to FORMAT record
+    4           2       Bit     Mask    Contents
+                        2-0     0007H   XF_TYPE_PROT . XF type, cell protection (see above)
+                        15-4    FFF0H   Index to parent style XF (always FFFH in style XFs)
+    6           1       Bit     Mask    Contents
+                        2-0     07H     XF_HOR_ALIGN . Horizontal alignment (see above)
+                        3       08H     1 = Text is wrapped at right border
+                        6-4     70H     XF_VERT_ALIGN . Vertical alignment (see above)
+    7           1       XF_ROTATION: Text rotation angle (see above)
+    8           1       Bit     Mask    Contents
+                        3-0     0FH     Indent level
+                        4       10H     1 = Shrink content to fit into cell
+                        5               merge
+                        7-6     C0H     Text direction (BIFF8X only)
+                                        00b = According to context
+                                        01b = Left-to-right
+                                        10b = Right-to-left
+    9           1       Bit     Mask    Contents
+                        7-2     FCH     XF_USED_ATTRIB . Used attributes (see above)
+    10          4       Cell border lines and background area:
+                        Bit     Mask      Contents
+                        3-0     0000000FH Left line style
+                        7-4     000000F0H Right line style
+                        11-8    00000F00H Top line style
+                        15-12   0000F000H Bottom line style
+                        22-16   007F0000H Colour index for left line colour
+                        29-23   3F800000H Colour index for right line colour
+                        30      40000000H 1 = Diagonal line from top left to right bottom
+                        31      80000000H 1 = Diagonal line from bottom left to right top
+    14          4       Bit     Mask      Contents
+                        6-0     0000007FH Colour index for top line colour
+                        13-7    00003F80H Colour index for bottom line colour
+                        20-14   001FC000H Colour index for diagonal line colour
+                        24-21   01E00000H Diagonal line style
+                        31-26   FC000000H Fill pattern
+    18          2       Bit     Mask    Contents
+                        6-0     007FH   Colour index for pattern colour
+                        13-7    3F80H   Colour index for pattern background
+
+    """
+    _REC_ID = 0x00E0
+
+    def __init__(self, xf, xftype='cell'):
+        font_xf_idx, fmt_str_xf_idx, alignment, borders, pattern, protection = xf
+        fnt = pack('<H', font_xf_idx)
+        fmt = pack('<H', fmt_str_xf_idx)
+        if xftype == 'cell':
+            prt = pack('<H',
+                ((protection.cell_locked    & 0x01) << 0) |
+                ((protection.formula_hidden & 0x01) << 1)
+            )
+        else:
+            prt = pack('<H', 0xFFF5)
+        aln = pack('B',
+            ((alignment.horz & 0x07) << 0) |
+            ((alignment.wrap & 0x01) << 3) |
+            ((alignment.vert & 0x07) << 4)
+        )
+        rot = pack('B', alignment.rota)
+        txt = pack('B',
+            ((alignment.inde & 0x0F) << 0) |
+            ((alignment.shri & 0x01) << 4) |
+            ((alignment.merg & 0x01) << 5) |
+            ((alignment.dire & 0x03) << 6)
+        )
+        if xftype == 'cell':
+            used_attr = pack('B', 0xF8)
+        else:
+            used_attr = pack('B', 0xF4)
+
+        if borders.left == borders.NO_LINE:
+            borders.left_colour = 0x00
+        if borders.right == borders.NO_LINE:
+            borders.right_colour = 0x00
+        if borders.top == borders.NO_LINE:
+            borders.top_colour = 0x00
+        if borders.bottom == borders.NO_LINE:
+            borders.bottom_colour = 0x00
+        if borders.diag == borders.NO_LINE:
+            borders.diag_colour = 0x00
+        brd1 = pack('<L',
+            ((borders.left          & 0x0F) << 0 ) |
+            ((borders.right         & 0x0F) << 4 ) |
+            ((borders.top           & 0x0F) << 8 ) |
+            ((borders.bottom        & 0x0F) << 12) |
+            ((borders.left_colour   & 0x7F) << 16) |
+            ((borders.right_colour  & 0x7F) << 23) |
+            ((borders.need_diag1    & 0x01) << 30) |
+            ((borders.need_diag2    & 0x01) << 31)
+        )
+        brd2 = pack('<L',
+            ((borders.top_colour    & 0x7F) << 0 ) |
+            ((borders.bottom_colour & 0x7F) << 7 ) |
+            ((borders.diag_colour   & 0x7F) << 14) |
+            ((borders.diag          & 0x0F) << 21) |
+            ((pattern.pattern       & 0x3F) << 26)
+        )
+        pat = pack('<H',
+            ((pattern.pattern_fore_colour & 0x7F) << 0 ) |
+            ((pattern.pattern_back_colour & 0x7F) << 7 )
+        )
+        self._rec_data = fnt + fmt + prt + \
+                        aln + rot + txt + used_attr + \
+                        brd1 + brd2 + \
+                        pat
+
+class StyleRecord(BiffRecord):
+    """
+    STYLE record for user-defined cell styles, BIFF3-BIFF8:
+    Offset  Size    Contents
+    0       2       Bit     Mask    Contents
+                    11-0    0FFFH   Index to style XF record
+                    15      8000H   Always 0 for user-defined styles
+    2       var.    BIFF2-BIFF7: Non-empty byte string, 8-bit string length
+                    BIFF8: Non-empty Unicode string, 16-bit string length
+    STYLE record for built-in cell styles, BIFF3-BIFF8:
+    Offset  Size    Contents
+    0       2       Bit     Mask    Contents
+                    11-0    0FFFH   Index to style XF record
+                    15      8000H   Always 1 for built-in styles
+    2       1       Identifier of the built-in cell style:
+                        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)
+    3       1       Level for RowLevel or ColLevel style
+                    (zero-based, lv), FFH otherwise
+    The  RowLevel  and  ColLevel  styles specify the formatting of subtotal
+    cells  in  a specific outline level. The level is specified by the last
+    field  in the STYLE record. Valid values are 0-6 for the outline levels
+    1-7.
+    """
+    _REC_ID = 0x0293
+
+    def __init__(self):
+        self._rec_data = pack('<HBB', 0x8000, 0x00, 0xFF)
+        # TODO: implement user-defined styles???
+
+
+class PaletteRecord(BiffRecord):
+    """
+    This  record  contains  the  definition  of  all  user-defined  colours
+    available for cell and object formatting.
+
+    Record PALETTE, BIFF3-BIFF8:
+
+    Offset  Size    Contents
+    0       2       Number of following colours (nm). Contains 16 in BIFF3-BIFF4 and 56 in BIFF5-BIFF8.
+    2       4*nm    List of nm RGB colours
+
+    The following table shows how colour indexes are used in other records:
+
+    Colour index    Resulting colour or internal list index
+    00H             Built-in Black (R = 00H, G = 00H, B = 00H)
+    01H             Built-in White (R = FFH, G = FFH, B = FFH)
+    02H             Built-in Red (R = FFH, G = 00H, B = 00H)
+    03H             Built-in Green (R = 00H, G = FFH, B = 00H)
+    04H             Built-in Blue (R = 00H, G = 00H, B = FFH)
+    05H             Built-in Yellow (R = FFH, G = FFH, B = 00H)
+    06H             Built-in Magenta (R = FFH, G = 00H, B = FFH)
+    07H             Built-in Cyan (R = 00H, G = FFH, B = FFH)
+    08H             First user-defined colour from the PALETTE record (entry 0 from record colour list)
+    .........................
+
+    17H (BIFF3-BIFF4) Last user-defined colour from the PALETTE record (entry 15 or 55 from record colour list)
+    3FH (BIFF5-BIFF8)
+
+    18H (BIFF3-BIFF4) System window text colour for border lines (used in records XF, CF, and
+    40H (BIFF5-BIFF8) WINDOW2 (BIFF8 only))
+
+    19H (BIFF3-BIFF4) System window background colour for pattern background (used in records XF, and CF)
+    41H (BIFF5-BIFF8)
+
+    43H             System face colour (dialogue background colour)
+    4DH             System window text colour for chart border lines
+    4EH             System window background colour for chart areas
+    4FH             Automatic colour for chart border lines (seems to be always Black)
+    50H             System ToolTip background colour (used in note objects)
+    51H             System ToolTip text colour (used in note objects)
+    7FFFH           System window text colour for fonts (used in records FONT, EFONT, and CF)
+
+    """
+    _REC_ID = 0x0092
+
+    def __init__(self, custom_palette):
+        n_colours = len(custom_palette)
+        assert n_colours == 56
+        # Pack number of colors with little-endian, what xlrd and excel expect.
+        self._rec_data = pack('<H', n_colours)
+        # Microsoft lists colors in big-endian format with 24 bits/color.
+        # Pad LSB of each color with 0x00, and write out in big-endian.
+        fmt = '>%dI' % n_colours
+        self._rec_data += pack(fmt, *(custom_palette))
+
+class BoundSheetRecord(BiffRecord):
+    """
+    This  record  is  located  in  the workbook globals area and represents
+    a  sheet  inside  of  the  workbook. For each sheet a BOUNDSHEET record
+    is  written.  It  stores  the sheet name and a stream offset to the BOF
+    record    within   the   workbook   stream.  The  record  is also known
+    as BUNDLESHEET.
+
+    Record BOUNDSHEET, BIFF5-BIFF8:
+    Offset  Size    Contents
+    0       4       Absolute stream position of the BOF record of the sheet represented by this record. This
+                    field is never encrypted in protected files.
+    4       1       Visibility:
+                        00H = Visible
+                        01H = Hidden
+                        02H = Strong hidden
+    5       1       Sheet type:
+                        00H = Worksheet
+                        02H = Chart
+                        06H = Visual Basic module
+    6       var.    Sheet name:
+                        BIFF5/BIFF7: Byte string, 8-bit string length
+                        BIFF8: Unicode string, 8-bit string length
+    """
+    _REC_ID = 0x0085
+
+    def __init__(self, stream_pos, visibility, sheetname, encoding='ascii'):
+        usheetname = upack1(sheetname, encoding)
+        uusheetname_len = len(usheetname)
+
+        self._rec_data = pack('<LBB%ds' % uusheetname_len, stream_pos, visibility, 0x00, usheetname)
+
+
+class ContinueRecord(BiffRecord):
+    """
+    Whenever  the content of a record exceeds the given limits (see table),
+    the  record  must  be  split.  Several  CONTINUE records containing the
+    additional data are added after the parent record.
+
+    BIFF version    Maximum data size of a record
+    BIFF2-BIFF7     2080 bytes (2084 bytes including record header)
+    BIFF8           8224 bytes (8228 bytes including record header) (0x2020)
+
+    Record CONTINUE, BIFF2-BIFF8:
+    Offset  Size    Contents
+    0       var.    Data continuation of the previous record
+
+    Unicode  strings  are  split in a special way. At the beginning of each
+    CONTINUE  record  the option flags byte is repeated. Only the character
+    size  flag  will  be set in this flags byte, the Rich-Text flag and the
+    Far-East  flag  are set to zero. In each CONTINUE record it is possible
+    that  the  character  size  changes  from  8-bit  characters  to 16-bit
+    characters and vice versa.
+
+    Never  a  Unicode  string  is  split  until  and  including  the  first
+    character.  That means, all header fields (string length, option flags,
+    optional Rich-Text size, and optional Far-East data size) and the first
+    character  of  the string have to occur together in the leading record,
+    or  have  to  be  moved completely into the CONTINUE record. Formatting
+    runs cannot be split between their components (character index and FONT
+    record  index).  If  a string is split between two formatting runs, the
+    option flags field will not be repeated in the CONTINUE record.
+    """
+    _REC_ID = 0x003C
+
+
+class SSTRecord(BiffRecord):
+    """
+    This  record  contains  a  list  of  all  strings  used anywhere in the
+    workbook.  Each string occurs only once. The workbook uses indexes into
+    the list to reference the strings.
+
+    Record SST, BIFF8:
+    Offset  Size    Contents
+    0       4       Total number of strings in the workbook (see below)
+    4       4       Number of following strings (nm)
+    8       var.    List of nm Unicode strings, 16-bit string length
+
+    The  first  field  of  the  SST  record  counts  the  total  occurrence
+    of  strings  in  the  workbook.  For  instance,  the string AAA is used
+    3  times  and  the string BBB is used 2 times. The first field contains
+    5 and the second field contains 2, followed by the two strings.
+    """
+    _REC_ID = 0x00FC
+
+
+class ExtSSTRecord(BiffRecord):
+    """
+    This  record  occurs  in  conjunction  with  the SST record. It is used
+    by  Excel  to create a hash table with stream offsets to the SST record
+    to optimise string search operations. Excel may not shorten this record
+    if  strings  are deleted from the shared string table, so the last part
+    might  contain  invalid  data. The stream indexes in this record divide
+    the SST into portions containing a constant number of strings.
+
+    Record EXTSST, BIFF8:
+
+    Offset  Size    Contents
+    0       2       Number of strings in a portion, this number is >=8
+    2       var.    List of OFFSET structures for all portions. Each OFFSET contains the following data:
+                        Offset Size Contents
+                        0       4   Absolute stream position of first string of the portion
+                        4       2   Position of first string of the portion inside of current record,
+                                    including record header. This counter restarts at zero, if the SST
+                                    record is continued with a CONTINUE record.
+                        6       2   Not used
+    """
+    _REC_ID = 0x00FF
+
+    def __init__(self, sst_stream_pos, str_placement, portions_len):
+        extsst = {}
+        abs_stream_pos = sst_stream_pos
+        str_counter = 0
+        portion_counter = 0
+        while str_counter < len(str_placement):
+            str_chunk_num, pos_in_chunk = str_placement[str_counter]
+            if str_chunk_num != portion_counter:
+                portion_counter = str_chunk_num
+                abs_stream_pos += portions_len[portion_counter-1]
+                #print hex(abs_stream_pos)
+            str_stream_pos = abs_stream_pos + pos_in_chunk + 4 # header
+            extsst[str_counter] = (pos_in_chunk, str_stream_pos)
+            str_counter += 1
+
+        exsst_str_count_delta = max(8, len(str_placement)*8/0x2000) # maybe smth else?
+        self._rec_data = pack('<H', exsst_str_count_delta)
+        str_counter = 0
+        while str_counter < len(str_placement):
+            self._rec_data += pack('<IHH', extsst[str_counter][1], extsst[str_counter][0], 0)
+            str_counter += exsst_str_count_delta
+
+class DimensionsRecord(BiffRecord):
+    """
+    Record DIMENSIONS, BIFF8:
+
+    Offset  Size    Contents
+    0       4       Index to first used row
+    4       4       Index to last used row, increased by 1
+    8       2       Index to first used column
+    10      2       Index to last used column, increased by 1
+    12      2       Not used
+    """
+    _REC_ID = 0x0200
+    def __init__(self, first_used_row, last_used_row, first_used_col, last_used_col):
+        if first_used_row > last_used_row or first_used_col > last_used_col:
+            # Special case: empty worksheet
+            first_used_row = first_used_col = 0
+            last_used_row = last_used_col = -1
+        self._rec_data = pack('<2L3H',
+            first_used_row, last_used_row + 1,
+            first_used_col, last_used_col + 1,
+            0x00)
+
+
+class Window2Record(BiffRecord):
+    """
+    Record WINDOW2, BIFF8:
+
+    Offset  Size Contents
+    0       2 Option flags (see below)
+    2       2 Index to first visible row
+    4       2 Index to first visible column
+    6       2 Colour index of grid line colour. Note that in BIFF2-BIFF7 an RGB colour is
+                written instead.
+    8       2 Not used
+    10      2 Cached magnification factor in page break preview (in percent); 0 = Default (60%)
+    12      2 Cached magnification factor in normal view (in percent); 0 = Default (100%)
+    14      4 Not used
+
+    In  BIFF8  this record stores used magnification factors for page break
+    preview  and  normal  view.  These  values  are  used  to  restore  the
+    magnification,  when the view is changed. The real magnification of the
+    currently  active  view  is  stored  in the SCL record. The type of the
+    active view is stored in the option flags field (see below).
+
+     0 0001H 0 = Show formula results 1 = Show formulas
+     1 0002H 0 = Do not show grid lines 1 = Show grid lines
+     2 0004H 0 = Do not show sheet headers 1 = Show sheet headers
+     3 0008H 0 = Panes are not frozen 1 = Panes are frozen (freeze)
+     4 0010H 0 = Show zero values as empty cells 1 = Show zero values
+     5 0020H 0 = Manual grid line colour 1 = Automatic grid line colour
+     6 0040H 0 = Columns from left to right 1 = Columns from right to left
+     7 0080H 0 = Do not show outline symbols 1 = Show outline symbols
+     8 0100H 0 = Keep splits if pane freeze is removed 1 = Remove splits if pane freeze is removed
+     9 0200H 0 = Sheet not selected 1 = Sheet selected (BIFF5-BIFF8)
+    10 0400H 0 = Sheet not visible 1 = Sheet visible (BIFF5-BIFF8)
+    11 0800H 0 = Show in normal view 1 = Show in page break preview (BIFF8)
+
+    The freeze flag specifies, if a following PANE record describes unfrozen or frozen panes.
+
+    *** This class appends the optional SCL record ***
+
+    Record SCL, BIFF4-BIFF8:
+
+    This record stores the magnification of the active view of the current worksheet.
+    In BIFF8 this can be either the normal view or the page break preview.
+    This is determined in the WINDOW2 record. The SCL record is part of the
+    Sheet View Settings Block.
+
+    Offset  Size    Contents
+    0       2       Numerator of the view magnification fraction (num)
+    2       2       Denumerator [denominator] of the view magnification fraction (den)
+    The magnification is stored as reduced fraction. The magnification results from num/den.
+
+    SJM note: Excel expresses (e.g.) 25% in reduced form i.e. 1/4. Reason unknown. This code
+    writes 25/100, and Excel is happy with that.
+
+    """
+    _REC_ID = 0x023E
+
+    def __init__(self, options, first_visible_row, first_visible_col,
+        grid_colour, preview_magn, normal_magn, scl_magn):
+        self._rec_data = pack('<7HL', options,
+                                    first_visible_row, first_visible_col,
+                                    grid_colour,
+                                    0x00,
+                                    preview_magn, normal_magn,
+                                    0x00)
+        if scl_magn is not None:
+            self._scl_rec = pack('<4H', 0x00A0, 4, scl_magn, 100)
+        else:
+            self._scl_rec = b''
+
+    def get(self):
+        return self.get_rec_header() + self._rec_data + self._scl_rec
+
+
+class PanesRecord(BiffRecord):
+    """
+    This record stores the position of window panes. It is part of the Sheet
+    View Settings Block. If the sheet does not contain any splits, this
+    record will not occur.
+    A sheet can be split in two different ways, with unfrozen panes or with
+    frozen panes. A flag in the WINDOW2 record specifies, if the panes are
+    frozen, which affects the contents of this record.
+
+    Record PANE, BIFF2-BIFF8:
+    Offset      Size        Contents
+    0           2           Position of the vertical split
+                            (px, 0 = No vertical split):
+                            Unfrozen pane: Width of the left pane(s)
+                            (in twips = 1/20 of a point)
+                            Frozen pane: Number of visible
+                            columns in left pane(s)
+    2           2           Position of the horizontal split
+                            (py, 0 = No horizontal split):
+                            Unfrozen pane: Height of the top pane(s)
+                            (in twips = 1/20 of a point)
+                            Frozen pane: Number of visible
+                            rows in top pane(s)
+    4           2           Index to first visible row
+                            in bottom pane(s)
+    6           2           Index to first visible column
+                            in right pane(s)
+    8           1           Identifier of pane with active
+                            cell cursor
+    [9]         1           Not used (BIFF5-BIFF8 only, not written
+                            in BIFF2-BIFF4)
+
+    If the panes are frozen, pane 0 is always active, regardless
+    of the cursor position. The correct identifiers for all possible
+    combinations of visible panes are shown in the following pictures.
+
+    px = 0, py = 0                  px = 0, py > 0
+    --------------------------      ------------|-------------
+    |                        |      |                        |
+    |                        |      |           3            |
+    |                        |      |                        |
+    -           3            -      --------------------------
+    |                        |      |                        |
+    |                        |      |           2            |
+    |                        |      |                        |
+    --------------------------      ------------|-------------
+
+    px > 0, py = 0                  px > 0, py > 0
+    ------------|-------------      ------------|-------------
+    |           |            |      |           |            |
+    |           |            |      |     3     |      2     |
+    |           |            |      |           |            |
+    -     3     |      1     -      --------------------------
+    |           |            |      |           |            |
+    |           |            |      |     1     |      0     |
+    |           |            |      |           |            |
+    ------------|-------------      ------------|-------------
+    """
+    _REC_ID = 0x0041
+    
+    valid_active_pane = {
+        # entries are of the form:
+        # (int(px > 0),int(px>0)) -> allowed values
+        (0,0):(3,),
+        (0,1):(2,3),
+        (1,0):(1,3),
+        (1,1):(0,1,2,3),
+        }
+    
+    def __init__(self, px, py, first_row_bottom, first_col_right, active_pane):
+        allowed = self.valid_active_pane.get(
+            (int(px > 0),int(py > 0))
+            )
+        if active_pane not in allowed:
+            raise ValueError('Cannot set active_pane to %i, must be one of %s' % (
+                    active_pane, ', '.join(allowed)
+                    ))
+        self._rec_data = pack('<5H',
+                              px, py,
+                              first_row_bottom, first_col_right,
+                              active_pane)
+
+
+class RowRecord(BiffRecord):
+    """
+    This  record  contains  the properties of a single row in a sheet. Rows
+    and cells in a sheet are divided into blocks of 32 rows.
+
+    Record ROW, BIFF3-BIFF8:
+
+    Offset  Size    Contents
+    0       2       Index of this row
+    2       2       Index to column of the first cell which is described by a cell record
+    4       2       Index to column of the last cell which is described by a cell record,
+                    increased by 1
+    6       2       Bit     Mask    Contents
+                    14-0    7FFFH   Height of the row, in twips = 1/20 of a point
+                    15      8000H   0 = Row has custom height; 1 = Row has default height
+    8       2       Not used
+    10      2       In BIFF3-BIFF4 this field contains a relative offset
+                    to calculate stream position of the first cell record
+                    for this row. In BIFF5-BIFF8 this field is not used
+                    anymore, but the DBCELL record instead.
+    12      4       Option flags and default row formatting:
+                    Bit     Mask        Contents
+                    2-0     00000007H   Outline level of the row
+                    4       00000010H   1 = Outline group starts or ends here (depending
+                                        on where the outline buttons are located,
+                                        see WSBOOL record), and is collapsed
+                    5       00000020H   1 = Row is hidden (manually, or by a filter or outline group)
+                    6       00000040H   1 = Row height and default font height do not match
+                    7       00000080H   1 = Row has explicit default format (fl)
+                    8       00000100H   Always 1
+                    27-16   0FFF0000H   If fl=1: Index to default XF record
+                    28      10000000H   1 = Additional space above the row. 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.
+                    29      20000000H   1 = Additional space below the row. 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.
+    """
+
+    _REC_ID = 0x0208
+
+    def __init__(self, index, first_col, last_col, height_options, options):
+        self._rec_data = pack('<6HL', index, first_col, last_col + 1,
+                                        height_options,
+                                        0x00, 0x00,
+                                        options)
+
+class LabelSSTRecord(BiffRecord):
+    """
+    This record represents a cell that contains a string. It replaces the
+    LABEL record and RSTRING record used in BIFF2-BIFF7.
+    """
+    _REC_ID = 0x00FD
+
+    def __init__(self, row, col, xf_idx, sst_idx):
+        self._rec_data = pack('<3HL', row, col, xf_idx, sst_idx)
+
+
+class MergedCellsRecord(BiffRecord):
+    """
+    This record contains all merged cell ranges of the current sheet.
+
+    Record MERGEDCELLS, BIFF8:
+
+    Offset  Size    Contents
+    0       var.    Cell range address list with all merged ranges
+
+    ------------------------------------------------------------------
+
+    A cell range address list consists of a field with the number of ranges
+    and the list of the range addresses.
+
+    Cell range address list, BIFF8:
+
+    Offset  Size            Contents
+    0       2               Number of following cell range addresses (nm)
+    2       8*nm            List of nm cell range addresses
+
+    ---------------------------------------------------------------------
+    Cell range address, BIFF8:
+
+    Offset  Size    Contents
+    0       2       Index to first row
+    2       2       Index to last row
+    4       2       Index to first column
+    6       2       Index to last column
+
+    """
+    _REC_ID = 0x00E5
+
+    def __init__(self, merged_list):
+        i = len(merged_list) - 1
+        while i >= 0:
+            j = 0
+            merged = b''
+            while (i >= 0) and (j < 0x403):
+                r1, r2, c1, c2 = merged_list[i]
+                merged += pack('<4H', r1, r2, c1, c2)
+                i -= 1
+                j += 1
+            self._rec_data += pack('<3H', self._REC_ID, len(merged) + 2, j) + \
+                                    merged
+
+    # for some reason Excel doesn't use CONTINUE
+    def get(self):
+        return self._rec_data
+
+class MulBlankRecord(BiffRecord):
+    """
+    This  record  represents  a  cell  range  of empty cells. All cells are
+    located in the same row.
+
+    Record MULBLANK, BIFF5-BIFF8:
+
+    Offset  Size    Contents
+    0       2       Index to row
+    2       2       Index to first column (fc)
+    4       2*nc    List of nc=lc-fc+1 16-bit indexes to XF records
+    4+2*nc  2       Index to last column (lc)
+    """
+    _REC_ID = 0x00BE
+
+    def __init__(self, row, first_col, last_col, xf_index):
+        blanks_count = last_col-first_col+1
+        self._rec_data = pack('<%dH' % blanks_count, *([xf_index] * blanks_count))
+        self._rec_data = pack('<2H', row, first_col) +  self._rec_data + pack('<H',  last_col)
+
+
+class BlankRecord(BiffRecord):
+    """
+    This  record  represents  an empty cell.
+
+    Record BLANK, BIFF5-BIFF8:
+
+    Offset  Size    Contents
+    0       2       Index to row
+    2       2       Index to first column (fc)
+    4       2       indexes to XF record
+    """
+    _REC_ID = 0x0201
+
+    def __init__(self, row, col, xf_index):
+        self._rec_data = pack('<3H', row, col, xf_index)
+
+
+class RKRecord(BiffRecord):
+    """
+    This record represents a cell that contains an RK value (encoded integer or
+    floating-point value). If a floating-point value cannot be encoded to an RK value,
+    a NUMBER record will be written.
+    """
+    _REC_ID = 0x027E
+
+    def __init__(self, row, col, xf_index, rk_encoded):
+        self._rec_data = pack('<3Hi', row, col, xf_index, rk_encoded)
+
+
+class NumberRecord(BiffRecord):
+    """
+    This record represents a cell that contains an IEEE-754 floating-point value.
+    """
+    _REC_ID = 0x0203
+
+    def __init__(self, row, col, xf_index, number):
+        self._rec_data = pack('<3Hd', row, col, xf_index, number)
+
+class BoolErrRecord(BiffRecord):
+    """
+    This record represents a cell that contains a boolean or error value.
+    """
+    _REC_ID = 0x0205
+
+    def __init__(self, row, col, xf_index, number, is_error):
+        self._rec_data = pack('<3HBB', row, col, xf_index, number, is_error)
+
+
+class FormulaRecord(BiffRecord):
+    """
+    Offset Size Contents
+    0      2    Index to row
+    2      2    Index to column
+    4      2    Index to XF record
+    6      8    Result of the formula
+    14     2    Option flags:
+                Bit Mask    Contents
+                0   0001H   1 = Recalculate always
+                1   0002H   1 = Calculate on open
+                3   0008H   1 = Part of a shared formula
+    16     4    Not used
+    20     var. Formula data (RPN token array)
+
+    """
+    _REC_ID = 0x0006
+
+    def __init__(self, row, col, xf_index, rpn, calc_flags=0):
+        self._rec_data = pack('<3HQHL', row, col, xf_index, 0xFFFF000000000003, calc_flags & 3, 0) + rpn
+
+
+class GutsRecord(BiffRecord):
+    """
+    This record contains information about the layout of outline symbols.
+
+    Record GUTS, BIFF3-BIFF8:
+
+    Offset  Size    Contents
+    0       2       Width of the area to display row outlines (left of the sheet), in pixel
+    2       2       Height of the area to display column outlines (above the sheet), in pixel
+    4       2       Number of visible row outline levels (used row levels + 1; or 0, if not used)
+    6       2       Number of visible column outline levels (used column levels + 1; or 0, if not used)
+
+    """
+
+    _REC_ID = 0x0080
+
+    def __init__(self, row_gut_width, col_gut_height, row_visible_levels, col_visible_levels):
+        self._rec_data = pack('<4H', row_gut_width, col_gut_height, row_visible_levels, col_visible_levels)
+
+class WSBoolRecord(BiffRecord):
+    """
+    This  record stores a 16 bit value with Boolean options for the current
+    sheet.  From BIFF5 on the "Save external linked values" option is moved
+    to the record BOOKBOOL.
+
+    Option flags of record WSBOOL, BIFF3-BIFF8:
+
+    Bit     Mask    Contents
+    0       0001H   0 = Do not show automatic page breaks
+                    1 = Show automatic page breaks
+    4       0010H   0 = Standard sheet
+                    1 = Dialogue sheet (BIFF5-BIFF8)
+    5       0020H   0 = No automatic styles in outlines
+                    1 = Apply automatic styles to outlines
+    6       0040H   0 = Outline buttons above outline group
+                    1 = Outline buttons below outline group
+    7       0080H   0 = Outline buttons left of outline group
+                    1 = Outline buttons right of outline group
+    8       0100H   0 = Scale printout in percent
+                    1 = Fit printout to number of pages
+    9       0200H   0 = Save external linked values (BIFF3?BIFF4 only)
+                    1 = Do not save external linked values (BIFF3?BIFF4 only)
+    10      0400H   0 = Do not show row outline symbols
+                    1 = Show row outline symbols
+    11      0800H   0 = Do not show column outline symbols
+                    1 = Show column outline symbols
+    13-12   3000H   These flags specify the arrangement of windows.
+                    They are stored in BIFF4 only.
+                    00 = Arrange windows tiled
+                    01 = Arrange windows horizontal
+                    10 = Arrange windows vertical112 = Arrange windows cascaded
+    The following flags are valid for BIFF4-BIFF8 only:
+    14      4000H   0 = Standard expression evaluation
+                    1 = Alternative expression evaluation
+    15      8000H   0 = Standard formula entries
+                    1 = Alternative formula entries
+
+    """
+    _REC_ID = 0x0081
+
+    def __init__(self, options):
+        self._rec_data = pack('<H', options)
+
+class ColInfoRecord(BiffRecord):
+    """
+    This record specifies the width for a given range of columns.
+    If a column does not have a corresponding COLINFO record,
+    the width specified in the record STANDARDWIDTH is used. If
+    this record is also not present, the contents of the record
+    DEFCOLWIDTH is used instead.
+    This record also specifies a default XF record to use for
+    cells in the columns that are not described by any cell record
+    (which contain the XF index for that cell). Additionally,
+    the option flags field contains hidden, outline, and collapsed
+    options applied at the columns.
+
+    Record COLINFO, BIFF3-BIFF8:
+
+    Offset  Size    Contents
+    0       2       Index to first column in the range
+    2       2       Index to last column in the range
+    4       2       Width of the columns in 1/256 of the width of the zero character, using default font
+                    (first FONT record in the file)
+    6       2       Index to XF record for default column formatting
+    8       2       Option flags:
+                    Bits    Mask    Contents
+                    0       0001H   1 = Columns are hidden
+                    10-8    0700H   Outline level of the columns (0 = no outline)
+                    12      1000H   1 = Columns are collapsed
+    10      2       Not used
+
+    """
+    _REC_ID = 0x007D
+
+    def __init__(self, first_col, last_col, width, xf_index, options, unused):
+        self._rec_data = pack('<6H', first_col, last_col, width, xf_index, options, unused)
+
+class CalcModeRecord(BiffRecord):
+    """
+    This record is part of the Calculation Settings Block.
+    It specifies whether to calculate formulas manually,
+    automatically or automatically except for multiple table operations.
+
+    Record CALCMODE, BIFF2-BIFF8:
+
+    Offset  Size    Contents
+    0       2       FFFFH = automatic except for multiple table operations
+                    0000H = manually
+                    0001H = automatically (default)
+    """
+    _REC_ID = 0x000D
+
+    def __init__(self, calc_mode):
+        self._rec_data = pack('<h', calc_mode)
+
+
+class CalcCountRecord(BiffRecord):
+    """
+    This record is part of the Calculation Settings Block. It specifies the maximum
+    number of times the formulas should be iteratively calculated. This is a fail-safe
+    against mutually recursive formulas locking up a spreadsheet application.
+
+    Record CALCCOUNT, BIFF2-BIFF8:
+
+    Offset  Size    Contents
+    0       2       Maximum number of iterations allowed in circular references
+    """
+
+    _REC_ID = 0x000C
+
+    def __init__(self, calc_count):
+        self._rec_data = pack('<H', calc_count)
+
+class RefModeRecord(BiffRecord):
+    """
+    This record is part of the Calculation Settings Block.
+    It stores which method is used to show cell addresses in formulas.
+    The “RC” mode uses numeric indexes for rows and columns,
+    i.e. “R(1)C(-1)”, or “R1C1:R2C2”.
+    The “A1” mode uses characters for columns and numbers for rows,
+    i.e. “B1”, or “$A$1:$B$2”.
+
+    Record REFMODE, BIFF2-BIFF8:
+
+    Offset  Size    Contents
+    0       2       0 = RC mode; 1 = A1 mode
+
+    """
+    _REC_ID = 0x00F
+
+    def __init__(self, ref_mode):
+        self._rec_data = pack('<H', ref_mode)
+
+class IterationRecord(BiffRecord):
+    """
+    This record is part of the Calculation Settings Block.
+    It stores if iterations are allowed while calculating recursive formulas.
+
+    Record ITERATION, BIFF2-BIFF8:
+
+    Offset  Size    Contents
+    0       2       0 = Iterations off; 1 = Iterations on
+    """
+    _REC_ID = 0x011
+
+    def __init__(self, iterations_on):
+        self._rec_data = pack('<H', iterations_on)
+
+class DeltaRecord(BiffRecord):
+    """
+    This record is part of the Calculation Settings Block.
+    It stores the maximum change of the result to exit an iteration.
+
+    Record DELTA, BIFF2-BIFF8:
+
+    Offset  Size    Contents
+    0       8       Maximum change in iteration
+                    (IEEE 754 floating-point value,
+                     64bit double precision)
+    """
+    _REC_ID = 0x010
+
+    def __init__(self, delta):
+        self._rec_data = pack('<d', delta)
+
+class SaveRecalcRecord(BiffRecord):
+    """
+    This record is part of the Calculation Settings Block.
+    It contains the “Recalculate before save” option in
+    Excel's calculation settings dialogue.
+
+    Record SAVERECALC, BIFF3-BIFF8:
+
+    Offset  Size    Contents
+    0       2       0 = Do not recalculate;
+                    1 = Recalculate before saving the document
+
+    """
+    _REC_ID = 0x05F
+
+    def __init__(self, recalc):
+        self._rec_data = pack('<H', recalc)
+
+class PrintHeadersRecord(BiffRecord):
+    """
+    This record stores if the row and column headers
+    (the areas with row numbers and column letters) will be printed.
+
+    Record PRINTHEADERS, BIFF2-BIFF8:
+
+    Offset  Size    Contents
+    0       2       0 = Do not print row/column headers;
+                    1 = Print row/column headers
+    """
+    _REC_ID = 0x02A
+
+    def __init__(self, print_headers):
+        self._rec_data = pack('<H', print_headers)
+
+
+class PrintGridLinesRecord(BiffRecord):
+    """
+    This record stores if sheet grid lines will be printed.
+
+    Record PRINTGRIDLINES, BIFF2-BIFF8:
+
+    Offset  Size    Contents
+    0       2       0 = Do not print sheet grid lines;
+                    1 = Print sheet grid lines
+
+    """
+    _REC_ID = 0x02B
+
+    def __init__(self, print_grid):
+        self._rec_data = pack('<H', print_grid)
+
+
+class GridSetRecord(BiffRecord):
+    """
+    This record specifies if the option to print sheet grid lines
+    (record PRINTGRIDLINES) has ever been changed.
+
+    Record GRIDSET, BIFF3-BIFF8:
+
+    Offset  Size    Contents
+    0       2       0 = Print grid lines option never changed
+                    1 = Print grid lines option changed
+    """
+    _REC_ID = 0x082
+
+    def __init__(self, print_grid_changed):
+        self._rec_data = pack('<H', print_grid_changed)
+
+
+class DefaultRowHeightRecord(BiffRecord):
+    """
+    This record specifies the default height and default flags
+    for rows that do not have a corresponding ROW record.
+
+    Record DEFAULTROWHEIGHT, BIFF3-BIFF8:
+
+    Offset  Size    Contents
+    0       2       Option flags:
+                    Bit Mask    Contents
+                    0   0001H   1 = Row height and default font height do not match
+                    1   0002H   1 = Row is hidden
+                    2   0004H   1 = Additional space above the row
+                    3   0008H   1 = Additional space below the row
+    2       2       Default height for unused rows, in twips = 1/20 of a point
+
+    """
+    _REC_ID = 0x0225
+
+    def __init__(self, options, def_height):
+        self._rec_data = pack('<2H', options, def_height)
+
+
+class DefColWidthRecord(BiffRecord):
+    """
+    This record specifies the default column width for columns that
+    do not have a specific width set using the record COLINFO or COLWIDTH.
+    This record has no effect, if a STANDARDWIDTH record is present in the file.
+
+    Record DEFCOLWIDTH, BIFF2-BIFF8:
+
+    Offset  Size    Contents
+    0       2       Column width in characters, using the width of the zero
+                    character from default font (first FONT record in the file)
+    """
+    _REC_ID = 0x0055
+
+    def __init__(self, def_width):
+        self._rec_data = pack('<H', options, def_width)
+
+class HorizontalPageBreaksRecord(BiffRecord):
+    """
+    This  record  is  part  of  the  Page  Settings  Block. It contains all
+    horizontal manual page breaks.
+
+    Record HORIZONTALPAGEBREAKS, BIFF8:
+    Offset  Size  Contents
+    0       2     Number of following row index structures (nm)
+    2       6nm   List of nm row index structures. Each row index
+                  structure contains:
+                    Offset  Size    Contents
+                    0       2       Index to first row below the page break
+                    2       2       Index to first column of this page break
+                    4       2       Index to last column of this page break
+
+    The row indexes in the lists must be ordered ascending.
+    If in BIFF8 a row contains several page breaks, they must be ordered
+    ascending by start column index.
+    """
+    _REC_ID = 0x001B
+
+    def __init__(self, breaks_list):
+        self._rec_data = pack('<H', len(breaks_list))
+        for r, c1, c2 in breaks_list:
+            self._rec_data += pack('<3H', r, c1, c2)
+
+class VerticalPageBreaksRecord(BiffRecord):
+    """
+    This  record  is  part  of  the  Page  Settings  Block. It contains all
+    vertical manual page breaks.
+
+    Record VERTICALPAGEBREAKS, BIFF8:
+    Offset  Size  Contents
+    0       2     Number of following column index structures (nm)
+    2       6nm   List of nm column index structures. Each column index
+                  structure contains:
+                    Offset  Size    Contents
+                    0       2       Index to first column following the page
+                                    break
+                    2       2       Index to first row of this page break
+                    4       2       Index to last row of this page break
+
+    The column indexes in the lists must be ordered ascending.
+    If in BIFF8 a column contains several page breaks, they must be ordered
+    ascending by start row index.
+    """
+    _REC_ID = 0x001A
+
+    def __init__(self, breaks_list):
+        self._rec_data = pack('<H', len(breaks_list))
+        for r, c1, c2 in breaks_list:
+            self._rec_data += pack('<3H', r, c1, c2)
+
+class HeaderRecord(BiffRecord):
+    """
+    This record is part of the Page Settings Block. It specifies the
+    page  header  string  for  the current worksheet. If this record is not
+    present  or  completely  empty  (record  size is 0), the sheet does not
+    contain a page header.
+
+    Record HEADER for non-empty page header, BIFF2-BIFF8:
+    Offset      Size    Contents
+    0           var.    Page header string
+                        BIFF2-BIFF7:    Non-empty byte string, 8bit string
+                        length
+                        BIFF8: Non-empty Unicode string, 16bit string length
+    The  header  string may contain special commands, i.e. placeholders for
+    the  page  number,  current  date, or text formatting attributes. These
+    fields  are  represented  by  single  letters (exception: font name and
+    size,  see  below)  with  a  leading  ampersand ("&"). If the ampersand
+    is  part  of the regular header text, it will be duplicated ("&&"). The
+    page  header is divided into 3 sections: the left, the centred, and the
+    right  section.  Each  section  is introduced by a special command. All
+    text  and all commands following are part of the selected section. Each
+    section  starts  with the text formatting specified in the default font
+    (first  FONT  record  in  the  file). Active formatting attributes from
+    a previous section do not go into the next section.
+
+    The following table shows all available commands:
+
+    Command         Contents
+    &&              The "&" character itself
+    &L              Start of the left section
+    &C              Start of the centred section
+    &R              Start of the right section
+    &P              Current page number
+    &N              Page count
+    &D              Current date
+    &T              Current time
+    &A              Sheet name (BIFF5-BIFF8)
+    &F              File name without path
+    &Z              File path without file name (BIFF8X)
+    &G              Picture (BIFF8X)
+    &B              Bold on/off (BIFF2-BIFF4)
+    &I              Italic on/off (BIFF2-BIFF4)
+    &U              Underlining on/off
+    &E              Double underlining on/off (BIFF5-BIFF8)
+    &S              Strikeout on/off
+    &X              Superscript on/off (BIFF5-BIFF8)
+    &Y              Subscript on/off (BIFF5-BIFF8)
+    &"<fontname>"   Set new font <fontname>
+    &"<fontname>,<fontstyle>"
+                    Set new font with specified style <fontstyle>.
+                    The style <fontstyle> is in most cases one of
+                    "Regular", "Bold", "Italic", or "Bold Italic".
+                    But this setting is dependent on the used font,
+                    it may differ (localised style names, or "Standard",
+                    "Oblique", ...). (BIFF5-BIFF8)
+    &<fontheight>   Set font height in points (<fontheight> is a decimal value).
+                    If this command is followed by a plain number to be printed
+                    in the header, it will be separated from the font height
+                    with a space character.
+
+    """
+    _REC_ID = 0x0014
+
+    def __init__(self, header_str):
+        self._rec_data = upack2(header_str)
+
+class FooterRecord(BiffRecord):
+    """
+    Semantic is equal to HEADER record
+    """
+    _REC_ID = 0x0015
+
+    def __init__(self, footer_str):
+        self._rec_data = upack2(footer_str)
+
+
+class HCenterRecord(BiffRecord):
+    """
+    This  record  is  part  of the Page Settings Block. It specifies if the
+    sheet is centred horizontally when printed.
+
+    Record HCENTER, BIFF3-BIFF8:
+
+    Offset  Size    Contents
+    0       2       0 = Print sheet left aligned
+                    1 = Print sheet centred horizontally
+
+    """
+    _REC_ID = 0x0083
+
+    def __init__(self, is_horz_center):
+        self._rec_data = pack('<H', is_horz_center)
+
+
+class VCenterRecord(BiffRecord):
+    """
+    This  record  is  part  of the Page Settings Block. It specifies if the
+    sheet is centred vertically when printed.
+
+    Record VCENTER, BIFF3-BIFF8:
+
+    Offset  Size    Contents
+    0       2       0 = Print sheet aligned at top page border
+                    1 = Print sheet vertically centred
+
+    """
+    _REC_ID = 0x0084
+
+    def __init__(self, is_vert_center):
+        self._rec_data = pack('<H', is_vert_center)
+
+
+class LeftMarginRecord(BiffRecord):
+    """
+    This  record  is  part of the Page Settings Block. It contains the left
+    page margin of the current worksheet.
+
+    Record LEFTMARGIN, BIFF2-BIFF8:
+
+    Offset  Size    Contents
+    0       8       Left page margin in inches
+                    (IEEE 754 floating-point value, 64bit double precision)
+
+    """
+    _REC_ID = 0x0026
+
+    def __init__(self, margin):
+        self._rec_data = pack('<d', margin)
+
+
+class RightMarginRecord(BiffRecord):
+    """
+    This  record  is  part of the Page Settings Block. It contains the right
+    page margin of the current worksheet.
+
+    Offset  Size    Contents
+    0       8       Right page margin in inches
+                    (IEEE 754 floating-point value, 64?bit double precision)
+
+    """
+    _REC_ID = 0x0027
+
+    def __init__(self, margin):
+        self._rec_data = pack('<d', margin)
+
+class TopMarginRecord(BiffRecord):
+    """
+    This  record  is  part of the Page Settings Block. It contains the top
+    page margin of the current worksheet.
+
+    Offset  Size    Contents
+    0       8       Top page margin in inches
+                    (IEEE 754 floating-point value, 64?bit double precision)
+
+    """
+    _REC_ID = 0x0028
+
+    def __init__(self, margin):
+        self._rec_data = pack('<d', margin)
+
+
+class BottomMarginRecord(BiffRecord):
+    """
+    This  record  is  part of the Page Settings Block. It contains the bottom
+    page margin of the current worksheet.
+
+    Offset  Size    Contents
+    0       8       Bottom page margin in inches
+                    (IEEE 754 floating-point value, 64?bit double precision)
+
+    """
+    _REC_ID = 0x0029
+
+    def __init__(self, margin):
+        self._rec_data = pack('<d', margin)
+
+class SetupPageRecord(BiffRecord):
+    """
+    This   record   is  part of the Page Settings Block. It stores the page
+    format   settings   of   the  current sheet. The pages may be scaled in
+    percent   or  by  using  an  absolute  number of pages. This setting is
+    located   in  the  WSBOOL  record.  If  pages  are  scaled in  percent,
+    the   scaling  factor  in  this  record is used, otherwise the "Fit  to
+    pages"  values. One of the "Fit to pages" values may be 0. In this case
+    the sheet is scaled to fit only to the other value.
+
+    Record SETUP, BIFF5-BIFF8:
+
+    Offset      Size    Contents
+    0           2       Paper size (see below)
+    2           2       Scaling factor in percent
+    4           2       Start page number
+    6           2       Fit worksheet width to this number of pages
+                        (0 = use as many as needed)
+    8           2       Fit worksheet height to this number of pages
+                        (0 = use as many as needed)
+    10          2       Option flags:
+                        Bit     Mask        Contents
+                        0       0001H       0 = Print pages in columns
+                                            1 = Print pages in rows
+                        1       0002H       0 = Landscape
+                                            1 = Portrait
+                        2       0004H       1 = Paper size, scaling factor,
+                                            paper orientation (portrait/landscape),
+                                            print resolution and number of copies
+                                            are not initialised
+                        3       0008H       0 = Print coloured
+                                            1 = Print black and white
+                        4       0010H       0 = Default print quality
+                                            1 = Draft quality
+                        5       0020H       0 = Do not print cell notes
+                                            1 = Print cell notes
+                        6       0040H       0 = Paper orientation setting is valid
+                                            1 = Paper orientation setting not
+                                            initialised
+                        7       0080H       0 = Automatic page numbers
+                                            1 = Use start page number
+                        The following flags are valid for BIFF8 only:
+                        9       0200H       0 = Print notes as displayed
+                                            1 = Print notes at end of sheet
+                        11-10   0C00H       00 = Print errors as displayed
+                                            01 = Do not print errors
+                                            10 = Print errors as "--"
+                                            11 = Print errors as "#N/A!"
+    12          2       Print resolution in dpi
+    14          2       Vertical print resolution in dpi
+    16          8       Header margin (IEEE 754 floating-point value,
+                        64bit double precision)
+    24          8       Footer margin (IEEE 754 floating-point value,
+                        64bit double precision)
+    32          2       Number of copies to print
+
+
+    PAPER TYPES:
+
+    Index   Paper type              Paper size
+    0       Undefined
+    1       Letter                  8 1/2" x 11"
+    2       Letter small            8 1/2" x 11"
+    3       Tabloid                 11" x 17"
+    4       Ledger                  17" x 11"
+    5       Legal                   8 1/2" x 14"
+    6       Statement               5 1/2" x 8 1/2"
+    7       Executive               7 1/4" x 10 1/2"
+    8       A3                      297mm x 420mm
+    9       A4                      210mm x 297mm
+    10      A4 small                210mm x 297mm
+    11      A5                      148mm x 210mm
+    12      B4 (JIS)                257mm x 364mm
+    13      B5 (JIS)                182mm x 257mm
+    14      Folio                   8 1/2" x 13"
+    15      Quarto                  215mm x 275mm
+    16      10x14                   10" x 14"
+    17      11x17                   11" x 17"
+    18      Note                    8 1/2" x 11"
+    19      Envelope #9             3 7/8" x 8 7/8"
+    20      Envelope #10            4 1/8" x 9 1/2"
+    21      Envelope #11            4 1/2" x 10 3/8"
+    22      Envelope #12            4 3/4" x 11"
+    23      Envelope #14            5" x 11 1/2"
+    24      C                       17" x 22"
+    25      D                       22" x 34"
+    26      E                       34" x 44"
+    27      Envelope DL             110mm x 220mm
+    28      Envelope C5             162mm x 229mm
+    29      Envelope C3             324mm x 458mm
+    30      Envelope C4             229mm x 324mm
+    31      Envelope C6             114mm x 162mm
+    32      Envelope C6/C5          114mm x 229mm
+    33      B4 (ISO)                250mm x 353mm
+    34      B5 (ISO)                176mm x 250mm
+    35      B6 (ISO)                125mm x 176mm
+    36      Envelope Italy          110mm x 230mm
+    37      Envelope Monarch        3 7/8" x 7 1/2"
+    38      63/4 Envelope           3 5/8" x 6 1/2"
+    39      US Standard Fanfold     14 7/8" x 11"
+    40      German Std. Fanfold     8 1/2" x 12"
+    41      German Legal Fanfold    8 1/2" x 13"
+    42      B4 (ISO)                250mm x 353mm
+    43      Japanese Postcard       100mm x 148mm
+    44      9x11                    9" x 11"
+    45      10x11                   10" x 11"
+    46      15x11                   15" x 11"
+    47      Envelope Invite         220mm x 220mm
+    48      Undefined
+    49      Undefined
+    50      Letter Extra            9 1/2" x 12"
+    51      Legal Extra             9 1/2" x 15"
+    52      Tabloid Extra           11 11/16" x 18"
+    53      A4 Extra                235mm x 322mm
+    54      Letter Transverse       8 1/2" x 11"
+    55      A4 Transverse           210mm x 297mm
+    56      Letter Extra Transv.    9 1/2" x 12"
+    57      Super A/A4              227mm x 356mm
+    58      Super B/A3              305mm x 487mm
+    59      Letter Plus             8 1/2" x 12 11/16"
+    60      A4 Plus                 210mm x 330mm
+    61      A5 Transverse           148mm x 210mm
+    62      B5 (JIS) Transverse     182mm x 257mm
+    63      A3 Extra                322mm x 445mm
+    64      A5 Extra                174mm x 235mm
+    65      B5 (ISO) Extra          201mm x 276mm
+    66      A2                      420mm x 594mm
+    67      A3 Transverse           297mm x 420mm
+    68      A3 Extra Transverse     322mm x 445mm
+    69      Dbl. Japanese Postcard  200mm x 148mm
+    70      A6                      105mm x 148mm
+    71
+    72
+    73
+    74
+    75      Letter Rotated          11" x 8 1/2"
+    76      A3 Rotated              420mm x 297mm
+    77      A4 Rotated              297mm x 210mm
+    78      A5 Rotated              210mm x 148mm
+    79      B4 (JIS) Rotated        364mm x 257mm
+    80      B5 (JIS) Rotated        257mm x 182mm
+    81      Japanese Postcard Rot.  148mm x 100mm
+    82      Dbl. Jap. Postcard Rot. 148mm x 200mm
+    83      A6 Rotated              148mm x 105mm
+    84
+    85
+    86
+    87
+    88      B6 (JIS)                128mm x 182mm
+    89      B6 (JIS) Rotated        182mm x 128mm
+    90      12x11                   12" x 11"
+
+    """
+    _REC_ID = 0x00A1
+    def __init__(self, paper, scaling, start_num, fit_width_to, fit_height_to,
+                    options,
+                    hres, vres,
+                    header_margin, footer_margin,
+                    num_copies):
+        self._rec_data = pack('<8H2dH', paper, scaling, start_num,
+                                        fit_width_to, fit_height_to, \
+                                        options,
+                                        hres, vres,
+                                        header_margin, footer_margin,
+                                        num_copies)
+
+class NameRecord(BiffRecord):
+    """
+    This record is part of a Link Table. It contains the name and the token
+    array of an internal defined name. Token arrays of defined names
+    contain tokens with aberrant token classes.
+
+    Record NAME, BIFF5/BIFF7:
+    Offset      Size    Contents
+       0          2     Option flags, see below
+       2          1     Keyboard shortcut (only for command macro names, see below)
+       3          1     Length of the name (character count, ln)
+       4          2     Size of the formula data (sz)
+       6          2     0 = Global name, otherwise index to EXTERNSHEET record (one-based)
+       8          2     0 = Global name, otherwise index to sheet (one-based)
+      10          1     Length of menu text (character count, lm)
+      11          1     Length of description text (character count, ld)
+      12          1     Length of help topic text (character count, lh)
+      13          1     Length of status bar text (character count, ls)
+      14         ln     Character array of the name
+    14+ln        sz     Formula data (RPN token array without size field, 4)
+  14+ln+sz       lm     Character array of menu text
+     var.        ld     Character array of description text
+     var.        lh     Character array of help topic text
+     var.        ls     Character array of status bar text
+
+    Record NAME, BIFF8:
+    Offset      Size Contents
+       0          2  Option flags, see below
+       2          1  Keyboard shortcut (only for command macro names, see below)
+       3          1  Length of the name (character count, ln)
+       4          2  Size of the formula data (sz)
+       6          2  Not used
+       8          2  0 = Global name, otherwise index to sheet (one-based)
+      10          1  Length of menu text (character count, lm)
+      11          1  Length of description text (character count, ld)
+      12          1  Length of help topic text (character count, lh)
+      13          1  Length of status bar text (character count, ls)
+      14        var. Name (Unicode string without length field, 3.4)
+     var.        sz  Formula data (RPN token array without size field, 4)
+    [var.]      var. (optional, only if lm > 0) Menu text (Unicode string without length field, 3.4)
+    [var.]      var. (optional, only if ld > 0) Description text (Unicode string without length field, 3.4)
+    [var.]      var. (optional, only if lh > 0) Help topic text (Unicode string without length field, 3.4)
+    [var.]      var. (optional, only if ls > 0) Status bar text (Unicode string without length field, 3.4)
+    """
+    _REC_ID = 0x0018
+
+    def __init__(self, options, keyboard_shortcut, name, sheet_index, rpn, menu_text='', desc_text='', help_text='', status_text=''):
+        if type(name) == int:
+            uname = chr(name)
+        else:
+            uname = upack1(name)[1:]
+        uname_len = len(uname)
+
+        #~ self._rec_data = pack('<HBBHHHBBBB%ds%ds' % (uname_len, len(rpn)), options, keyboard_shortcut, uname_len, len(rpn), 0x0000, sheet_index, len(menu_text), len(desc_text), len(help_text), len(status_text), uname, rpn) + menu_text + desc_text + help_text + status_text
+        self._rec_data = pack('<HBBHHHBBBBB%ds%ds' % (uname_len, len(rpn)), options, keyboard_shortcut, uname_len, len(rpn), 0x0000, sheet_index, 0x00, len(menu_text), len(desc_text), len(help_text), len(status_text), uname, rpn) + menu_text + desc_text + help_text + status_text
+
+# Excel (both 2003 and 2007) don't like refs
+# split over a record boundary, which is what the
+# standard BiffRecord.get method does.
+
+# 8224 max data bytes in a BIFF record
+# 6 bytes per ref
+# 1370 = floor((8224 - 2) / 6.0) max refs in a record
+
+_maxRefPerRecord = 1370
+
+class ExternSheetRecord(BiffRecord):
+    """
+    In BIFF8 the record stores a list with indexes to SUPBOOK
+    records (list of REF structures, 6.100). See 5.10.3 for
+    details about external references in BIFF8.
+
+    Record EXTERNSHEET, BIFF8:
+    Offset          Size      Contents
+       0             2        Number of following REF structures (nm)
+       2           6nm        List of nm REF structures. Each REF contains the following data:
+                              Offset     Size     Contents
+                                 0         2      Index to SUPBOOK record
+                                 2         2      Index to first SUPBOOK sheet
+                                 4         2      Index to last SUPBOOK sheet
+    """
+    _REC_ID = 0x0017
+
+    def __init__(self, refs):
+
+        # do we always need this ref? or only if there are no refs?
+        # (I believe that if there are no refs then we should not generate the link table - Ruben)
+        #refs.insert(0, (0,0,0))
+
+        self.refs = refs
+
+    def get(self):
+        res = []
+        nrefs = len(self.refs)
+        for idx in xrange(0, nrefs, _maxRefPerRecord):
+            chunk = self.refs[idx:idx+_maxRefPerRecord]
+            krefs = len(chunk)
+            if idx: # CONTINUE record
+                header = pack("<HH", 0x003C, 6 * krefs)
+            else: # ExternSheetRecord
+                header = pack("<HHH", self._REC_ID, 6 * krefs + 2, nrefs)
+            res.append(header)
+            res.extend(pack("<HHH", *r) for r in chunk)
+        return b''.join(res)
+
+class SupBookRecord(BiffRecord):
+    """
+    This record mainly stores the URL of an external document
+    and a list of sheet names inside this document. Furthermore
+    it is used to store DDE and OLE object links, or to indicate
+    an internal 3D reference or an add-in function. See 5.10.3
+    for details about external references in BIFF8.
+
+    """
+    _REC_ID = 0x01AE
+
+class InternalReferenceSupBookRecord(SupBookRecord):
+    """
+    In each file occurs a SUPBOOK that is used for internal 3D
+    references. It stores the number of sheets of the own document.
+
+    Record SUPBOOK for 3D references, BIFF8:
+    Offset         Size   Contents
+      0             2     Number of sheets in this document
+      2             2     01H 04H (relict of BIFF5/BIFF7, the byte string "<04H>", see 3.9.1)
+
+    """
+
+    def __init__(self, num_sheets):
+        self._rec_data = pack('<HBB', num_sheets, 0x01, 0x04)
+
+class XcallSupBookRecord(SupBookRecord):
+    """
+    Add-in function names are stored in EXTERNNAME records following this record.
+
+    Offset  Size    Contents
+    0       2       0001H
+    2       2       01H 3AH (relict of BIFF5, the byte string ':', see EXTERNSHEET record, 5.41)
+
+    """
+
+    def __init__(self):
+        self._rec_data = pack('<HBB', 1, 0x01, 0x3A)
+
+
+class ExternnameRecord(BiffRecord):
+    """
+    Record EXTERNNAME for external names and Analysis add-in functions, BIFF5-BIFF8:
+    Offset  Size    Contents
+    0       2       Option flags (see below)
+    2       2       0 for global names, or:
+                    BIFF5: One-based index to EXTERNSHEET record containing the sheet name,
+                    BIFF8: One-based index to sheet list in preceding EXTERNALBOOK record.
+    4       2       Not used
+    6       var.    BIFF5: Name (byte string, 8-bit string length, ?2.5.2).
+                    BIFF8: Name (Unicode string, 8-bit string length, ?2.5.3).
+                    See DEFINEDNAME record (?5.33) for a list of built-in names, if the built-in flag is set
+                    in the option flags above.
+    var.    var.    Formula data (RPN token array, ?3)
+
+    Option flags for external names (BIFF5-BIFF8)
+    Bit     Mask    Contents
+    0       0001H   0 = Standard name; 1 = Built-in name
+    1       0002H   0 = Manual link; 1 = Automatic link (DDE links and OLE links only)
+    2       0004H   1 = Picture link (DDE links and OLE links only)
+    3       0008H   1 = This is the “StdDocumentName” identifier (DDE links only)
+    4       0010H   1 = OLE link
+    14-5    7FE0H   Clipboard format of last successful update (DDE links and OLE links only)
+    15      8000H   1 = Iconified picture link (BIFF8 OLE links only)
+    """
+    _REC_ID = 0x0023
+
+    def __init__(self, options=0, index=0, name=None, fmla=None):
+        self._rec_data = pack('<HHH', options, index, 0) + upack1(name) + fmla
+

+ 275 - 0
desktop/core/ext-py/xlwt-1.3.0/xlwt/Bitmap.py

@@ -0,0 +1,275 @@
+# -*- coding: windows-1251 -*-
+
+#  Portions are Copyright (C) 2005 Roman V. Kiseliov
+#  Portions are Copyright (c) 2004 Evgeny Filatov <fufff@users.sourceforge.net>
+#  Portions are Copyright (c) 2002-2004 John McNamara (Perl Spreadsheet::WriteExcel)
+
+from .BIFFRecords import BiffRecord
+from struct import pack, unpack
+
+
+def _size_col(sheet, col):
+    return sheet.col_width(col)
+
+
+def _size_row(sheet, row):
+    return sheet.row_height(row)
+
+
+def _position_image(sheet, row_start, col_start, x1, y1, width, height):
+    """Calculate the vertices that define the position of the image as required by
+    the OBJ record.
+
+             +------------+------------+
+             |     A      |      B     |
+       +-----+------------+------------+
+       |     |(x1,y1)     |            |
+       |  1  |(A1)._______|______      |
+       |     |    |              |     |
+       |     |    |              |     |
+       +-----+----|    BITMAP    |-----+
+       |     |    |              |     |
+       |  2  |    |______________.     |
+       |     |            |        (B2)|
+       |     |            |     (x2,y2)|
+       +---- +------------+------------+
+
+    Example of a bitmap that covers some of the area from cell A1 to cell B2.
+
+    Based on the width and height of the bitmap we need to calculate 8 vars:
+        col_start, row_start, col_end, row_end, x1, y1, x2, y2.
+    The width and height of the cells are also variable and have to be taken into
+    account.
+    The values of col_start and row_start are passed in from the calling
+    function. The values of col_end and row_end are calculated by subtracting
+    the width and height of the bitmap from the width and height of the
+    underlying cells.
+    The vertices are expressed as a percentage of the underlying cell width as
+    follows (rhs values are in pixels):
+
+           x1 = X / W *1024
+           y1 = Y / H *256
+           x2 = (X-1) / W *1024
+           y2 = (Y-1) / H *256
+
+           Where:  X is distance from the left side of the underlying cell
+                   Y is distance from the top of the underlying cell
+                   W is the width of the cell
+                   H is the height of the cell
+
+    Note: the SDK incorrectly states that the height should be expressed as a
+    percentage of 1024.
+
+    col_start  - Col containing upper left corner of object
+    row_start  - Row containing top left corner of object
+    x1  - Distance to left side of object
+    y1  - Distance to top of object
+    width  - Width of image frame
+    height  - Height of image frame
+
+    """
+    # Adjust start column for offsets that are greater than the col width
+    while x1 >= _size_col(sheet, col_start):
+        x1 -= _size_col(sheet, col_start)
+        col_start += 1
+    # Adjust start row for offsets that are greater than the row height
+    while y1 >= _size_row(sheet, row_start):
+        y1 -= _size_row(sheet, row_start)
+        row_start += 1
+    # Initialise end cell to the same as the start cell
+    row_end = row_start   # Row containing bottom right corner of object
+    col_end = col_start   # Col containing lower right corner of object
+    width = width + x1 - 1
+    height = height + y1 - 1
+    # Subtract the underlying cell widths to find the end cell of the image
+    while (width >= _size_col(sheet, col_end)):
+        width -= _size_col(sheet, col_end)
+        col_end += 1
+    # Subtract the underlying cell heights to find the end cell of the image
+    while (height >= _size_row(sheet, row_end)):
+        height -= _size_row(sheet, row_end)
+        row_end += 1
+    # Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
+    # with zero height or width.
+    if ((_size_col(sheet, col_start) == 0) or (_size_col(sheet, col_end) == 0)
+            or (_size_row(sheet, row_start) == 0) or (_size_row(sheet, row_end) == 0)):
+        return
+    # Convert the pixel values to the percentage value expected by Excel
+    x1 = int(float(x1) / _size_col(sheet, col_start) * 1024)
+    y1 = int(float(y1) / _size_row(sheet, row_start) * 256)
+    # Distance to right side of object
+    x2 = int(float(width) / _size_col(sheet, col_end) * 1024)
+    # Distance to bottom of object
+    y2 = int(float(height) / _size_row(sheet, row_end) * 256)
+    return (col_start, x1, row_start, y1, col_end, x2, row_end, y2)
+
+
+class ObjBmpRecord(BiffRecord):
+    _REC_ID = 0x005D    # Record identifier
+
+    def __init__(self, row, col, sheet, im_data_bmp, x, y, scale_x, scale_y):
+        # Scale the frame of the image.
+        width = im_data_bmp.width * scale_x
+        height = im_data_bmp.height * scale_y
+
+        # Calculate the vertices of the image and write the OBJ record
+        coordinates = _position_image(sheet, row, col, x, y, width, height)
+        # print coordinates
+        col_start, x1, row_start, y1, col_end, x2, row_end, y2 = coordinates
+
+        """Store the OBJ record that precedes an IMDATA record. This could be generalise
+        to support other Excel objects.
+
+        """
+        cObj = 0x0001      # Count of objects in file (set to 1)
+        OT = 0x0008        # Object type. 8 = Picture
+        id = 0x0001        # Object ID
+        grbit = 0x0614     # Option flags
+        colL = col_start    # Col containing upper left corner of object
+        dxL = x1            # Distance from left side of cell
+        rwT = row_start     # Row containing top left corner of object
+        dyT = y1            # Distance from top of cell
+        colR = col_end      # Col containing lower right corner of object
+        dxR = x2            # Distance from right of cell
+        rwB = row_end       # Row containing bottom right corner of object
+        dyB = y2            # Distance from bottom of cell
+        cbMacro = 0x0000    # Length of FMLA structure
+        Reserved1 = 0x0000  # Reserved
+        Reserved2 = 0x0000  # Reserved
+        icvBack = 0x09      # Background colour
+        icvFore = 0x09      # Foreground colour
+        fls = 0x00          # Fill pattern
+        fAuto = 0x00        # Automatic fill
+        icv = 0x08          # Line colour
+        lns = 0xff          # Line style
+        lnw = 0x01          # Line weight
+        fAutoB = 0x00       # Automatic border
+        frs = 0x0000        # Frame style
+        cf = 0x0009         # Image format, 9 = bitmap
+        Reserved3 = 0x0000  # Reserved
+        cbPictFmla = 0x0000 # Length of FMLA structure
+        Reserved4 = 0x0000  # Reserved
+        grbit2 = 0x0001     # Option flags
+        Reserved5 = 0x0000  # Reserved
+
+        data = pack("<L", cObj)
+        data += pack("<H", OT)
+        data += pack("<H", id)
+        data += pack("<H", grbit)
+        data += pack("<H", colL)
+        data += pack("<H", dxL)
+        data += pack("<H", rwT)
+        data += pack("<H", dyT)
+        data += pack("<H", colR)
+        data += pack("<H", dxR)
+        data += pack("<H", rwB)
+        data += pack("<H", dyB)
+        data += pack("<H", cbMacro)
+        data += pack("<L", Reserved1)
+        data += pack("<H", Reserved2)
+        data += pack("<B", icvBack)
+        data += pack("<B", icvFore)
+        data += pack("<B", fls)
+        data += pack("<B", fAuto)
+        data += pack("<B", icv)
+        data += pack("<B", lns)
+        data += pack("<B", lnw)
+        data += pack("<B", fAutoB)
+        data += pack("<H", frs)
+        data += pack("<L", cf)
+        data += pack("<H", Reserved3)
+        data += pack("<H", cbPictFmla)
+        data += pack("<H", Reserved4)
+        data += pack("<H", grbit2)
+        data += pack("<L", Reserved5)
+
+        self._rec_data = data
+
+def _process_bitmap(bitmap):
+    """Convert a 24 bit bitmap into the modified internal format used by Windows.
+    This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
+    MSDN library.
+
+    """
+    # Open file and binmode the data in case the platform needs it.
+    with open(bitmap, "rb") as fh:
+        # Slurp the file into a string.
+        data = fh.read()
+    return _process_bitmap_data(data)
+
+def _process_bitmap_data(data):
+    # Check that the file is big enough to be a bitmap.
+    if len(data) <= 0x36:
+        raise Exception("bitmap doesn't contain enough data.")
+    # The first 2 bytes are used to identify the bitmap.
+    if (data[:2] != b"BM"):
+        raise Exception("bitmap doesn't appear to to be a valid bitmap image.")
+    # Remove bitmap data: ID.
+    data = data[2:]
+    # Read and remove the bitmap size. This is more reliable than reading
+    # the data size at offset 0x22.
+    #
+    size = unpack("<L", data[:4])[0]
+    size -=  0x36   # Subtract size of bitmap header.
+    size +=  0x0C   # Add size of BIFF header.
+    data = data[4:]
+    # Remove bitmap data: reserved, offset, header length.
+    data = data[12:]
+    # Read and remove the bitmap width and height. Verify the sizes.
+    width, height = unpack("<LL", data[:8])
+    data = data[8:]
+    if (width > 0xFFFF):
+        raise Exception("bitmap: largest image width supported is 65k.")
+    if (height > 0xFFFF):
+        raise Exception("bitmap: largest image height supported is 65k.")
+    # Read and remove the bitmap planes and bpp data. Verify them.
+    planes, bitcount = unpack("<HH", data[:4])
+    data = data[4:]
+    if (bitcount != 24):
+        raise Exception("bitmap isn't a 24bit true color bitmap.")
+    if (planes != 1):
+        raise Exception("bitmap: only 1 plane supported in bitmap image.")
+    # Read and remove the bitmap compression. Verify compression.
+    compression = unpack("<L", data[:4])[0]
+    data = data[4:]
+    if (compression != 0):
+        raise Exception("bitmap: compression not supported in bitmap image.")
+    # Remove bitmap data: data size, hres, vres, colours, imp. colours.
+    data = data[20:]
+    # Add the BITMAPCOREHEADER data
+    header = pack("<LHHHH", 0x000c, width, height, 0x01, 0x18)
+    data = header + data
+    return (width, height, size, data)
+
+
+class ImRawDataBmpRecord(BiffRecord):
+    _REC_ID = 0x007F
+
+    def __init__(self, data):
+        """Insert a 24bit bitmap image in a worksheet. The main record required is
+        IMDATA but it must be proceeded by a OBJ record to define its position.
+
+        """
+        BiffRecord.__init__(self)
+
+        self.width, self.height, self.size, data = _process_bitmap_data(data)
+        self._write_imdata(data)
+
+    def _write_imdata(self, data):
+        # Write the IMDATA record to store the bitmap data
+        cf = 0x09
+        env = 0x01
+        lcb = self.size
+        self._rec_data = pack("<HHL", cf, env, lcb) + data
+
+class ImDataBmpRecord(ImRawDataBmpRecord):
+    def __init__(self, filename):
+        """Insert a 24bit bitmap image in a worksheet. The main record required is
+        IMDATA but it must be proceeded by a OBJ record to define its position.
+
+        """
+        BiffRecord.__init__(self)
+
+        self.width, self.height, self.size, data = _process_bitmap(filename)
+        self._write_imdata(data)
+

+ 244 - 0
desktop/core/ext-py/xlwt-1.3.0/xlwt/Cell.py

@@ -0,0 +1,244 @@
+# -*- coding: windows-1252 -*-
+
+from struct import unpack, pack
+from . import BIFFRecords
+from .compat import xrange
+
+class StrCell(object):
+    __slots__ = ["rowx", "colx", "xf_idx", "sst_idx"]
+
+    def __init__(self, rowx, colx, xf_idx, sst_idx):
+        self.rowx = rowx
+        self.colx = colx
+        self.xf_idx = xf_idx
+        self.sst_idx = sst_idx
+
+    def get_biff_data(self):
+        # return BIFFRecords.LabelSSTRecord(self.rowx, self.colx, self.xf_idx, self.sst_idx).get()
+        return pack('<5HL', 0x00FD, 10, self.rowx, self.colx, self.xf_idx, self.sst_idx)
+
+class BlankCell(object):
+    __slots__ = ["rowx", "colx", "xf_idx"]
+
+    def __init__(self, rowx, colx, xf_idx):
+        self.rowx = rowx
+        self.colx = colx
+        self.xf_idx = xf_idx
+
+    def get_biff_data(self):
+        # return BIFFRecords.BlankRecord(self.rowx, self.colx, self.xf_idx).get()
+        return pack('<5H', 0x0201, 6, self.rowx, self.colx, self.xf_idx)
+
+class MulBlankCell(object):
+    __slots__ = ["rowx", "colx1", "colx2", "xf_idx"]
+
+    def __init__(self, rowx, colx1, colx2, xf_idx):
+        self.rowx = rowx
+        self.colx1 = colx1
+        self.colx2 = colx2
+        self.xf_idx = xf_idx
+
+    def get_biff_data(self):
+        return BIFFRecords.MulBlankRecord(self.rowx,
+            self.colx1, self.colx2, self.xf_idx).get()
+
+class NumberCell(object):
+    __slots__ = ["rowx", "colx", "xf_idx", "number"]
+
+    def __init__(self, rowx, colx, xf_idx, number):
+        self.rowx = rowx
+        self.colx = colx
+        self.xf_idx = xf_idx
+        self.number = float(number)
+
+    def get_encoded_data(self):
+        rk_encoded = 0
+        num = self.number
+
+        # The four possible kinds of RK encoding are *not* mutually exclusive.
+        # The 30-bit integer variety picks up the most.
+        # In the code below, the four varieties are checked in descending order
+        # of bangs per buck, or not at all.
+        # SJM 2007-10-01
+
+        if -0x20000000 <= num < 0x20000000: # fits in 30-bit *signed* int
+            inum = int(num)
+            if inum == num: # survives round-trip
+                # print "30-bit integer RK", inum, hex(inum)
+                rk_encoded = 2 | (inum << 2)
+                return 1, rk_encoded
+
+        temp = num * 100
+
+        if -0x20000000 <= temp < 0x20000000:
+            # That was step 1: the coded value will fit in
+            # a 30-bit signed integer.
+            itemp = int(round(temp, 0))
+            # That was step 2: "itemp" is the best candidate coded value.
+            # Now for step 3: simulate the decoding,
+            # to check for round-trip correctness.
+            if itemp / 100.0 == num:
+                # print "30-bit integer RK*100", itemp, hex(itemp)
+                rk_encoded = 3 | (itemp << 2)
+                return 1, rk_encoded
+
+        if 0: # Cost of extra pack+unpack not justified by tiny yield.
+            packed = pack('<d', num)
+            w01, w23 = unpack('<2i', packed)
+            if not w01 and not(w23 & 3):
+                # 34 lsb are 0
+                # print "float RK", w23, hex(w23)
+                return 1, w23
+
+            packed100 = pack('<d', temp)
+            w01, w23 = unpack('<2i', packed100)
+            if not w01 and not(w23 & 3):
+                # 34 lsb are 0
+                # print "float RK*100", w23, hex(w23)
+                return 1, w23 | 1
+
+        #print "Number"
+        #print
+        return 0, pack('<5Hd', 0x0203, 14, self.rowx, self.colx, self.xf_idx, num)
+
+    def get_biff_data(self):
+        isRK, value = self.get_encoded_data()
+        if isRK:
+            return pack('<5Hi', 0x27E, 10, self.rowx, self.colx, self.xf_idx, value)
+        return value # NUMBER record already packed
+
+class BooleanCell(object):
+    __slots__ = ["rowx", "colx", "xf_idx", "number"]
+
+    def __init__(self, rowx, colx, xf_idx, number):
+        self.rowx = rowx
+        self.colx = colx
+        self.xf_idx = xf_idx
+        self.number = number
+
+    def get_biff_data(self):
+        return BIFFRecords.BoolErrRecord(self.rowx,
+            self.colx, self.xf_idx, self.number, 0).get()
+
+error_code_map = {
+    0x00:  0, # Intersection of two cell ranges is empty
+    0x07:  7, # Division by zero
+    0x0F: 15, # Wrong type of operand
+    0x17: 23, # Illegal or deleted cell reference
+    0x1D: 29, # Wrong function or range name
+    0x24: 36, # Value range overflow
+    0x2A: 42, # Argument or function not available
+    '#NULL!' :  0, # Intersection of two cell ranges is empty
+    '#DIV/0!':  7, # Division by zero
+    '#VALUE!': 36, # Wrong type of operand
+    '#REF!'  : 23, # Illegal or deleted cell reference
+    '#NAME?' : 29, # Wrong function or range name
+    '#NUM!'  : 36, # Value range overflow
+    '#N/A!'  : 42, # Argument or function not available
+}
+
+class ErrorCell(object):
+    __slots__ = ["rowx", "colx", "xf_idx", "number"]
+
+    def __init__(self, rowx, colx, xf_idx, error_string_or_code):
+        self.rowx = rowx
+        self.colx = colx
+        self.xf_idx = xf_idx
+        try:
+            self.number = error_code_map[error_string_or_code]
+        except KeyError:
+            raise Exception('Illegal error value (%r)' % error_string_or_code)
+
+    def get_biff_data(self):
+        return BIFFRecords.BoolErrRecord(self.rowx,
+            self.colx, self.xf_idx, self.number, 1).get()
+
+class FormulaCell(object):
+    __slots__ = ["rowx", "colx", "xf_idx", "frmla", "calc_flags"]
+
+    def __init__(self, rowx, colx, xf_idx, frmla, calc_flags=0):
+        self.rowx = rowx
+        self.colx = colx
+        self.xf_idx = xf_idx
+        self.frmla = frmla
+        self.calc_flags = calc_flags
+
+    def get_biff_data(self):
+        return BIFFRecords.FormulaRecord(self.rowx,
+            self.colx, self.xf_idx, self.frmla.rpn(), self.calc_flags).get()
+
+# module-level function for *internal* use by the Row module
+
+def _get_cells_biff_data_mul(rowx, cell_items):
+    # Return the BIFF data for all cell records in the row.
+    # Adjacent BLANK|RK records are combined into MUL(BLANK|RK) records.
+    pieces = []
+    nitems = len(cell_items)
+    i = 0
+    while i < nitems:
+        icolx, icell = cell_items[i]
+        if isinstance(icell, NumberCell):
+            isRK, value = icell.get_encoded_data()
+            if not isRK:
+                pieces.append(value) # pre-packed NUMBER record
+                i += 1
+                continue
+            muldata = [(value, icell.xf_idx)]
+            target = NumberCell
+        elif isinstance(icell, BlankCell):
+            muldata = [icell.xf_idx]
+            target = BlankCell
+        else:
+            pieces.append(icell.get_biff_data())
+            i += 1
+            continue
+        lastcolx = icolx
+        j = i
+        packed_record = ''
+        for j in xrange(i+1, nitems):
+            jcolx, jcell = cell_items[j]
+            if jcolx != lastcolx + 1:
+                nexti = j
+                break
+            if not isinstance(jcell, target):
+                nexti = j
+                break
+            if target == NumberCell:
+                isRK, value = jcell.get_encoded_data()
+                if not isRK:
+                    packed_record = value
+                    nexti = j + 1
+                    break
+                muldata.append((value, jcell.xf_idx))
+            else:
+                muldata.append(jcell.xf_idx)
+            lastcolx = jcolx
+        else:
+            nexti = j + 1
+        if target == NumberCell:
+            if lastcolx == icolx:
+                # RK record
+                value, xf_idx = muldata[0]
+                pieces.append(pack('<5Hi', 0x027E, 10, rowx, icolx, xf_idx, value))
+            else:
+                # MULRK record
+                nc = lastcolx - icolx + 1
+                pieces.append(pack('<4H', 0x00BD, 6 * nc + 6, rowx, icolx))
+                pieces.append(b''.join(pack('<Hi', xf_idx, value) for value, xf_idx in muldata))
+                pieces.append(pack('<H', lastcolx))
+        else:
+            if lastcolx == icolx:
+                # BLANK record
+                xf_idx = muldata[0]
+                pieces.append(pack('<5H', 0x0201, 6, rowx, icolx, xf_idx))
+            else:
+                # MULBLANK record
+                nc = lastcolx - icolx + 1
+                pieces.append(pack('<4H', 0x00BE, 2 * nc + 6, rowx, icolx))
+                pieces.append(b''.join(pack('<H', xf_idx) for xf_idx in muldata))
+                pieces.append(pack('<H', lastcolx))
+        if packed_record:
+            pieces.append(packed_record)
+        i = nexti
+    return b''.join(pieces)
+

Неке датотеке нису приказане због велике количине промена