From 999c49a4f092422f8340849dbd72399233149a86 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 11 May 2011 19:01:35 -0400 Subject: [PATCH 01/22] initial compat module --- tablib/__init__.py | 16 ++++------------ tablib/compat.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 tablib/compat.py diff --git a/tablib/__init__.py b/tablib/__init__.py index dc85527..24d5fdb 100644 --- a/tablib/__init__.py +++ b/tablib/__init__.py @@ -1,16 +1,8 @@ """ Tablib. """ -import sys -if sys.version_info[0:1] > (2, 5): - from tablib.core import ( - Databook, Dataset, detect, import_set, - InvalidDatasetType, InvalidDimensions, UnsupportedFormat - ) - -else: - from tablib.core25 import ( - Databook, Dataset, detect, import_set, - InvalidDatasetType, InvalidDimensions, UnsupportedFormat - ) +from tablib.compat import ( + Databook, Dataset, detect, import_set, + InvalidDatasetType, InvalidDimensions, UnsupportedFormat +) diff --git a/tablib/compat.py b/tablib/compat.py new file mode 100644 index 0000000..f3dad91 --- /dev/null +++ b/tablib/compat.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +""" +tablib.compat +~~~~~~~~~~~~~ + +Tablib compatiblity module. + +""" + +import sys + + +from tablib.core25 import ( + Databook, Dataset, detect, import_set, + InvalidDatasetType, InvalidDimensions, UnsupportedFormat + ) + From 35f21cf73ea19a1c1a1a9fc5ed35de2b23d0a0b3 Mon Sep 17 00:00:00 2001 From: Luca Beltrame Date: Thu, 12 May 2011 16:04:46 +0200 Subject: [PATCH 02/22] Fix pickling/unpickling of Dataset instances. --- tablib/core25.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tablib/core25.py b/tablib/core25.py index c8352a6..800cae3 100644 --- a/tablib/core25.py +++ b/tablib/core25.py @@ -35,7 +35,7 @@ __docformat__ = u'restructuredtext' class Row(object): u"""Internal Row object. Mainly used for filtering.""" - __slots__ = [u'tuple', u'_row', u'tags'] + __slots__ = [ u'_row', u'tags'] def __init__(self, row=list(), tags=list()): self._row = list(row) @@ -63,7 +63,14 @@ class Row(object): del self._row[i] def __getstate__(self): - return {slot: [getattr(self, slot) for slot in self.__slots__]} + + slots = dict() + + for slot in self.__slots__: + attribute = getattr(self, slot) + slots[slot] = attribute + + return slots def __setstate__(self, state): for (k, v) in list(state.items()): setattr(self, k, v) From 2cd381389cb330f57c212eaa2ba233610cf0bbfa Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 12 May 2011 10:13:42 -0400 Subject: [PATCH 03/22] Merge pull request #8 from cswegger/tablib --- This pull request is to fix pickling / unpickling of Row within Dataset. __getstate__ resembled a dictionary comprehension (Python 2.7+) but it wasnt, and it caused wrong values to be pickled, leading to unusable objects after restoring. This patch fixes the issues. All unit tests still pass. --- HISTORY.rst | 6 ++++++ tablib/core25.py | 11 +++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index f545a4a..22437ca 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,12 @@ History ------- +0.9.7 ++++++ + +* Pickling Bugfix + + 0.9.6 (2011-05-12) ++++++++++++++++++ diff --git a/tablib/core25.py b/tablib/core25.py index c8352a6..800cae3 100644 --- a/tablib/core25.py +++ b/tablib/core25.py @@ -35,7 +35,7 @@ __docformat__ = u'restructuredtext' class Row(object): u"""Internal Row object. Mainly used for filtering.""" - __slots__ = [u'tuple', u'_row', u'tags'] + __slots__ = [ u'_row', u'tags'] def __init__(self, row=list(), tags=list()): self._row = list(row) @@ -63,7 +63,14 @@ class Row(object): del self._row[i] def __getstate__(self): - return {slot: [getattr(self, slot) for slot in self.__slots__]} + + slots = dict() + + for slot in self.__slots__: + attribute = getattr(self, slot) + slots[slot] = attribute + + return slots def __setstate__(self, state): for (k, v) in list(state.items()): setattr(self, k, v) From 87ce64d4c859efb7c3d37b1e715fa04a0c1cc631 Mon Sep 17 00:00:00 2001 From: Mark Rogers Date: Thu, 12 May 2011 15:12:46 -0500 Subject: [PATCH 04/22] Merely a proof of concept, soon to be tidied up --- tablib/core.py | 4 + tablib/formats/__init__.py | 3 +- tablib/formats/_xlsx.py | 104 ++++ tablib/packages/openpyxl/__init__.py | 53 ++ tablib/packages/openpyxl/cell.py | 384 +++++++++++++ tablib/packages/openpyxl/chart.py | 340 +++++++++++ tablib/packages/openpyxl/drawing.py | 402 +++++++++++++ tablib/packages/openpyxl/namedrange.py | 68 +++ tablib/packages/openpyxl/reader/__init__.py | 33 ++ tablib/packages/openpyxl/reader/excel.py | 111 ++++ .../openpyxl/reader/iter_worksheet.py | 343 +++++++++++ tablib/packages/openpyxl/reader/strings.py | 64 +++ tablib/packages/openpyxl/reader/style.py | 69 +++ tablib/packages/openpyxl/reader/workbook.py | 156 +++++ tablib/packages/openpyxl/reader/worksheet.py | 112 ++++ tablib/packages/openpyxl/shared/__init__.py | 33 ++ tablib/packages/openpyxl/shared/date_time.py | 154 +++++ tablib/packages/openpyxl/shared/exc.py | 59 ++ tablib/packages/openpyxl/shared/ooxml.py | 60 ++ .../openpyxl/shared/password_hasher.py | 47 ++ tablib/packages/openpyxl/shared/units.py | 67 +++ tablib/packages/openpyxl/shared/xmltools.py | 96 ++++ tablib/packages/openpyxl/style.py | 392 +++++++++++++ tablib/packages/openpyxl/tests/__init__.py | 24 + tablib/packages/openpyxl/tests/helper.py | 94 +++ tablib/packages/openpyxl/tests/test_cell.py | 200 +++++++ tablib/packages/openpyxl/tests/test_chart.py | 131 +++++ tablib/packages/openpyxl/tests/test_dump.py | 109 ++++ tablib/packages/openpyxl/tests/test_iter.py | 112 ++++ tablib/packages/openpyxl/tests/test_meta.py | 50 ++ .../openpyxl/tests/test_named_range.py | 102 ++++ .../openpyxl/tests/test_number_format.py | 142 +++++ .../openpyxl/tests/test_password_hash.py | 41 ++ tablib/packages/openpyxl/tests/test_props.py | 124 ++++ tablib/packages/openpyxl/tests/test_read.py | 134 +++++ .../packages/openpyxl/tests/test_strings.py | 68 +++ tablib/packages/openpyxl/tests/test_style.py | 181 ++++++ tablib/packages/openpyxl/tests/test_theme.py | 37 ++ .../packages/openpyxl/tests/test_workbook.py | 148 +++++ .../packages/openpyxl/tests/test_worksheet.py | 258 +++++++++ tablib/packages/openpyxl/tests/test_write.py | 203 +++++++ tablib/packages/openpyxl/workbook.py | 186 ++++++ tablib/packages/openpyxl/worksheet.py | 534 ++++++++++++++++++ tablib/packages/openpyxl/writer/__init__.py | 34 ++ tablib/packages/openpyxl/writer/charts.py | 261 +++++++++ tablib/packages/openpyxl/writer/drawings.py | 192 +++++++ .../openpyxl/writer/dump_worksheet.py | 256 +++++++++ tablib/packages/openpyxl/writer/excel.py | 156 +++++ tablib/packages/openpyxl/writer/strings.py | 86 +++ tablib/packages/openpyxl/writer/styles.py | 256 +++++++++ tablib/packages/openpyxl/writer/theme.py | 202 +++++++ tablib/packages/openpyxl/writer/workbook.py | 204 +++++++ tablib/packages/openpyxl/writer/worksheet.py | 209 +++++++ 53 files changed, 7887 insertions(+), 1 deletion(-) create mode 100644 tablib/formats/_xlsx.py create mode 100644 tablib/packages/openpyxl/__init__.py create mode 100644 tablib/packages/openpyxl/cell.py create mode 100644 tablib/packages/openpyxl/chart.py create mode 100644 tablib/packages/openpyxl/drawing.py create mode 100644 tablib/packages/openpyxl/namedrange.py create mode 100644 tablib/packages/openpyxl/reader/__init__.py create mode 100644 tablib/packages/openpyxl/reader/excel.py create mode 100644 tablib/packages/openpyxl/reader/iter_worksheet.py create mode 100644 tablib/packages/openpyxl/reader/strings.py create mode 100644 tablib/packages/openpyxl/reader/style.py create mode 100644 tablib/packages/openpyxl/reader/workbook.py create mode 100644 tablib/packages/openpyxl/reader/worksheet.py create mode 100644 tablib/packages/openpyxl/shared/__init__.py create mode 100644 tablib/packages/openpyxl/shared/date_time.py create mode 100644 tablib/packages/openpyxl/shared/exc.py create mode 100644 tablib/packages/openpyxl/shared/ooxml.py create mode 100644 tablib/packages/openpyxl/shared/password_hasher.py create mode 100644 tablib/packages/openpyxl/shared/units.py create mode 100644 tablib/packages/openpyxl/shared/xmltools.py create mode 100644 tablib/packages/openpyxl/style.py create mode 100644 tablib/packages/openpyxl/tests/__init__.py create mode 100644 tablib/packages/openpyxl/tests/helper.py create mode 100644 tablib/packages/openpyxl/tests/test_cell.py create mode 100644 tablib/packages/openpyxl/tests/test_chart.py create mode 100644 tablib/packages/openpyxl/tests/test_dump.py create mode 100644 tablib/packages/openpyxl/tests/test_iter.py create mode 100644 tablib/packages/openpyxl/tests/test_meta.py create mode 100644 tablib/packages/openpyxl/tests/test_named_range.py create mode 100644 tablib/packages/openpyxl/tests/test_number_format.py create mode 100644 tablib/packages/openpyxl/tests/test_password_hash.py create mode 100644 tablib/packages/openpyxl/tests/test_props.py create mode 100644 tablib/packages/openpyxl/tests/test_read.py create mode 100644 tablib/packages/openpyxl/tests/test_strings.py create mode 100644 tablib/packages/openpyxl/tests/test_style.py create mode 100644 tablib/packages/openpyxl/tests/test_theme.py create mode 100644 tablib/packages/openpyxl/tests/test_workbook.py create mode 100644 tablib/packages/openpyxl/tests/test_worksheet.py create mode 100644 tablib/packages/openpyxl/tests/test_write.py create mode 100644 tablib/packages/openpyxl/workbook.py create mode 100644 tablib/packages/openpyxl/worksheet.py create mode 100644 tablib/packages/openpyxl/writer/__init__.py create mode 100644 tablib/packages/openpyxl/writer/charts.py create mode 100644 tablib/packages/openpyxl/writer/drawings.py create mode 100644 tablib/packages/openpyxl/writer/dump_worksheet.py create mode 100644 tablib/packages/openpyxl/writer/excel.py create mode 100644 tablib/packages/openpyxl/writer/strings.py create mode 100644 tablib/packages/openpyxl/writer/styles.py create mode 100644 tablib/packages/openpyxl/writer/theme.py create mode 100644 tablib/packages/openpyxl/writer/workbook.py create mode 100644 tablib/packages/openpyxl/writer/worksheet.py diff --git a/tablib/core.py b/tablib/core.py index 896dfcc..71f4f71 100644 --- a/tablib/core.py +++ b/tablib/core.py @@ -390,6 +390,10 @@ class Dataset(object): """ pass + @property + def xlsx(): + pass + @property def csv(): diff --git a/tablib/formats/__init__.py b/tablib/formats/__init__.py index 305026d..306ca30 100644 --- a/tablib/formats/__init__.py +++ b/tablib/formats/__init__.py @@ -9,5 +9,6 @@ from . import _xls as xls from . import _yaml as yaml from . import _tsv as tsv from . import _html as html +from . import _xlsx as xlsx -available = (json, xls, yaml, csv, tsv, html) +available = (json, xls, yaml, csv, tsv, html, xlsx) diff --git a/tablib/formats/_xlsx.py b/tablib/formats/_xlsx.py new file mode 100644 index 0000000..92e33f9 --- /dev/null +++ b/tablib/formats/_xlsx.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +""" Tablib - XLSX Support. +""" + +import sys + + +if sys.version_info[0] > 2: + from io import BytesIO + import tablib.packages.xlwt3 as xlwt + +else: + from cStringIO import StringIO as BytesIO + import tablib.packages.xlwt as xlwt + +from tablib.packages.openpyxl.workbook import Workbook +from tablib.packages.openpyxl.writer.excel import ExcelWriter + +from tablib.packages.openpyxl.cell import get_column_letter + + + +title = 'xlsx' +extentions = ('xlsx',) + +# special styles +#wrap = xlwt.easyxf("alignment: wrap on") +#bold = xlwt.easyxf("font: bold on") + + +def export_set(dataset): + """Returns XLS representation of Dataset.""" + + wb = Workbook() + ws = wb.worksheets[0] + ws.title = dataset.title if dataset.title else 'Tablib Dataset' + + dset_sheet(dataset, ws) + + stream = BytesIO() + wb.save(stream) + return stream.getvalue() + + +def export_book(databook): + """Returns XLS representation of DataBook.""" + + wb = Workbook() + ew = ExcelWriter(workbook = wb) + for i, dset in enumerate(databook._datasets): + ws = wb.add_sheet() + ws.title = dset.title if dset.title else 'Sheet%s' % (i) + + dset_sheet(dset, ws) + + + stream = BytesIO() + ew.save(filename='test.xlsx') + return stream.getvalue() + + +def dset_sheet(dataset, ws): + """Completes given worksheet from given Dataset.""" + _package = dataset._package(dicts=False) + + for i, sep in enumerate(dataset._separators): + _offset = i + _package.insert((sep[0] + _offset), (sep[1],)) + + for i, row in enumerate(_package): + row_number = i + 1 + for j, col in enumerate(row): + col_idx = get_column_letter(j + 1) + + # bold headers + if (row_number == 1) and dataset.headers: + ws.cell('%s%s'%(col_idx, row_number)).value = '%s' % col + #ws.write(i, j, col, bold) + + # frozen header row + #ws.panes_frozen = True + #ws.horz_split_pos = 1 + + + # bold separators + elif len(row) < dataset.width: + ws.cell('%s%s'%(col_idx, row_number)).value = '%s' % col + #ws.write(i, j, col, bold) + + # wrap the rest + else: + try: + if '\n' in col: + ws.cell('%s%s'%(col_idx, row_number)).value = '%s' % col + #ws.write(i, j, col, wrap) + else: + ws.cell('%s%s'%(col_idx, row_number)).value = '%s' % col + #ws.write(i, j, col) + except TypeError: + ws.cell('%s%s'%(col_idx, row_number)).value = '%s' % col + #ws.write(i, j, col) + + diff --git a/tablib/packages/openpyxl/__init__.py b/tablib/packages/openpyxl/__init__.py new file mode 100644 index 0000000..c3dcc5e --- /dev/null +++ b/tablib/packages/openpyxl/__init__.py @@ -0,0 +1,53 @@ +# file openpyxl/__init__.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Imports for the openpyxl package.""" + +# package imports +from openpyxl import cell +from openpyxl import namedrange +from openpyxl import style +from openpyxl import workbook +from openpyxl import worksheet +from openpyxl import reader +from openpyxl import shared +from openpyxl import writer + +# constants + +__major__ = 1 # for major interface/format changes +__minor__ = 5 # for minor interface/format changes +__release__ = 2 # for tweaks, bug-fixes, or development + +__version__ = '%d.%d.%d' % (__major__, __minor__, __release__) + +__author__ = 'Eric Gazoni' +__license__ = 'MIT/Expat' +__author_email__ = 'eric.gazoni@gmail.com' +__maintainer_email__ = 'openpyxl-users@googlegroups.com' +__url__ = 'http://bitbucket.org/ericgazoni/openpyxl/wiki/Home' +__downloadUrl__ = "http://bitbucket.org/ericgazoni/openpyxl/downloads" + +__all__ = ('reader', 'shared', 'writer',) diff --git a/tablib/packages/openpyxl/cell.py b/tablib/packages/openpyxl/cell.py new file mode 100644 index 0000000..324fc96 --- /dev/null +++ b/tablib/packages/openpyxl/cell.py @@ -0,0 +1,384 @@ +# file openpyxl/cell.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Manage individual cells in a spreadsheet. + +The Cell class is required to know its value and type, display options, +and any other features of an Excel cell. Utilities for referencing +cells using Excel's 'A1' column/row nomenclature are also provided. + +""" + +__docformat__ = "restructuredtext en" + +# Python stdlib imports +import datetime +import re + +# package imports +from openpyxl.shared.date_time import SharedDate +from openpyxl.shared.exc import CellCoordinatesException, \ + ColumnStringIndexException, DataTypeException +from openpyxl.style import NumberFormat + +# constants +COORD_RE = re.compile('^[$]?([A-Z]+)[$]?(\d+)$') + +ABSOLUTE_RE = re.compile('^[$]?([A-Z]+)[$]?(\d+)(:[$]?([A-Z]+)[$]?(\d+))?$') + +def coordinate_from_string(coord_string): + """Convert a coordinate string like 'B12' to a tuple ('B', 12)""" + match = COORD_RE.match(coord_string.upper()) + if not match: + msg = 'Invalid cell coordinates (%s)' % coord_string + raise CellCoordinatesException(msg) + column, row = match.groups() + return (column, int(row)) + + +def absolute_coordinate(coord_string): + """Convert a coordinate to an absolute coordinate string (B12 -> $B$12)""" + parts = ABSOLUTE_RE.match(coord_string).groups() + + if all(parts[-2:]): + return '$%s$%s:$%s$%s' % (parts[0], parts[1], parts[3], parts[4]) + else: + return '$%s$%s' % (parts[0], parts[1]) + + +def column_index_from_string(column, fast = False): + """Convert a column letter into a column number (e.g. B -> 2) + + Excel only supports 1-3 letter column names from A -> ZZZ, so we + restrict our column names to 1-3 characters, each in the range A-Z. + + .. note:: + + Fast mode is faster but does not check that all letters are capitals between A and Z + + """ + column = column.upper() + + clen = len(column) + + if not fast and not all('A' <= char <= 'Z' for char in column): + msg = 'Column string must contain only characters A-Z: got %s' % column + raise ColumnStringIndexException(msg) + + if clen == 1: + return ord(column[0]) - 64 + elif clen == 2: + return ((1 + (ord(column[0]) - 65)) * 26) + (ord(column[1]) - 64) + elif clen == 3: + return ((1 + (ord(column[0]) - 65)) * 676) + ((1 + (ord(column[1]) - 65)) * 26) + (ord(column[2]) - 64) + elif clen > 3: + raise ColumnStringIndexException('Column string index can not be longer than 3 characters') + else: + raise ColumnStringIndexException('Column string index can not be empty') + + +def get_column_letter(col_idx): + """Convert a column number into a column letter (3 -> 'C') + + Right shift the column col_idx by 26 to find column letters in reverse + order. These numbers are 1-based, and can be converted to ASCII + ordinals by adding 64. + + """ + # these indicies corrospond to A -> ZZZ and include all allowed + # columns + if not 1 <= col_idx <= 18278: + msg = 'Column index out of bounds: %s' % col_idx + raise ColumnStringIndexException(msg) + ordinals = [] + temp = col_idx + while temp: + quotient, remainder = divmod(temp, 26) + # check for exact division and borrow if needed + if remainder == 0: + quotient -= 1 + remainder = 26 + ordinals.append(remainder + 64) + temp = quotient + ordinals.reverse() + return ''.join([chr(ordinal) for ordinal in ordinals]) + + +class Cell(object): + """Describes cell associated properties. + + Properties of interest include style, type, value, and address. + + """ + __slots__ = ('column', + 'row', + '_value', + '_data_type', + 'parent', + 'xf_index', + '_hyperlink_rel') + + ERROR_CODES = {'#NULL!': 0, + '#DIV/0!': 1, + '#VALUE!': 2, + '#REF!': 3, + '#NAME?': 4, + '#NUM!': 5, + '#N/A': 6} + + TYPE_STRING = 's' + TYPE_FORMULA = 'f' + TYPE_NUMERIC = 'n' + TYPE_BOOL = 'b' + TYPE_NULL = 's' + TYPE_INLINE = 'inlineStr' + TYPE_ERROR = 'e' + + VALID_TYPES = [TYPE_STRING, TYPE_FORMULA, TYPE_NUMERIC, TYPE_BOOL, + TYPE_NULL, TYPE_INLINE, TYPE_ERROR] + + RE_PATTERNS = { + 'percentage': re.compile('^\-?[0-9]*\.?[0-9]*\s?\%$'), + 'time': re.compile('^(\d|[0-1]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$'), + 'numeric': re.compile('^\-?([0-9]+\\.?[0-9]*|[0-9]*\\.?[0-9]+)((E|e)\-?[0-9]+)?$'), } + + def __init__(self, worksheet, column, row, value = None): + self.column = column.upper() + self.row = row + # _value is the stored value, while value is the displayed value + self._value = None + self._hyperlink_rel = None + self._data_type = self.TYPE_NULL + if value: + self.value = value + self.parent = worksheet + self.xf_index = 0 + + def __repr__(self): + return u"" % (self.parent.title, self.get_coordinate()) + + def check_string(self, value): + """Check string coding, length, and line break character""" + # convert to unicode string + value = unicode(value) + # string must never be longer than 32,767 characters + # truncate if necessary + value = value[:32767] + # we require that newline is represented as "\n" in core, + # not as "\r\n" or "\r" + value = value.replace('\r\n', '\n') + return value + + def check_numeric(self, value): + """Cast value to int or float if necessary""" + if not isinstance(value, (int, float)): + try: + value = int(value) + except ValueError: + value = float(value) + return value + + def set_value_explicit(self, value = None, data_type = TYPE_STRING): + """Coerce values according to their explicit type""" + type_coercion_map = { + self.TYPE_INLINE: self.check_string, + self.TYPE_STRING: self.check_string, + self.TYPE_FORMULA: unicode, + self.TYPE_NUMERIC: self.check_numeric, + self.TYPE_BOOL: bool, } + try: + self._value = type_coercion_map[data_type](value) + except KeyError: + if data_type not in self.VALID_TYPES: + msg = 'Invalid data type: %s' % data_type + raise DataTypeException(msg) + self._data_type = data_type + + def data_type_for_value(self, value): + """Given a value, infer the correct data type""" + if value is None: + data_type = self.TYPE_NULL + elif value is True or value is False: + data_type = self.TYPE_BOOL + elif isinstance(value, (int, float)): + data_type = self.TYPE_NUMERIC + elif not value: + data_type = self.TYPE_STRING + elif isinstance(value, (datetime.datetime, datetime.date)): + data_type = self.TYPE_NUMERIC + elif isinstance(value, basestring) and value[0] == '=': + data_type = self.TYPE_FORMULA + elif self.RE_PATTERNS['numeric'].match(value): + data_type = self.TYPE_NUMERIC + elif value.strip() in self.ERROR_CODES: + data_type = self.TYPE_ERROR + else: + data_type = self.TYPE_STRING + return data_type + + def bind_value(self, value): + """Given a value, infer type and display options.""" + self._data_type = self.data_type_for_value(value) + if value is None: + self.set_value_explicit('', self.TYPE_NULL) + return True + elif self._data_type == self.TYPE_STRING: + # percentage detection + percentage_search = self.RE_PATTERNS['percentage'].match(value) + if percentage_search and value.strip() != '%': + value = float(value.replace('%', '')) / 100.0 + self.set_value_explicit(value, self.TYPE_NUMERIC) + self._set_number_format(NumberFormat.FORMAT_PERCENTAGE) + return True + # time detection + time_search = self.RE_PATTERNS['time'].match(value) + if time_search: + sep_count = value.count(':') #pylint: disable-msg=E1103 + if sep_count == 1: + hours, minutes = [int(bit) for bit in value.split(':')] #pylint: disable-msg=E1103 + seconds = 0 + elif sep_count == 2: + hours, minutes, seconds = \ + [int(bit) for bit in value.split(':')] #pylint: disable-msg=E1103 + days = (hours / 24.0) + (minutes / 1440.0) + \ + (seconds / 86400.0) + self.set_value_explicit(days, self.TYPE_NUMERIC) + self._set_number_format(NumberFormat.FORMAT_DATE_TIME3) + return True + if self._data_type == self.TYPE_NUMERIC: + # date detection + # if the value is a date, but not a date time, make it a + # datetime, and set the time part to 0 + if isinstance(value, datetime.date) and not \ + isinstance(value, datetime.datetime): + value = datetime.datetime.combine(value, datetime.time()) + if isinstance(value, datetime.datetime): + value = SharedDate().datetime_to_julian(date = value) + self.set_value_explicit(value, self.TYPE_NUMERIC) + self._set_number_format(NumberFormat.FORMAT_DATE_YYYYMMDD2) + return True + self.set_value_explicit(value, self._data_type) + + def _get_value(self): + """Return the value, formatted as a date if needed""" + value = self._value + if self.is_date(): + value = SharedDate().from_julian(value) + return value + + def _set_value(self, value): + """Set the value and infer type and display options.""" + self.bind_value(value) + + value = property(_get_value, _set_value, + doc = 'Get or set the value held in the cell.\n\n' + ':rtype: depends on the value (string, float, int or ' + ':class:`datetime.datetime`)') + + def _set_hyperlink(self, val): + """Set value and display for hyperlinks in a cell""" + if self._hyperlink_rel is None: + self._hyperlink_rel = self.parent.create_relationship("hyperlink") + self._hyperlink_rel.target = val + self._hyperlink_rel.target_mode = "External" + if self._value is None: + self.value = val + + def _get_hyperlink(self): + """Return the hyperlink target or an empty string""" + return self._hyperlink_rel is not None and \ + self._hyperlink_rel.target or '' + + hyperlink = property(_get_hyperlink, _set_hyperlink, + doc = 'Get or set the hyperlink held in the cell. ' + 'Automatically sets the `value` of the cell with link text, ' + 'but you can modify it afterwards by setting the ' + '`value` property, and the hyperlink will remain.\n\n' + ':rtype: string') + + @property + def hyperlink_rel_id(self): + """Return the id pointed to by the hyperlink, or None""" + return self._hyperlink_rel is not None and \ + self._hyperlink_rel.id or None + + def _set_number_format(self, format_code): + """Set a new formatting code for numeric values""" + self.style.number_format.format_code = format_code + + @property + def has_style(self): + """Check if the parent worksheet has a style for this cell""" + return self.get_coordinate() in self.parent._styles #pylint: disable-msg=W0212 + + @property + def style(self): + """Returns the :class:`openpyxl.style.Style` object for this cell""" + return self.parent.get_style(self.get_coordinate()) + + @property + def data_type(self): + """Return the data type represented by this cell""" + return self._data_type + + def get_coordinate(self): + """Return the coordinate string for this cell (e.g. 'B12') + + :rtype: string + """ + return '%s%s' % (self.column, self.row) + + @property + def address(self): + """Return the coordinate string for this cell (e.g. 'B12') + + :rtype: string + """ + return self.get_coordinate() + + def offset(self, row = 0, column = 0): + """Returns a cell location relative to this cell. + + :param row: number of rows to offset + :type row: int + + :param column: number of columns to offset + :type column: int + + :rtype: :class:`openpyxl.cell.Cell` + """ + offset_column = get_column_letter(column_index_from_string( + column = self.column) + column) + offset_row = self.row + row + return self.parent.cell('%s%s' % (offset_column, offset_row)) + + def is_date(self): + """Returns whether the value is *probably* a date or not + + :rtype: bool + """ + return (self.has_style + and self.style.number_format.is_date_format() + and isinstance(self._value, (int, float))) diff --git a/tablib/packages/openpyxl/chart.py b/tablib/packages/openpyxl/chart.py new file mode 100644 index 0000000..c23e78f --- /dev/null +++ b/tablib/packages/openpyxl/chart.py @@ -0,0 +1,340 @@ +''' +Copyright (c) 2010 openpyxl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license: http://www.opensource.org/licenses/mit-license.php +@author: Eric Gazoni +''' + +import math + +from openpyxl.style import NumberFormat +from openpyxl.drawing import Drawing, Shape +from openpyxl.shared.units import pixels_to_EMU, short_color +from openpyxl.cell import get_column_letter + +class Axis(object): + + POSITION_BOTTOM = 'b' + POSITION_LEFT = 'l' + + ORIENTATION_MIN_MAX = "minMax" + + def __init__(self): + + self.orientation = self.ORIENTATION_MIN_MAX + self.number_format = NumberFormat() + for attr in ('position','tick_label_position','crosses', + 'auto','label_align','label_offset','cross_between'): + setattr(self, attr, None) + self.min = 0 + self.max = None + self.unit = None + + @classmethod + def default_category(cls): + """ default values for category axes """ + + ax = Axis() + ax.id = 60871424 + ax.cross = 60873344 + ax.position = Axis.POSITION_BOTTOM + ax.tick_label_position = 'nextTo' + ax.crosses = "autoZero" + ax.auto = True + ax.label_align = 'ctr' + ax.label_offset = 100 + return ax + + @classmethod + def default_value(cls): + """ default values for value axes """ + + ax = Axis() + ax.id = 60873344 + ax.cross = 60871424 + ax.position = Axis.POSITION_LEFT + ax.major_gridlines = None + ax.tick_label_position = 'nextTo' + ax.crosses = 'autoZero' + ax.auto = False + ax.cross_between = 'between' + return ax + +class Reference(object): + """ a simple wrapper around a serie of reference data """ + + def __init__(self, sheet, pos1, pos2=None): + + self.sheet = sheet + self.pos1 = pos1 + self.pos2 = pos2 + + def get_type(self): + + if isinstance(self.cache[0], basestring): + return 'str' + else: + return 'num' + + def _get_ref(self): + """ format excel reference notation """ + + if self.pos2: + return '%s!$%s$%s:$%s$%s' % (self.sheet.title, + get_column_letter(self.pos1[1]+1), self.pos1[0]+1, + get_column_letter(self.pos2[1]+1), self.pos2[0]+1) + else: + return '%s!$%s$%s' % (self.sheet.title, + get_column_letter(self.pos1[1]+1), self.pos1[0]+1) + + + def _get_cache(self): + """ read data in sheet - to be used at writing time """ + + cache = [] + if self.pos2: + for row in range(self.pos1[0], self.pos2[0]+1): + for col in range(self.pos1[1], self.pos2[1]+1): + cache.append(self.sheet.cell(row=row, column=col).value) + else: + cell = self.sheet.cell(row=self.pos1[0], column=self.pos1[1]) + cache.append(cell.value) + return cache + + +class Serie(object): + """ a serie of data and possibly associated labels """ + + MARKER_NONE = 'none' + + def __init__(self, values, labels=None, legend=None, color=None, xvalues=None): + + self.marker = Serie.MARKER_NONE + self.values = values + self.xvalues = xvalues + self.labels = labels + self.legend = legend + self.error_bar = None + self._color = color + + def _get_color(self): + return self._color + + def _set_color(self, color): + self._color = short_color(color) + + color = property(_get_color, _set_color) + + def get_min_max(self): + + if self.error_bar: + err_cache = self.error_bar.values._get_cache() + vals = [v + err_cache[i] \ + for i,v in enumerate(self.values._get_cache())] + else: + vals = self.values._get_cache() + return min(vals), max(vals) + + def __len__(self): + + return len(self.values.cache) + +class Legend(object): + + def __init__(self): + + self.position = 'r' + self.layout = None + +class ErrorBar(object): + + PLUS = 1 + MINUS = 2 + PLUS_MINUS = 3 + + def __init__(self, _type, values): + + self.type = _type + self.values = values + +class Chart(object): + """ raw chart class """ + + GROUPING_CLUSTERED = 'clustered' + GROUPING_STANDARD = 'standard' + + BAR_CHART = 1 + LINE_CHART = 2 + SCATTER_CHART = 3 + + def __init__(self, _type, grouping): + + self._series = [] + + # public api + self.type = _type + self.grouping = grouping + self.x_axis = Axis.default_category() + self.y_axis = Axis.default_value() + self.legend = Legend() + self.lang = 'fr-FR' + self.title = '' + self.print_margins = dict(b=.75, l=.7, r=.7, t=.75, header=0.3, footer=.3) + + # the containing drawing + self.drawing = Drawing() + + # the offset for the plot part in percentage of the drawing size + self.width = .6 + self.height = .6 + self.margin_top = self._get_max_margin_top() + self.margin_left = 0 + + # the user defined shapes + self._shapes = [] + + def add_serie(self, serie): + + serie.id = len(self._series) + self._series.append(serie) + self._compute_min_max() + if not None in [s.xvalues for s in self._series]: + self._compute_xmin_xmax() + + def add_shape(self, shape): + + shape._chart = self + self._shapes.append(shape) + + def get_x_units(self): + """ calculate one unit for x axis in EMU """ + + return max([len(s.values._get_cache()) for s in self._series]) + + def get_y_units(self): + """ calculate one unit for y axis in EMU """ + + dh = pixels_to_EMU(self.drawing.height) + return (dh * self.height) / self.y_axis.max + + def get_y_chars(self): + """ estimate nb of chars for y axis """ + + _max = max([max(s.values._get_cache()) for s in self._series]) + return len(str(int(_max))) + + def _compute_min_max(self): + """ compute y axis limits and units """ + + maxi = max([max(s.values._get_cache()) for s in self._series]) + + mul = None + if maxi < 1: + s = str(maxi).split('.')[1] + mul = 10 + for x in s: + if x == '0': + mul *= 10 + else: + break + maxi = maxi * mul + + maxi = math.ceil(maxi * 1.1) + sz = len(str(int(maxi))) - 1 + unit = math.ceil(math.ceil(maxi / pow(10, sz)) * pow(10, sz-1)) + maxi = math.ceil(maxi/unit) * unit + + if mul is not None: + maxi = maxi/mul + unit = unit/mul + + if maxi / unit > 9: + # no more that 10 ticks + unit *= 2 + + self.y_axis.max = maxi + self.y_axis.unit = unit + + def _compute_xmin_xmax(self): + """ compute x axis limits and units """ + + maxi = max([max(s.xvalues._get_cache()) for s in self._series]) + + mul = None + if maxi < 1: + s = str(maxi).split('.')[1] + mul = 10 + for x in s: + if x == '0': + mul *= 10 + else: + break + maxi = maxi * mul + + maxi = math.ceil(maxi * 1.1) + sz = len(str(int(maxi))) - 1 + unit = math.ceil(math.ceil(maxi / pow(10, sz)) * pow(10, sz-1)) + maxi = math.ceil(maxi/unit) * unit + + if mul is not None: + maxi = maxi/mul + unit = unit/mul + + if maxi / unit > 9: + # no more that 10 ticks + unit *= 2 + + self.x_axis.max = maxi + self.x_axis.unit = unit + + def _get_max_margin_top(self): + + mb = Shape.FONT_HEIGHT + Shape.MARGIN_BOTTOM + plot_height = self.drawing.height * self.height + return float(self.drawing.height - plot_height - mb)/self.drawing.height + + def _get_min_margin_left(self): + + ml = (self.get_y_chars() * Shape.FONT_WIDTH) + Shape.MARGIN_LEFT + return float(ml)/self.drawing.width + + def _get_margin_top(self): + """ get margin in percent """ + + return min(self.margin_top, self._get_max_margin_top()) + + def _get_margin_left(self): + + return max(self._get_min_margin_left(), self.margin_left) + +class BarChart(Chart): + def __init__(self): + super(BarChart, self).__init__(Chart.BAR_CHART, Chart.GROUPING_CLUSTERED) + +class LineChart(Chart): + def __init__(self): + super(LineChart, self).__init__(Chart.LINE_CHART, Chart.GROUPING_STANDARD) + +class ScatterChart(Chart): + def __init__(self): + super(ScatterChart, self).__init__(Chart.SCATTER_CHART, Chart.GROUPING_STANDARD) + + diff --git a/tablib/packages/openpyxl/drawing.py b/tablib/packages/openpyxl/drawing.py new file mode 100644 index 0000000..c9cf084 --- /dev/null +++ b/tablib/packages/openpyxl/drawing.py @@ -0,0 +1,402 @@ +''' +Copyright (c) 2010 openpyxl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license: http://www.opensource.org/licenses/mit-license.php +@author: Eric Gazoni +''' + +import math +from openpyxl.style import Color +from openpyxl.shared.units import pixels_to_EMU, EMU_to_pixels, short_color + +class Shadow(object): + + SHADOW_BOTTOM = 'b' + SHADOW_BOTTOM_LEFT = 'bl' + SHADOW_BOTTOM_RIGHT = 'br' + SHADOW_CENTER = 'ctr' + SHADOW_LEFT = 'l' + SHADOW_TOP = 't' + SHADOW_TOP_LEFT = 'tl' + SHADOW_TOP_RIGHT = 'tr' + + def __init__(self): + self.visible = False + self.blurRadius = 6 + self.distance = 2 + self.direction = 0 + self.alignment = self.SHADOW_BOTTOM_RIGHT + self.color = Color(Color.BLACK) + self.alpha = 50 + +class Drawing(object): + """ a drawing object - eg container for shapes or charts + we assume user specifies dimensions in pixels; units are + converted to EMU in the drawing part + """ + + count = 0 + + def __init__(self): + + self.name = '' + self.description = '' + self.coordinates = ((1,2), (16,8)) + self.left = 0 + self.top = 0 + self._width = EMU_to_pixels(200000) + self._height = EMU_to_pixels(1828800) + self.resize_proportional = False + self.rotation = 0 +# self.shadow = Shadow() + + def _set_width(self, w): + + if self.resize_proportional and w: + ratio = self._height / self._width + self._height = round(ratio * w) + self._width = w + + def _get_width(self): + + return self._width + + width = property(_get_width, _set_width) + + def _set_height(self, h): + + if self.resize_proportional and h: + ratio = self._width / self._height + self._width = round(ratio * h) + self._height = h + + def _get_height(self): + + return self._height + + height = property(_get_height, _set_height) + + def set_dimension(self, w=0, h=0): + + xratio = w / self._width + yratio = h / self._height + + if self.resize_proportional and w and h: + if (xratio * self._height) < h: + self._height = math.ceil(xratio * self._height) + self._width = width + else: + self._width = math.ceil(yratio * self._width) + self._height = height + + def get_emu_dimensions(self): + """ return (x, y, w, h) in EMU """ + + return (pixels_to_EMU(self.left), pixels_to_EMU(self.top), + pixels_to_EMU(self._width), pixels_to_EMU(self._height)) + + +class Shape(object): + """ a drawing inside a chart + coordiantes are specified by the user in the axis units + """ + + MARGIN_LEFT = 6 + 13 + 1 + MARGIN_BOTTOM = 17 + 11 + + FONT_WIDTH = 7 + FONT_HEIGHT = 8 + + ROUND_RECT = 'roundRect' + RECT = 'rect' + + # other shapes to define : + ''' + "line" + "lineInv" + "triangle" + "rtTriangle" + "diamond" + "parallelogram" + "trapezoid" + "nonIsoscelesTrapezoid" + "pentagon" + "hexagon" + "heptagon" + "octagon" + "decagon" + "dodecagon" + "star4" + "star5" + "star6" + "star7" + "star8" + "star10" + "star12" + "star16" + "star24" + "star32" + "roundRect" + "round1Rect" + "round2SameRect" + "round2DiagRect" + "snipRoundRect" + "snip1Rect" + "snip2SameRect" + "snip2DiagRect" + "plaque" + "ellipse" + "teardrop" + "homePlate" + "chevron" + "pieWedge" + "pie" + "blockArc" + "donut" + "noSmoking" + "rightArrow" + "leftArrow" + "upArrow" + "downArrow" + "stripedRightArrow" + "notchedRightArrow" + "bentUpArrow" + "leftRightArrow" + "upDownArrow" + "leftUpArrow" + "leftRightUpArrow" + "quadArrow" + "leftArrowCallout" + "rightArrowCallout" + "upArrowCallout" + "downArrowCallout" + "leftRightArrowCallout" + "upDownArrowCallout" + "quadArrowCallout" + "bentArrow" + "uturnArrow" + "circularArrow" + "leftCircularArrow" + "leftRightCircularArrow" + "curvedRightArrow" + "curvedLeftArrow" + "curvedUpArrow" + "curvedDownArrow" + "swooshArrow" + "cube" + "can" + "lightningBolt" + "heart" + "sun" + "moon" + "smileyFace" + "irregularSeal1" + "irregularSeal2" + "foldedCorner" + "bevel" + "frame" + "halfFrame" + "corner" + "diagStripe" + "chord" + "arc" + "leftBracket" + "rightBracket" + "leftBrace" + "rightBrace" + "bracketPair" + "bracePair" + "straightConnector1" + "bentConnector2" + "bentConnector3" + "bentConnector4" + "bentConnector5" + "curvedConnector2" + "curvedConnector3" + "curvedConnector4" + "curvedConnector5" + "callout1" + "callout2" + "callout3" + "accentCallout1" + "accentCallout2" + "accentCallout3" + "borderCallout1" + "borderCallout2" + "borderCallout3" + "accentBorderCallout1" + "accentBorderCallout2" + "accentBorderCallout3" + "wedgeRectCallout" + "wedgeRoundRectCallout" + "wedgeEllipseCallout" + "cloudCallout" + "cloud" + "ribbon" + "ribbon2" + "ellipseRibbon" + "ellipseRibbon2" + "leftRightRibbon" + "verticalScroll" + "horizontalScroll" + "wave" + "doubleWave" + "plus" + "flowChartProcess" + "flowChartDecision" + "flowChartInputOutput" + "flowChartPredefinedProcess" + "flowChartInternalStorage" + "flowChartDocument" + "flowChartMultidocument" + "flowChartTerminator" + "flowChartPreparation" + "flowChartManualInput" + "flowChartManualOperation" + "flowChartConnector" + "flowChartPunchedCard" + "flowChartPunchedTape" + "flowChartSummingJunction" + "flowChartOr" + "flowChartCollate" + "flowChartSort" + "flowChartExtract" + "flowChartMerge" + "flowChartOfflineStorage" + "flowChartOnlineStorage" + "flowChartMagneticTape" + "flowChartMagneticDisk" + "flowChartMagneticDrum" + "flowChartDisplay" + "flowChartDelay" + "flowChartAlternateProcess" + "flowChartOffpageConnector" + "actionButtonBlank" + "actionButtonHome" + "actionButtonHelp" + "actionButtonInformation" + "actionButtonForwardNext" + "actionButtonBackPrevious" + "actionButtonEnd" + "actionButtonBeginning" + "actionButtonReturn" + "actionButtonDocument" + "actionButtonSound" + "actionButtonMovie" + "gear6" + "gear9" + "funnel" + "mathPlus" + "mathMinus" + "mathMultiply" + "mathDivide" + "mathEqual" + "mathNotEqual" + "cornerTabs" + "squareTabs" + "plaqueTabs" + "chartX" + "chartStar" + "chartPlus" + ''' + + def __init__(self, coordinates=((0,0), (1,1)), text=None, scheme="accent1"): + + self.coordinates = coordinates # in axis unit + self.text = text + self.scheme = scheme + self.style = Shape.RECT + self._border_width = 3175 # in EMU + self._border_color = Color.BLACK[2:] #"F3B3C5" + self._color = Color.WHITE[2:] + self._text_color = Color.BLACK[2:] + + def _get_border_color(self): + return self._border_color + + def _set_border_color(self, color): + self._border_color = short_color(color) + + border_color = property(_get_border_color, _set_border_color) + + def _get_color(self): + return self._color + + def _set_color(self, color): + self._color = short_color(color) + + color = property(_get_color, _set_color) + + def _get_text_color(self): + return self._text_color + + def _set_text_color(self, color): + self._text_color = short_color(color) + + text_color = property(_get_text_color, _set_text_color) + + def _get_border_width(self): + + return EMU_to_pixels(self._border_width) + + def _set_border_width(self, w): + + self._border_width = pixels_to_EMU(w) + print self._border_width + + border_width = property(_get_border_width, _set_border_width) + + def get_coordinates(self): + """ return shape coordinates in percentages (left, top, right, bottom) """ + + (x1, y1), (x2, y2) = self.coordinates + + drawing_width = pixels_to_EMU(self._chart.drawing.width) + drawing_height = pixels_to_EMU(self._chart.drawing.height) + plot_width = drawing_width * self._chart.width + plot_height = drawing_height * self._chart.height + + margin_left = self._chart._get_margin_left() * drawing_width + xunit = plot_width / self._chart.get_x_units() + + margin_top = self._chart._get_margin_top() * drawing_height + yunit = self._chart.get_y_units() + + x_start = (margin_left + (float(x1) * xunit)) / drawing_width + y_start = (margin_top + plot_height - (float(y1) * yunit)) / drawing_height + + x_end = (margin_left + (float(x2) * xunit)) / drawing_width + y_end = (margin_top + plot_height - (float(y2) * yunit)) / drawing_height + + def _norm_pct(pct): + """ force shapes to appear by truncating too large sizes """ + if pct>1: pct = 1 + elif pct<0: pct = 0 + return pct + + # allow user to specify y's in whatever order + # excel expect y_end to be lower + if y_end < y_start: + y_end, y_start = y_start, y_end + + return (_norm_pct(x_start), _norm_pct(y_start), + _norm_pct(x_end), _norm_pct(y_end)) + \ No newline at end of file diff --git a/tablib/packages/openpyxl/namedrange.py b/tablib/packages/openpyxl/namedrange.py new file mode 100644 index 0000000..617847a --- /dev/null +++ b/tablib/packages/openpyxl/namedrange.py @@ -0,0 +1,68 @@ +# file openpyxl/namedrange.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Track named groups of cells in a worksheet""" + +# Python stdlib imports +import re + +# package imports +from openpyxl.shared.exc import NamedRangeException + +# constants +NAMED_RANGE_RE = re.compile("'?([^']*)'?!((\$([A-Za-z]+))?\$([0-9]+)(:(\$([A-Za-z]+))?(\$([0-9]+)))?)$") + +class NamedRange(object): + """A named group of cells""" + __slots__ = ('name', 'destinations', 'local_only') + + def __init__(self, name, destinations): + self.name = name + self.destinations = destinations + self.local_only = False + + def __str__(self): + return ','.join([u'%s!%s' % (sheet, name) for sheet, name in self.destinations]) + + def __repr__(self): + + return '<%s "%s">' % (self.__class__.__name__, str(self)) + + +def split_named_range(range_string): + """Separate a named range into its component parts""" + + destinations = [] + + for range_string in range_string.split(','): + + match = NAMED_RANGE_RE.match(range_string) + if not match: + raise NamedRangeException('Invalid named range string: "%s"' % range_string) + else: + sheet_name, xlrange = match.groups()[:2] + destinations.append((sheet_name, xlrange)) + + return destinations diff --git a/tablib/packages/openpyxl/reader/__init__.py b/tablib/packages/openpyxl/reader/__init__.py new file mode 100644 index 0000000..a45d67e --- /dev/null +++ b/tablib/packages/openpyxl/reader/__init__.py @@ -0,0 +1,33 @@ +# file openpyxl/reader/__init__.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Imports for the openpyxl.reader namespace.""" + +# package imports +from openpyxl.reader import excel +from openpyxl.reader import strings +from openpyxl.reader import style +from openpyxl.reader import workbook +from openpyxl.reader import worksheet diff --git a/tablib/packages/openpyxl/reader/excel.py b/tablib/packages/openpyxl/reader/excel.py new file mode 100644 index 0000000..ba633e6 --- /dev/null +++ b/tablib/packages/openpyxl/reader/excel.py @@ -0,0 +1,111 @@ +# file openpyxl/reader/excel.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Read an xlsx file into Python""" + +# Python stdlib imports +from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile + +# package imports +from openpyxl.shared.exc import OpenModeError, InvalidFileException +from openpyxl.shared.ooxml import ARC_SHARED_STRINGS, ARC_CORE, ARC_APP, \ + ARC_WORKBOOK, PACKAGE_WORKSHEETS, ARC_STYLE +from openpyxl.workbook import Workbook +from openpyxl.reader.strings import read_string_table +from openpyxl.reader.style import read_style_table +from openpyxl.reader.workbook import read_sheets_titles, read_named_ranges, \ + read_properties_core, get_sheet_ids +from openpyxl.reader.worksheet import read_worksheet +from openpyxl.reader.iter_worksheet import unpack_worksheet + +def load_workbook(filename, use_iterators = False): + """Open the given filename and return the workbook + + :param filename: the path to open + :type filename: string + + :param use_iterators: use lazy load for cells + :type use_iterators: bool + + :rtype: :class:`openpyxl.workbook.Workbook` + + .. note:: + + When using lazy load, all worksheets will be :class:`openpyxl.reader.iter_worksheet.IterableWorksheet` + and the returned workbook will be read-only. + + """ + + if isinstance(filename, file): + # fileobject must have been opened with 'rb' flag + # it is required by zipfile + if 'b' not in filename.mode: + raise OpenModeError("File-object must be opened in binary mode") + + try: + archive = ZipFile(filename, 'r', ZIP_DEFLATED) + except (BadZipfile, RuntimeError, IOError, ValueError), e: + raise InvalidFileException(unicode(e)) + wb = Workbook() + + if use_iterators: + wb._set_optimized_read() + + try: + _load_workbook(wb, archive, filename, use_iterators) + except KeyError, e: + raise InvalidFileException(unicode(e)) + except Exception, e: + raise e + finally: + archive.close() + return wb + +def _load_workbook(wb, archive, filename, use_iterators): + + # get workbook-level information + wb.properties = read_properties_core(archive.read(ARC_CORE)) + try: + string_table = read_string_table(archive.read(ARC_SHARED_STRINGS)) + except KeyError: + string_table = {} + style_table = read_style_table(archive.read(ARC_STYLE)) + + # get worksheets + wb.worksheets = [] # remove preset worksheet + sheet_names = read_sheets_titles(archive.read(ARC_APP)) + for i, sheet_name in enumerate(sheet_names): + sheet_codename = 'sheet%d.xml' % (i + 1) + worksheet_path = '%s/%s' % (PACKAGE_WORKSHEETS, sheet_codename) + + if not use_iterators: + new_ws = read_worksheet(archive.read(worksheet_path), wb, sheet_name, string_table, style_table) + else: + xml_source = unpack_worksheet(archive, worksheet_path) + new_ws = read_worksheet(xml_source, wb, sheet_name, string_table, style_table, filename, sheet_codename) + #new_ws = read_worksheet(archive.read(worksheet_path), wb, sheet_name, string_table, style_table, filename, sheet_codename) + wb.add_sheet(new_ws, index = i) + + wb._named_ranges = read_named_ranges(archive.read(ARC_WORKBOOK), wb) diff --git a/tablib/packages/openpyxl/reader/iter_worksheet.py b/tablib/packages/openpyxl/reader/iter_worksheet.py new file mode 100644 index 0000000..f8e9399 --- /dev/null +++ b/tablib/packages/openpyxl/reader/iter_worksheet.py @@ -0,0 +1,343 @@ +# file openpyxl/reader/iter_worksheet.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +""" Iterators-based worksheet reader +*Still very raw* +""" + +from StringIO import StringIO +import warnings +import operator +from functools import partial +from itertools import ifilter, groupby +from openpyxl.worksheet import Worksheet +from openpyxl.cell import coordinate_from_string, get_column_letter, Cell +from openpyxl.reader.excel import get_sheet_ids +from openpyxl.reader.strings import read_string_table +from openpyxl.reader.style import read_style_table, NumberFormat +from openpyxl.shared.date_time import SharedDate +from openpyxl.reader.worksheet import read_dimension +from openpyxl.shared.ooxml import (MIN_COLUMN, MAX_COLUMN, PACKAGE_WORKSHEETS, + MAX_ROW, MIN_ROW, ARC_SHARED_STRINGS, ARC_APP, ARC_STYLE) +from xml.etree.cElementTree import iterparse +from zipfile import ZipFile +import openpyxl.cell +import re +import tempfile +import zlib +import zipfile +import struct + +TYPE_NULL = Cell.TYPE_NULL +MISSING_VALUE = None + +RE_COORDINATE = re.compile('^([A-Z]+)([0-9]+)$') + +SHARED_DATE = SharedDate() + +_COL_CONVERSION_CACHE = dict((get_column_letter(i), i) for i in xrange(1, 18279)) +def column_index_from_string(str_col, _col_conversion_cache=_COL_CONVERSION_CACHE): + # we use a function argument to get indexed name lookup + return _col_conversion_cache[str_col] +del _COL_CONVERSION_CACHE + +RAW_ATTRIBUTES = ['row', 'column', 'coordinate', 'internal_value', 'data_type', 'style_id', 'number_format'] + +try: + from collections import namedtuple + BaseRawCell = namedtuple('RawCell', RAW_ATTRIBUTES) +except ImportError: + + warnings.warn("""Unable to import 'namedtuple' module, this may cause memory issues when using optimized reader. Please upgrade your Python installation to 2.6+""") + + class BaseRawCell(object): + + def __init__(self, *args): + assert len(args)==len(RAW_ATTRIBUTES) + + for attr, val in zip(RAW_ATTRIBUTES, args): + setattr(self, attr, val) + + def _replace(self, **kwargs): + + self.__dict__.update(kwargs) + + return self + + +class RawCell(BaseRawCell): + """Optimized version of the :class:`openpyxl.cell.Cell`, using named tuples. + + Useful attributes are: + + * row + * column + * coordinate + * internal_value + + You can also access if needed: + + * data_type + * number_format + + """ + + @property + def is_date(self): + res = (self.data_type == Cell.TYPE_NUMERIC + and self.number_format is not None + and ('d' in self.number_format + or 'm' in self.number_format + or 'y' in self.number_format + or 'h' in self.number_format + or 's' in self.number_format + )) + + return res + +def iter_rows(workbook_name, sheet_name, xml_source, range_string = '', row_offset = 0, column_offset = 0): + + archive = get_archive_file(workbook_name) + + source = xml_source + + if range_string: + min_col, min_row, max_col, max_row = get_range_boundaries(range_string, row_offset, column_offset) + else: + min_col, min_row, max_col, max_row = read_dimension(xml_source = source) + min_col = column_index_from_string(min_col) + max_col = column_index_from_string(max_col) + 1 + max_row += 6 + + try: + string_table = read_string_table(archive.read(ARC_SHARED_STRINGS)) + except KeyError: + string_table = {} + + style_table = read_style_table(archive.read(ARC_STYLE)) + + source.seek(0) + p = iterparse(source) + + return get_squared_range(p, min_col, min_row, max_col, max_row, string_table, style_table) + + +def get_rows(p, min_column = MIN_COLUMN, min_row = MIN_ROW, max_column = MAX_COLUMN, max_row = MAX_ROW): + + return groupby(get_cells(p, min_row, min_column, max_row, max_column), operator.attrgetter('row')) + +def get_cells(p, min_row, min_col, max_row, max_col, _re_coordinate=RE_COORDINATE): + + for _event, element in p: + + if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c': + coord = element.get('r') + column_str, row = _re_coordinate.match(coord).groups() + + row = int(row) + column = column_index_from_string(column_str) + + if min_col <= column <= max_col and min_row <= row <= max_row: + data_type = element.get('t', 'n') + style_id = element.get('s') + value = element.findtext('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v') + yield RawCell(row, column_str, coord, value, data_type, style_id, None) + + if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v': + continue + element.clear() + + + +def get_range_boundaries(range_string, row = 0, column = 0): + + if ':' in range_string: + min_range, max_range = range_string.split(':') + min_col, min_row = coordinate_from_string(min_range) + max_col, max_row = coordinate_from_string(max_range) + + min_col = column_index_from_string(min_col) + column + max_col = column_index_from_string(max_col) + column + min_row += row + max_row += row + + else: + min_col, min_row = coordinate_from_string(range_string) + min_col = column_index_from_string(min_col) + max_col = min_col + 1 + max_row = min_row + + return (min_col, min_row, max_col, max_row) + +def get_archive_file(archive_name): + + return ZipFile(archive_name, 'r') + +def get_xml_source(archive_file, sheet_name): + + return archive_file.read('%s/%s' % (PACKAGE_WORKSHEETS, sheet_name)) + +def get_missing_cells(row, columns): + + return dict([(column, RawCell(row, column, '%s%s' % (column, row), MISSING_VALUE, TYPE_NULL, None, None)) for column in columns]) + +def get_squared_range(p, min_col, min_row, max_col, max_row, string_table, style_table): + + expected_columns = [get_column_letter(ci) for ci in xrange(min_col, max_col)] + + current_row = min_row + for row, cells in get_rows(p, min_row = min_row, max_row = max_row, min_column = min_col, max_column = max_col): + full_row = [] + if current_row < row: + + for gap_row in xrange(current_row, row): + + dummy_cells = get_missing_cells(gap_row, expected_columns) + + yield tuple([dummy_cells[column] for column in expected_columns]) + + current_row = row + + temp_cells = list(cells) + + retrieved_columns = dict([(c.column, c) for c in temp_cells]) + + missing_columns = list(set(expected_columns) - set(retrieved_columns.keys())) + + replacement_columns = get_missing_cells(row, missing_columns) + + for column in expected_columns: + + if column in retrieved_columns: + cell = retrieved_columns[column] + + if cell.style_id is not None: + style = style_table[int(cell.style_id)] + cell = cell._replace(number_format = style.number_format.format_code) #pylint: disable-msg=W0212 + if cell.internal_value is not None: + if cell.data_type == Cell.TYPE_STRING: + cell = cell._replace(internal_value = string_table[int(cell.internal_value)]) #pylint: disable-msg=W0212 + elif cell.data_type == Cell.TYPE_BOOL: + cell = cell._replace(internal_value = cell.internal_value == 'True') + elif cell.is_date: + cell = cell._replace(internal_value = SHARED_DATE.from_julian(float(cell.internal_value))) + elif cell.data_type == Cell.TYPE_NUMERIC: + cell = cell._replace(internal_value = float(cell.internal_value)) + full_row.append(cell) + + else: + full_row.append(replacement_columns[column]) + + current_row = row + 1 + + yield tuple(full_row) + +#------------------------------------------------------------------------------ + +class IterableWorksheet(Worksheet): + + def __init__(self, parent_workbook, title, workbook_name, + sheet_codename, xml_source): + + Worksheet.__init__(self, parent_workbook, title) + self._workbook_name = workbook_name + self._sheet_codename = sheet_codename + self._xml_source = xml_source + + def iter_rows(self, range_string = '', row_offset = 0, column_offset = 0): + """ Returns a squared range based on the `range_string` parameter, + using generators. + + :param range_string: range of cells (e.g. 'A1:C4') + :type range_string: string + + :param row: row index of the cell (e.g. 4) + :type row: int + + :param column: column index of the cell (e.g. 3) + :type column: int + + :rtype: generator + + """ + + return iter_rows(workbook_name = self._workbook_name, + sheet_name = self._sheet_codename, + xml_source = self._xml_source, + range_string = range_string, + row_offset = row_offset, + column_offset = column_offset) + + def cell(self, *args, **kwargs): + + raise NotImplementedError("use 'iter_rows()' instead") + + def range(self, *args, **kwargs): + + raise NotImplementedError("use 'iter_rows()' instead") + +def unpack_worksheet(archive, filename): + + temp_file = tempfile.TemporaryFile(mode='r+', prefix='openpyxl.', suffix='.unpack.temp') + + zinfo = archive.getinfo(filename) + + if zinfo.compress_type == zipfile.ZIP_STORED: + decoder = None + elif zinfo.compress_type == zipfile.ZIP_DEFLATED: + decoder = zlib.decompressobj(-zlib.MAX_WBITS) + else: + raise zipfile.BadZipFile("Unrecognized compression method") + + archive.fp.seek(_get_file_offset(archive, zinfo)) + bytes_to_read = zinfo.compress_size + + while True: + buff = archive.fp.read(min(bytes_to_read, 102400)) + if not buff: + break + bytes_to_read -= len(buff) + if decoder: + buff = decoder.decompress(buff) + temp_file.write(buff) + + if decoder: + temp_file.write(decoder.decompress('Z')) + + return temp_file + +def _get_file_offset(archive, zinfo): + + try: + return zinfo.file_offset + except AttributeError: + # From http://stackoverflow.com/questions/3781261/how-to-simulate-zipfile-open-in-python-2-5 + + # Seek over the fixed size fields to the "file name length" field in + # the file header (26 bytes). Unpack this and the "extra field length" + # field ourselves as info.extra doesn't seem to be the correct length. + archive.fp.seek(zinfo.header_offset + 26) + file_name_len, extra_len = struct.unpack(" 10000: + msg = 'Year not supported by Excel: %s' % year + raise ValueError(msg) + if self.excel_base_date == self.CALENDAR_WINDOWS_1900: + # Fudge factor for the erroneous fact that the year 1900 is + # treated as a Leap Year in MS Excel. This affects every date + # following 28th February 1900 + if year == 1900 and month <= 2: + excel_1900_leap_year = False + else: + excel_1900_leap_year = True + excel_base_date = 2415020 + else: + raise NotImplementedError('Mac dates are not yet supported.') + #excel_base_date = 2416481 + #excel_1900_leap_year = False + + # Julian base date adjustment + if month > 2: + month = month - 3 + else: + month = month + 9 + year -= 1 + + # Calculate the Julian Date, then subtract the Excel base date + # JD 2415020 = 31 - Dec - 1899 -> Excel Date of 0 + century, decade = int(str(year)[:2]), int(str(year)[2:]) + excel_date = floor(146097 * century / 4) + \ + floor((1461 * decade) / 4) + floor((153 * month + 2) / 5) + \ + day + 1721119 - excel_base_date + if excel_1900_leap_year: + excel_date += 1 + + # check to ensure that we exclude 2/29/1900 as a possible value + if self.excel_base_date == self.CALENDAR_WINDOWS_1900 \ + and excel_date == 60: + msg = 'Error: Excel believes 1900 was a leap year' + raise ValueError(msg) + excel_time = ((hours * 3600) + (minutes * 60) + seconds) / 86400 + return excel_date + excel_time + + def from_julian(self, value=0): + """Convert from the Excel JD back to a date""" + if self.excel_base_date == self.CALENDAR_WINDOWS_1900: + excel_base_date = 25569 + if value < 60: + excel_base_date -= 1 + elif value == 60: + msg = 'Error: Excel believes 1900 was a leap year' + raise ValueError(msg) + else: + raise NotImplementedError('Mac dates are not yet supported.') + #excel_base_date = 24107 + + if value >= 1: + utc_days = value - excel_base_date + + return EPOCH + datetime.timedelta(days=utc_days) + + elif value >= 0: + hours = floor(value * 24) + mins = floor(value * 24 * 60) - floor(hours * 60) + secs = floor(value * 24 * 60 * 60) - floor(hours * 60 * 60) - \ + floor(mins * 60) + return datetime.time(int(hours), int(mins), int(secs)) + else: + msg = 'Negative dates (%s) are not supported' % value + raise ValueError(msg) diff --git a/tablib/packages/openpyxl/shared/exc.py b/tablib/packages/openpyxl/shared/exc.py new file mode 100644 index 0000000..94a3e2c --- /dev/null +++ b/tablib/packages/openpyxl/shared/exc.py @@ -0,0 +1,59 @@ +# file openpyxl/shared/exc.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Definitions for openpyxl shared exception classes.""" + + +class CellCoordinatesException(Exception): + """Error for converting between numeric and A1-style cell references.""" + +class ColumnStringIndexException(Exception): + """Error for bad column names in A1-style cell references.""" + +class DataTypeException(Exception): + """Error for any data type inconsistencies.""" + +class NamedRangeException(Exception): + """Error for badly formatted named ranges.""" + +class SheetTitleException(Exception): + """Error for bad sheet names.""" + +class InsufficientCoordinatesException(Exception): + """Error for partially specified cell coordinates.""" + +class OpenModeError(Exception): + """Error for fileobj opened in non-binary mode.""" + +class InvalidFileException(Exception): + """Error for trying to open a non-ooxml file.""" + +class ReadOnlyWorkbookException(Exception): + """Error for trying to modify a read-only workbook""" + +class MissingNumberFormat(Exception): + """Error when a referenced number format is not in the stylesheet""" + + diff --git a/tablib/packages/openpyxl/shared/ooxml.py b/tablib/packages/openpyxl/shared/ooxml.py new file mode 100644 index 0000000..979b172 --- /dev/null +++ b/tablib/packages/openpyxl/shared/ooxml.py @@ -0,0 +1,60 @@ +# file openpyxl/shared/ooxml.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Constants for fixed paths in a file and xml namespace urls.""" + +MIN_ROW = 0 +MIN_COLUMN = 0 +MAX_COLUMN = 16384 +MAX_ROW = 1048576 + +# constants +PACKAGE_PROPS = 'docProps' +PACKAGE_XL = 'xl' +PACKAGE_RELS = '_rels' +PACKAGE_THEME = PACKAGE_XL + '/' + 'theme' +PACKAGE_WORKSHEETS = PACKAGE_XL + '/' + 'worksheets' +PACKAGE_DRAWINGS = PACKAGE_XL + '/' + 'drawings' +PACKAGE_CHARTS = PACKAGE_XL + '/' + 'charts' + +ARC_CONTENT_TYPES = '[Content_Types].xml' +ARC_ROOT_RELS = PACKAGE_RELS + '/.rels' +ARC_WORKBOOK_RELS = PACKAGE_XL + '/' + PACKAGE_RELS + '/workbook.xml.rels' +ARC_CORE = PACKAGE_PROPS + '/core.xml' +ARC_APP = PACKAGE_PROPS + '/app.xml' +ARC_WORKBOOK = PACKAGE_XL + '/workbook.xml' +ARC_STYLE = PACKAGE_XL + '/styles.xml' +ARC_THEME = PACKAGE_THEME + '/theme1.xml' +ARC_SHARED_STRINGS = PACKAGE_XL + '/sharedStrings.xml' + +NAMESPACES = { + 'cp': 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties', + 'dc': 'http://purl.org/dc/elements/1.1/', + 'dcterms': 'http://purl.org/dc/terms/', + 'dcmitype': 'http://purl.org/dc/dcmitype/', + 'xsi': 'http://www.w3.org/2001/XMLSchema-instance', + 'vt': 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes', + 'xml': 'http://www.w3.org/XML/1998/namespace' +} diff --git a/tablib/packages/openpyxl/shared/password_hasher.py b/tablib/packages/openpyxl/shared/password_hasher.py new file mode 100644 index 0000000..b5d0dd0 --- /dev/null +++ b/tablib/packages/openpyxl/shared/password_hasher.py @@ -0,0 +1,47 @@ +# file openpyxl/shared/password_hasher.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Basic password hashing.""" + + +def hash_password(plaintext_password=''): + """Create a password hash from a given string. + + This method is based on the algorithm provided by + Daniel Rentz of OpenOffice and the PEAR package + Spreadsheet_Excel_Writer by Xavier Noguer . + + """ + password = 0x0000 + i = 1 + for char in plaintext_password: + value = ord(char) << i + rotated_bits = value >> 15 + value &= 0x7fff + password ^= (value | rotated_bits) + i += 1 + password ^= len(plaintext_password) + password ^= 0xCE4B + return str(hex(password)).upper()[2:] diff --git a/tablib/packages/openpyxl/shared/units.py b/tablib/packages/openpyxl/shared/units.py new file mode 100644 index 0000000..fba82d7 --- /dev/null +++ b/tablib/packages/openpyxl/shared/units.py @@ -0,0 +1,67 @@ +# file openpyxl/shared/units.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +import math + +def pixels_to_EMU(value): + return int(round(value * 9525)) + +def EMU_to_pixels(value): + if not value: + return 0 + else: + return round(value / 9525.) + +def EMU_to_cm(value): + if not value: + return 0 + else: + return (EMU_to_pixels(value) * 2.57 / 96) + +def pixels_to_points(value): + return value * 0.67777777 + +def points_to_pixels(value): + if not value: + return 0 + else: + return int(math.ceil(value * 1.333333333)) + +def degrees_to_angle(value): + return int(round(value * 60000)) + +def angle_to_degrees(value): + if not value: + return 0 + else: + return round(value / 60000.) + +def short_color(color): + """ format a color to its short size """ + + if len(color) > 6: + return color[2:] + else: + return color diff --git a/tablib/packages/openpyxl/shared/xmltools.py b/tablib/packages/openpyxl/shared/xmltools.py new file mode 100644 index 0000000..9f0f943 --- /dev/null +++ b/tablib/packages/openpyxl/shared/xmltools.py @@ -0,0 +1,96 @@ +# file openpyxl/shared/xmltools.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Shared xml tools. + +Shortcut functions taken from: + http://lethain.com/entry/2009/jan/22/handling-very-large-csv-and-xml-files-in-python/ + +""" + +# Python stdlib imports +from xml.sax.xmlreader import AttributesNSImpl +from xml.sax.saxutils import XMLGenerator +try: + from xml.etree.ElementTree import ElementTree, Element, SubElement, \ + QName, fromstring, tostring +except ImportError: + from cElementTree import ElementTree, Element, SubElement, \ + QName, fromstring, tostring + +# package imports +from openpyxl import __name__ as prefix + + +def get_document_content(xml_node): + """Print nicely formatted xml to a string.""" + pretty_indent(xml_node) + return tostring(xml_node, 'utf-8') + + +def pretty_indent(elem, level=0): + """Format xml with nice indents and line breaks.""" + i = "\n" + level * " " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + pretty_indent(elem, level + 1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + +def start_tag(doc, name, attr=None, body=None, namespace=None): + """Wrapper to start an xml tag.""" + if attr is None: + attr = {} + attr_vals = {} + attr_keys = {} + for key, val in attr.iteritems(): + key_tuple = (namespace, key) + attr_vals[key_tuple] = val + attr_keys[key_tuple] = key + attr2 = AttributesNSImpl(attr_vals, attr_keys) + doc.startElementNS((namespace, name), name, attr2) + if body: + doc.characters(body) + + +def end_tag(doc, name, namespace=None): + """Wrapper to close an xml tag.""" + doc.endElementNS((namespace, name), name) + + +def tag(doc, name, attr=None, body=None, namespace=None): + """Wrapper to print xml tags and comments.""" + if attr is None: + attr = {} + start_tag(doc, name, attr, body, namespace) + end_tag(doc, name, namespace) diff --git a/tablib/packages/openpyxl/style.py b/tablib/packages/openpyxl/style.py new file mode 100644 index 0000000..0828105 --- /dev/null +++ b/tablib/packages/openpyxl/style.py @@ -0,0 +1,392 @@ +# file openpyxl/style.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Style and formatting option tracking.""" + +# Python stdlib imports +import re +try: + from hashlib import md5 +except ImportError: + from md5 import md5 + + +class HashableObject(object): + """Define how to hash property classes.""" + __fields__ = None + __leaf__ = False + + def __repr__(self): + + return ':'.join([repr(getattr(self, x)) for x in self.__fields__]) + + def __hash__(self): + +# return int(md5(repr(self)).hexdigest(), 16) + return hash(repr(self)) + +class Color(HashableObject): + """Named colors for use in styles.""" + BLACK = 'FF000000' + WHITE = 'FFFFFFFF' + RED = 'FFFF0000' + DARKRED = 'FF800000' + BLUE = 'FF0000FF' + DARKBLUE = 'FF000080' + GREEN = 'FF00FF00' + DARKGREEN = 'FF008000' + YELLOW = 'FFFFFF00' + DARKYELLOW = 'FF808000' + + __fields__ = ('index',) + __slots__ = __fields__ + __leaf__ = True + + def __init__(self, index): + super(Color, self).__init__() + self.index = index + + +class Font(HashableObject): + """Font options used in styles.""" + UNDERLINE_NONE = 'none' + UNDERLINE_DOUBLE = 'double' + UNDERLINE_DOUBLE_ACCOUNTING = 'doubleAccounting' + UNDERLINE_SINGLE = 'single' + UNDERLINE_SINGLE_ACCOUNTING = 'singleAccounting' + + __fields__ = ('name', + 'size', + 'bold', + 'italic', + 'superscript', + 'subscript', + 'underline', + 'strikethrough', + 'color') + __slots__ = __fields__ + + def __init__(self): + super(Font, self).__init__() + self.name = 'Calibri' + self.size = 11 + self.bold = False + self.italic = False + self.superscript = False + self.subscript = False + self.underline = self.UNDERLINE_NONE + self.strikethrough = False + self.color = Color(Color.BLACK) + + +class Fill(HashableObject): + """Area fill patterns for use in styles.""" + FILL_NONE = 'none' + FILL_SOLID = 'solid' + FILL_GRADIENT_LINEAR = 'linear' + FILL_GRADIENT_PATH = 'path' + FILL_PATTERN_DARKDOWN = 'darkDown' + FILL_PATTERN_DARKGRAY = 'darkGray' + FILL_PATTERN_DARKGRID = 'darkGrid' + FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal' + FILL_PATTERN_DARKTRELLIS = 'darkTrellis' + FILL_PATTERN_DARKUP = 'darkUp' + FILL_PATTERN_DARKVERTICAL = 'darkVertical' + FILL_PATTERN_GRAY0625 = 'gray0625' + FILL_PATTERN_GRAY125 = 'gray125' + FILL_PATTERN_LIGHTDOWN = 'lightDown' + FILL_PATTERN_LIGHTGRAY = 'lightGray' + FILL_PATTERN_LIGHTGRID = 'lightGrid' + FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal' + FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis' + FILL_PATTERN_LIGHTUP = 'lightUp' + FILL_PATTERN_LIGHTVERTICAL = 'lightVertical' + FILL_PATTERN_MEDIUMGRAY = 'mediumGray' + + __fields__ = ('fill_type', + 'rotation', + 'start_color', + 'end_color') + __slots__ = __fields__ + + def __init__(self): + super(Fill, self).__init__() + self.fill_type = self.FILL_NONE + self.rotation = 0 + self.start_color = Color(Color.WHITE) + self.end_color = Color(Color.BLACK) + + +class Border(HashableObject): + """Border options for use in styles.""" + BORDER_NONE = 'none' + BORDER_DASHDOT = 'dashDot' + BORDER_DASHDOTDOT = 'dashDotDot' + BORDER_DASHED = 'dashed' + BORDER_DOTTED = 'dotted' + BORDER_DOUBLE = 'double' + BORDER_HAIR = 'hair' + BORDER_MEDIUM = 'medium' + BORDER_MEDIUMDASHDOT = 'mediumDashDot' + BORDER_MEDIUMDASHDOTDOT = 'mediumDashDotDot' + BORDER_MEDIUMDASHED = 'mediumDashed' + BORDER_SLANTDASHDOT = 'slantDashDot' + BORDER_THICK = 'thick' + BORDER_THIN = 'thin' + + __fields__ = ('border_style', + 'color') + __slots__ = __fields__ + + def __init__(self): + super(Border, self).__init__() + self.border_style = self.BORDER_NONE + self.color = Color(Color.BLACK) + + +class Borders(HashableObject): + """Border positioning for use in styles.""" + DIAGONAL_NONE = 0 + DIAGONAL_UP = 1 + DIAGONAL_DOWN = 2 + DIAGONAL_BOTH = 3 + + __fields__ = ('left', + 'right', + 'top', + 'bottom', + 'diagonal', + 'diagonal_direction', + 'all_borders', + 'outline', + 'inside', + 'vertical', + 'horizontal') + __slots__ = __fields__ + + def __init__(self): + super(Borders, self).__init__() + self.left = Border() + self.right = Border() + self.top = Border() + self.bottom = Border() + self.diagonal = Border() + self.diagonal_direction = self.DIAGONAL_NONE + + self.all_borders = Border() + self.outline = Border() + self.inside = Border() + self.vertical = Border() + self.horizontal = Border() + + +class Alignment(HashableObject): + """Alignment options for use in styles.""" + HORIZONTAL_GENERAL = 'general' + HORIZONTAL_LEFT = 'left' + HORIZONTAL_RIGHT = 'right' + HORIZONTAL_CENTER = 'center' + HORIZONTAL_CENTER_CONTINUOUS = 'centerContinuous' + HORIZONTAL_JUSTIFY = 'justify' + VERTICAL_BOTTOM = 'bottom' + VERTICAL_TOP = 'top' + VERTICAL_CENTER = 'center' + VERTICAL_JUSTIFY = 'justify' + + __fields__ = ('horizontal', + 'vertical', + 'text_rotation', + 'wrap_text', + 'shrink_to_fit', + 'indent') + __slots__ = __fields__ + __leaf__ = True + + def __init__(self): + super(Alignment, self).__init__() + self.horizontal = self.HORIZONTAL_GENERAL + self.vertical = self.VERTICAL_BOTTOM + self.text_rotation = 0 + self.wrap_text = False + self.shrink_to_fit = False + self.indent = 0 + + +class NumberFormat(HashableObject): + """Numer formatting for use in styles.""" + FORMAT_GENERAL = 'General' + FORMAT_TEXT = '@' + FORMAT_NUMBER = '0' + FORMAT_NUMBER_00 = '0.00' + FORMAT_NUMBER_COMMA_SEPARATED1 = '#,##0.00' + FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-' + FORMAT_PERCENTAGE = '0%' + FORMAT_PERCENTAGE_00 = '0.00%' + FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd' + FORMAT_DATE_YYYYMMDD = 'yy-mm-dd' + FORMAT_DATE_DDMMYYYY = 'dd/mm/yy' + FORMAT_DATE_DMYSLASH = 'd/m/y' + FORMAT_DATE_DMYMINUS = 'd-m-y' + FORMAT_DATE_DMMINUS = 'd-m' + FORMAT_DATE_MYMINUS = 'm-y' + FORMAT_DATE_XLSX14 = 'mm-dd-yy' + FORMAT_DATE_XLSX15 = 'd-mmm-yy' + FORMAT_DATE_XLSX16 = 'd-mmm' + FORMAT_DATE_XLSX17 = 'mmm-yy' + FORMAT_DATE_XLSX22 = 'm/d/yy h:mm' + FORMAT_DATE_DATETIME = 'd/m/y h:mm' + FORMAT_DATE_TIME1 = 'h:mm AM/PM' + FORMAT_DATE_TIME2 = 'h:mm:ss AM/PM' + FORMAT_DATE_TIME3 = 'h:mm' + FORMAT_DATE_TIME4 = 'h:mm:ss' + FORMAT_DATE_TIME5 = 'mm:ss' + FORMAT_DATE_TIME6 = 'h:mm:ss' + FORMAT_DATE_TIME7 = 'i:s.S' + FORMAT_DATE_TIME8 = 'h:mm:ss@' + FORMAT_DATE_YYYYMMDDSLASH = 'yy/mm/dd@' + FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-' + FORMAT_CURRENCY_USD = '$#,##0_-' + FORMAT_CURRENCY_EUR_SIMPLE = '[$EUR ]#,##0.00_-' + _BUILTIN_FORMATS = { + 0: 'General', + 1: '0', + 2: '0.00', + 3: '#,##0', + 4: '#,##0.00', + + 9: '0%', + 10: '0.00%', + 11: '0.00E+00', + 12: '# ?/?', + 13: '# ??/??', + 14: 'mm-dd-yy', + 15: 'd-mmm-yy', + 16: 'd-mmm', + 17: 'mmm-yy', + 18: 'h:mm AM/PM', + 19: 'h:mm:ss AM/PM', + 20: 'h:mm', + 21: 'h:mm:ss', + 22: 'm/d/yy h:mm', + + 37: '#,##0 (#,##0)', + 38: '#,##0 [Red](#,##0)', + 39: '#,##0.00(#,##0.00)', + 40: '#,##0.00[Red](#,##0.00)', + + 41: '_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)', + 42: '_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)', + 43: '_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)', + + 44: '_("$"* #,##0.00_)_("$"* \(#,##0.00\)_("$"* "-"??_)_(@_)', + 45: 'mm:ss', + 46: '[h]:mm:ss', + 47: 'mmss.0', + 48: '##0.0E+0', + 49: '@', } + _BUILTIN_FORMATS_REVERSE = dict( + [(value, key) for key, value in _BUILTIN_FORMATS.iteritems()]) + + __fields__ = ('_format_code', + '_format_index') + __slots__ = __fields__ + __leaf__ = True + + DATE_INDICATORS = 'dmyhs' + + def __init__(self): + super(NumberFormat, self).__init__() + self._format_code = self.FORMAT_GENERAL + self._format_index = 0 + + def _set_format_code(self, format_code = FORMAT_GENERAL): + """Setter for the format_code property.""" + self._format_code = format_code + self._format_index = self.builtin_format_id(format = format_code) + + def _get_format_code(self): + """Getter for the format_code property.""" + return self._format_code + + format_code = property(_get_format_code, _set_format_code) + + def builtin_format_code(self, index): + """Return one of the standard format codes by index.""" + return self._BUILTIN_FORMATS[index] + + def is_builtin(self, format = None): + """Check if a format code is a standard format code.""" + if format is None: + format = self._format_code + return format in self._BUILTIN_FORMATS.values() + + def builtin_format_id(self, format): + """Return the id of a standard style.""" + return self._BUILTIN_FORMATS_REVERSE.get(format, None) + + def is_date_format(self, format = None): + """Check if the number format is actually representing a date.""" + if format is None: + format = self._format_code + + return any([x in format for x in self.DATE_INDICATORS]) + +class Protection(HashableObject): + """Protection options for use in styles.""" + PROTECTION_INHERIT = 'inherit' + PROTECTION_PROTECTED = 'protected' + PROTECTION_UNPROTECTED = 'unprotected' + + __fields__ = ('locked', + 'hidden') + __slots__ = __fields__ + __leaf__ = True + + def __init__(self): + super(Protection, self).__init__() + self.locked = self.PROTECTION_INHERIT + self.hidden = self.PROTECTION_INHERIT + + +class Style(HashableObject): + """Style object containing all formatting details.""" + __fields__ = ('font', + 'fill', + 'borders', + 'alignment', + 'number_format', + 'protection') + __slots__ = __fields__ + + def __init__(self): + super(Style, self).__init__() + self.font = Font() + self.fill = Fill() + self.borders = Borders() + self.alignment = Alignment() + self.number_format = NumberFormat() + self.protection = Protection() + +DEFAULTS = Style() diff --git a/tablib/packages/openpyxl/tests/__init__.py b/tablib/packages/openpyxl/tests/__init__.py new file mode 100644 index 0000000..de89032 --- /dev/null +++ b/tablib/packages/openpyxl/tests/__init__.py @@ -0,0 +1,24 @@ +# file openpyxl/tests/__init__.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni diff --git a/tablib/packages/openpyxl/tests/helper.py b/tablib/packages/openpyxl/tests/helper.py new file mode 100644 index 0000000..ae9c8b4 --- /dev/null +++ b/tablib/packages/openpyxl/tests/helper.py @@ -0,0 +1,94 @@ +# file openpyxl/tests/helper.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +import os +import os.path +import shutil +import difflib +from StringIO import StringIO +from pprint import pprint +from tempfile import gettempdir + +# package imports +from openpyxl.shared.xmltools import fromstring, ElementTree +from openpyxl.shared.xmltools import pretty_indent + +# constants +DATADIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test_data')) +TMPDIR = os.path.join(gettempdir(), 'openpyxl_test_temp') + + +def make_tmpdir(): + try: + os.makedirs(TMPDIR) + except OSError: + pass + + +def clean_tmpdir(): + if os.path.isdir(TMPDIR): + shutil.rmtree(TMPDIR, ignore_errors = True) + + +def assert_equals_file_content(reference_file, fixture, filetype = 'xml'): + if os.path.isfile(fixture): + with open(fixture) as fixture_file: + fixture_content = fixture_file.read() + else: + fixture_content = fixture + + with open(reference_file) as expected_file: + expected_content = expected_file.read() + + if filetype == 'xml': + fixture_content = fromstring(fixture_content) + pretty_indent(fixture_content) + temp = StringIO() + ElementTree(fixture_content).write(temp) + fixture_content = temp.getvalue() + + expected_content = fromstring(expected_content) + pretty_indent(expected_content) + temp = StringIO() + ElementTree(expected_content).write(temp) + expected_content = temp.getvalue() + + fixture_lines = fixture_content.split('\n') + expected_lines = expected_content.split('\n') + differences = list(difflib.unified_diff(expected_lines, fixture_lines)) + if differences: + temp = StringIO() + pprint(differences, stream = temp) + assert False, 'Differences found : %s' % temp.getvalue() + +def get_xml(xml_node): + + io = StringIO() + ElementTree(xml_node).write(io, encoding = 'UTF-8') + ret = io.getvalue() + io.close() + return ret.replace('\n', '') diff --git a/tablib/packages/openpyxl/tests/test_cell.py b/tablib/packages/openpyxl/tests/test_cell.py new file mode 100644 index 0000000..edc35c0 --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_cell.py @@ -0,0 +1,200 @@ +# file openpyxl/tests/test_cell.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from datetime import time, datetime + +# 3rd party imports +from nose.tools import eq_, raises, assert_raises + +# package imports +from openpyxl.worksheet import Worksheet +from openpyxl.workbook import Workbook +from openpyxl.shared.exc import ColumnStringIndexException, \ + CellCoordinatesException, DataTypeException +from openpyxl.cell import column_index_from_string, \ + coordinate_from_string, get_column_letter, Cell, absolute_coordinate + + +def test_coordinates(): + column, row = coordinate_from_string('ZF46') + eq_("ZF", column) + eq_(46, row) + + +@raises(CellCoordinatesException) +def test_invalid_coordinate(): + coordinate_from_string('AAA') + + +def test_absolute(): + eq_('$ZF$51', absolute_coordinate('ZF51')) + +def test_absolute_multiple(): + + eq_('$ZF$51:$ZF$53', absolute_coordinate('ZF51:ZF$53')) + + +def test_column_index(): + eq_(10, column_index_from_string('J')) + eq_(270, column_index_from_string('jJ')) + eq_(7030, column_index_from_string('jjj')) + + +def test_bad_column_index(): + + @raises(ColumnStringIndexException) + def _check(bad_string): + column_index_from_string(bad_string) + + bad_strings = ('JJJJ', '', '$', '1',) + for bad_string in bad_strings: + yield _check, bad_string + + +def test_column_letter_boundries(): + assert_raises(ColumnStringIndexException, get_column_letter, 0) + assert_raises(ColumnStringIndexException, get_column_letter, 18279) + + +def test_column_letter(): + eq_('ZZZ', get_column_letter(18278)) + eq_('JJJ', get_column_letter(7030)) + eq_('AB', get_column_letter(28)) + eq_('AA', get_column_letter(27)) + eq_('Z', get_column_letter(26)) + + +def test_initial_value(): + cell = Cell(None, 'A', 1, value = '17.5') + eq_(cell.TYPE_NUMERIC, cell.data_type) + + +class TestCellValueTypes(): + + @classmethod + def setup_class(cls): + cls.cell = Cell(None, 'A', 1) + + def test_1st(self): + eq_(self.cell.TYPE_NULL, self.cell.data_type) + + def test_null(self): + self.cell.value = None + eq_(self.cell.TYPE_NULL, self.cell.data_type) + + def test_numeric(self): + + def check_numeric(value): + self.cell.value = value + eq_(self.cell.TYPE_NUMERIC, self.cell.data_type) + + values = (42, '4.2', '-42.000', '0', 0, 0.0001, '0.9999', '99E-02') + for value in values: + yield check_numeric, value + + def test_string(self): + self.cell.value = 'hello' + eq_(self.cell.TYPE_STRING, self.cell.data_type) + + def test_formula(self): + self.cell.value = '=42' + eq_(self.cell.TYPE_FORMULA, self.cell.data_type) + + def test_boolean(self): + self.cell.value = True + eq_(self.cell.TYPE_BOOL, self.cell.data_type) + self.cell.value = False + eq_(self.cell.TYPE_BOOL, self.cell.data_type) + + def test_error_codes(self): + + def check_error(): + eq_(self.cell.TYPE_ERROR, self.cell.data_type) + + for error_string in self.cell.ERROR_CODES.keys(): + self.cell.value = error_string + yield check_error + + +def test_data_type_check(): + cell = Cell(None, 'A', 1) + cell.bind_value(None) + eq_(Cell.TYPE_NULL, cell._data_type) + + cell.bind_value('.0e000') + eq_(Cell.TYPE_NUMERIC, cell._data_type) + + cell.bind_value('-0.e-0') + eq_(Cell.TYPE_NUMERIC, cell._data_type) + + cell.bind_value('1E') + eq_(Cell.TYPE_STRING, cell._data_type) + +@raises(DataTypeException) +def test_set_bad_type(): + cell = Cell(None, 'A', 1) + cell.set_value_explicit(1, 'q') + + +def test_time(): + + def check_time(raw_value, coerced_value): + cell.value = raw_value + eq_(cell.value, coerced_value) + eq_(cell.TYPE_NUMERIC, cell.data_type) + + wb = Workbook() + ws = Worksheet(wb) + cell = Cell(ws, 'A', 1) + values = (('03:40:16', time(3, 40, 16)), ('03:40', time(3, 40)),) + for raw_value, coerced_value in values: + yield check_time, raw_value, coerced_value + + +def test_date_format_on_non_date(): + wb = Workbook() + ws = Worksheet(wb) + cell = Cell(ws, 'A', 1) + cell.value = datetime.now() + cell.value = 'testme' + eq_('testme', cell.value) + + +def test_repr(): + wb = Workbook() + ws = Worksheet(wb) + cell = Cell(ws, 'A', 1) + eq_(repr(cell), '', 'Got bad repr: %s' % repr(cell)) + +def test_is_date(): + wb = Workbook() + ws = Worksheet(wb) + cell = Cell(ws, 'A', 1) + cell.value = datetime.now() + eq_(cell.is_date(), True) + cell.value = 'testme' + eq_('testme', cell.value) + eq_(cell.is_date(), False) diff --git a/tablib/packages/openpyxl/tests/test_chart.py b/tablib/packages/openpyxl/tests/test_chart.py new file mode 100644 index 0000000..2605a2b --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_chart.py @@ -0,0 +1,131 @@ +# file openpyxl/tests/test_chart.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +from nose.tools import eq_ + +from openpyxl.tests.helper import get_xml +from openpyxl.shared.xmltools import Element +from openpyxl.writer.charts import ChartWriter +from openpyxl.workbook import Workbook +from openpyxl.chart import BarChart, ScatterChart, Serie, Reference +from openpyxl.style import Color + +class TestChartWriter(object): + + def setUp(self): + + wb = Workbook() + ws = wb.get_active_sheet() + ws.title = u'data' + for i in range(10): + ws.cell(row = i, column = 0).value = i + self.chart = BarChart() + self.chart.title = 'TITLE' + self.chart.add_serie(Serie(Reference(ws, (0, 0), (10, 0)))) + self.chart._series[-1].color = Color.GREEN + self.cw = ChartWriter(self.chart) + self.root = Element('test') + + def test_write_title(self): + self.cw._write_title(self.root) + eq_(get_xml(self.root), 'TITLE') + + def test_write_xaxis(self): + + self.cw._write_axis(self.root, self.chart.x_axis, 'c:catAx') + eq_(get_xml(self.root), '') + + def test_write_yaxis(self): + + self.cw._write_axis(self.root, self.chart.y_axis, 'c:valAx') + eq_(get_xml(self.root), '') + + def test_write_series(self): + + self.cw._write_series(self.root) + eq_(get_xml(self.root), 'data!$A$1:$A$11General0123456789None') + + def test_write_legend(self): + + self.cw._write_legend(self.root) + eq_(get_xml(self.root), '') + + def test_write_print_settings(self): + + self.cw._write_print_settings(self.root) + eq_(get_xml(self.root), '') + + def test_write_chart(self): + + self.cw._write_chart(self.root) + eq_(get_xml(self.root), 'TITLEdata!$A$1:$A$11General0123456789None') + + +class TestScatterChartWriter(object): + + def setUp(self): + + wb = Workbook() + ws = wb.get_active_sheet() + ws.title = u'data' + for i in range(10): + ws.cell(row = i, column = 0).value = i + ws.cell(row = i, column = 1).value = i + self.scatterchart = ScatterChart() + self.scatterchart.add_serie(Serie(Reference(ws, (0, 0), (10, 0)), + xvalues = Reference(ws, (0, 1), (10, 1)))) + self.cw = ChartWriter(self.scatterchart) + self.root = Element('test') + + def test_write_xaxis(self): + + self.cw._write_axis(self.root, self.scatterchart.x_axis, 'c:valAx') + eq_(get_xml(self.root), '') + + + def test_write_yaxis(self): + + self.cw._write_axis(self.root, self.scatterchart.y_axis, 'c:valAx') + eq_(get_xml(self.root), '') + + def test_write_series(self): + + self.cw._write_series(self.root) + eq_(get_xml(self.root), 'data!$B$1:$B$11General0123456789Nonedata!$A$1:$A$11General0123456789None') + + def test_write_legend(self): + + self.cw._write_legend(self.root) + eq_(get_xml(self.root), '') + + def test_write_print_settings(self): + + self.cw._write_print_settings(self.root) + eq_(get_xml(self.root), '') + + def test_write_chart(self): + + self.cw._write_chart(self.root) + eq_(get_xml(self.root), 'data!$B$1:$B$11General0123456789Nonedata!$A$1:$A$11General0123456789None') diff --git a/tablib/packages/openpyxl/tests/test_dump.py b/tablib/packages/openpyxl/tests/test_dump.py new file mode 100644 index 0000000..58794b7 --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_dump.py @@ -0,0 +1,109 @@ + +# file openpyxl/tests/test_dump.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from datetime import time, datetime + +# 3rd party imports +from nose.tools import eq_, raises, assert_raises + +from openpyxl.workbook import Workbook +from openpyxl.cell import get_column_letter + +from openpyxl.reader.excel import load_workbook + +from openpyxl.writer.strings import StringTableBuilder + +from tempfile import NamedTemporaryFile +import os +import shutil + +def test_dump_sheet(): + + test_file = NamedTemporaryFile(prefix='openpyxl.', suffix='.xlsx', delete=False) + test_file.close() + test_filename = test_file.name + + wb = Workbook(optimized_write = True) + + ws = wb.create_sheet() + + letters = [get_column_letter(x+1) for x in xrange(20)] + + expected_rows = [] + + for row in xrange(20): + + expected_rows.append(['%s%d' % (letter, row+1) for letter in letters]) + + for row in xrange(20): + + expected_rows.append([(row+1) for letter in letters]) + + for row in xrange(10): + + expected_rows.append([datetime(2010, ((x % 12)+1), row+1) for x in range(len(letters))]) + + for row in xrange(20): + + expected_rows.append(['=%s%d' % (letter, row+1) for letter in letters]) + + for row in expected_rows: + + ws.append(row) + + wb.save(test_filename) + + wb2 = load_workbook(test_filename, True) + + ws = wb2.worksheets[0] + + + for ex_row, ws_row in zip(expected_rows[:-20], ws.iter_rows()): + + for ex_cell, ws_cell in zip(ex_row, ws_row): + + eq_(ex_cell, ws_cell.internal_value) + + os.remove(test_filename) + + +def test_table_builder(): + + sb = StringTableBuilder() + + result = {'a':0, 'b':1, 'c':2, 'd':3} + + for letter in sorted(result.keys()): + + for x in range(5): + + sb.add(letter) + + table = dict(sb.get_table()) + + for key,idx in result.iteritems(): + eq_(idx, table[key]) diff --git a/tablib/packages/openpyxl/tests/test_iter.py b/tablib/packages/openpyxl/tests/test_iter.py new file mode 100644 index 0000000..a68237d --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_iter.py @@ -0,0 +1,112 @@ +# file openpyxl/tests/test_iter.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +from nose.tools import eq_, raises, assert_raises +import os.path as osp +from openpyxl.tests.helper import DATADIR +from openpyxl.reader.iter_worksheet import get_range_boundaries +from openpyxl.reader.excel import load_workbook +import datetime + +class TestWorksheet(object): + + workbook_name = osp.join(DATADIR, 'genuine', 'empty.xlsx') + +class TestText(TestWorksheet): + sheet_name = 'Sheet1 - Text' + + expected = [['This is cell A1 in Sheet 1', None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, 'This is cell G5'], ] + + def test_read_fast_integrated(self): + + wb = load_workbook(filename = self.workbook_name, use_iterators = True) + ws = wb.get_sheet_by_name(name = self.sheet_name) + + for row, expected_row in zip(ws.iter_rows(), self.expected): + + row_values = [x.internal_value for x in row] + + eq_(row_values, expected_row) + + + def test_get_boundaries_range(self): + + eq_(get_range_boundaries('C1:C4'), (3, 1, 3, 4)) + + def test_get_boundaries_one(self): + + + eq_(get_range_boundaries('C1'), (3, 1, 4, 1)) + + def test_read_single_cell_range(self): + + wb = load_workbook(filename = self.workbook_name, use_iterators = True) + ws = wb.get_sheet_by_name(name = self.sheet_name) + + eq_('This is cell A1 in Sheet 1', list(ws.iter_rows('A1'))[0][0].internal_value) + +class TestIntegers(TestWorksheet): + + sheet_name = 'Sheet2 - Numbers' + + expected = [[x + 1] for x in xrange(30)] + + query_range = 'D1:E30' + + def test_read_fast_integrated(self): + + wb = load_workbook(filename = self.workbook_name, use_iterators = True) + ws = wb.get_sheet_by_name(name = self.sheet_name) + + for row, expected_row in zip(ws.iter_rows(self.query_range), self.expected): + + row_values = [x.internal_value for x in row] + + eq_(row_values, expected_row) + +class TestFloats(TestWorksheet): + + sheet_name = 'Sheet2 - Numbers' + query_range = 'K1:L30' + expected = expected = [[(x + 1) / 100.0] for x in xrange(30)] + +class TestDates(TestWorksheet): + + sheet_name = 'Sheet4 - Dates' + + def test_read_single_cell_date(self): + + wb = load_workbook(filename = self.workbook_name, use_iterators = True) + ws = wb.get_sheet_by_name(name = self.sheet_name) + + eq_(datetime.datetime(1973, 5, 20), list(ws.iter_rows('A1'))[0][0].internal_value) + eq_(datetime.datetime(1973, 5, 20, 9, 15, 2), list(ws.iter_rows('C1'))[0][0].internal_value) + + + diff --git a/tablib/packages/openpyxl/tests/test_meta.py b/tablib/packages/openpyxl/tests/test_meta.py new file mode 100644 index 0000000..3aefdcf --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_meta.py @@ -0,0 +1,50 @@ +# file openpyxl/tests/test_meta.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +import os.path + +# package imports +from openpyxl.tests.helper import DATADIR, assert_equals_file_content +from openpyxl.writer.workbook import write_content_types, write_root_rels +from openpyxl.workbook import Workbook + + +def test_write_content_types(): + wb = Workbook() + wb.create_sheet() + wb.create_sheet() + content = write_content_types(wb) + reference_file = os.path.join(DATADIR, 'writer', 'expected', + '[Content_Types].xml') + assert_equals_file_content(reference_file, content) + + +def test_write_root_rels(): + wb = Workbook() + content = write_root_rels(wb) + reference_file = os.path.join(DATADIR, 'writer', 'expected', '.rels') + assert_equals_file_content(reference_file, content) diff --git a/tablib/packages/openpyxl/tests/test_named_range.py b/tablib/packages/openpyxl/tests/test_named_range.py new file mode 100644 index 0000000..a56ed86 --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_named_range.py @@ -0,0 +1,102 @@ +# file openpyxl/tests/test_named_range.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +import os.path + +# 3rd-party imports +from nose.tools import eq_, assert_raises + +# package imports +from openpyxl.tests.helper import DATADIR +from openpyxl.namedrange import split_named_range +from openpyxl.reader.workbook import read_named_ranges +from openpyxl.shared.exc import NamedRangeException +from openpyxl.reader.excel import load_workbook + + +def test_split(): + eq_([('My Sheet', '$D$8'), ], split_named_range("'My Sheet'!$D$8")) + + +def test_split_no_quotes(): + eq_([('HYPOTHESES', '$B$3:$L$3'), ], split_named_range('HYPOTHESES!$B$3:$L$3')) + + +def test_bad_range_name(): + assert_raises(NamedRangeException, split_named_range, 'HYPOTHESES$B$3') + + +def test_read_named_ranges(): + + class DummyWs(object): + title = 'My Sheeet' + + def __str__(self): + return self.title + + class DummyWB(object): + + def get_sheet_by_name(self, name): + return DummyWs() + + with open(os.path.join(DATADIR, 'reader', 'workbook.xml')) as handle: + content = handle.read() + named_ranges = read_named_ranges(content, DummyWB()) + eq_(["My Sheeet!$D$8"], [str(range) for range in named_ranges]) + +def test_oddly_shaped_named_ranges(): + + ranges_counts = ((4, 'TEST_RANGE'), + (3, 'TRAP_1'), + (13, 'TRAP_2')) + + def check_ranges(ws, count, range_name): + + eq_(count, len(ws.range(range_name))) + + wb = load_workbook(os.path.join(DATADIR, 'genuine', 'merge_range.xlsx'), + use_iterators = False) + + ws = wb.worksheets[0] + + for count, range_name in ranges_counts: + + yield check_ranges, ws, count, range_name + + +def test_merged_cells_named_range(): + + wb = load_workbook(os.path.join(DATADIR, 'genuine', 'merge_range.xlsx'), + use_iterators = False) + + ws = wb.worksheets[0] + + cell = ws.range('TRAP_3') + + eq_('B15', cell.get_coordinate()) + + eq_(10, cell.value) diff --git a/tablib/packages/openpyxl/tests/test_number_format.py b/tablib/packages/openpyxl/tests/test_number_format.py new file mode 100644 index 0000000..2f5c9da --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_number_format.py @@ -0,0 +1,142 @@ +# file openpyxl/tests/test_number_format.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +from datetime import datetime, date + +# 3rd party imports +from nose.tools import eq_, assert_almost_equal, assert_raises + +# package imports +from openpyxl.workbook import Workbook +from openpyxl.worksheet import Worksheet +from openpyxl.cell import Cell +from openpyxl.style import NumberFormat +from openpyxl.shared.date_time import SharedDate + + +class TestNumberFormat(object): + + @classmethod + def setup_class(cls): + cls.workbook = Workbook() + cls.worksheet = Worksheet(cls.workbook, 'Test') + cls.sd = SharedDate() + + def test_convert_date_to_julian(self): + eq_(40167, self.sd.to_julian(2009, 12, 20)) + + def test_convert_date_from_julian(self): + + def test_date_equal(julian, datetime): + + eq_(self.sd.from_julian(julian), datetime) + + date_pairs= ( + (40167, datetime(2009, 12, 20)), + (21980, datetime(1960, 03, 05)), + ) + + for count, dt in date_pairs: + yield test_date_equal, count, dt + + def test_convert_datetime_to_julian(self): + eq_(40167, self.sd.datetime_to_julian(datetime(2009, 12, 20))) + + def test_insert_float(self): + self.worksheet.cell('A1').value = 3.14 + eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) + + def test_insert_percentage(self): + self.worksheet.cell('A1').value = '3.14%' + eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) + assert_almost_equal(0.0314, self.worksheet.cell('A1').value) + + def test_insert_datetime(self): + self.worksheet.cell('A1').value = date.today() + eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) + + def test_insert_date(self): + self.worksheet.cell('A1').value = datetime.now() + eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) + + def test_internal_date(self): + dt = datetime(2010, 7, 13, 6, 37, 41) + self.worksheet.cell('A3').value = dt + eq_(40372.27616898148, self.worksheet.cell('A3')._value) + + def test_datetime_interpretation(self): + dt = datetime(2010, 7, 13, 6, 37, 41) + self.worksheet.cell('A3').value = dt + eq_(dt, self.worksheet.cell('A3').value) + + def test_date_interpretation(self): + dt = date(2010, 7, 13) + self.worksheet.cell('A3').value = dt + eq_(datetime(2010, 7, 13, 0, 0), self.worksheet.cell('A3').value) + + def test_number_format_style(self): + self.worksheet.cell('A1').value = '12.6%' + eq_(NumberFormat.FORMAT_PERCENTAGE, \ + self.worksheet.cell('A1').style.number_format.format_code) + + def test_date_format_on_non_date(self): + cell = self.worksheet.cell('A1') + + def check_date_pair(count, date_string): + cell.value = datetime.strptime(date_string, '%Y-%m-%d') + eq_(count, cell._value) + + date_pairs = ( + (15, '1900-01-15'), + (59, '1900-02-28'), + (61, '1900-03-01'), + (367, '1901-01-01'), + (2958465, '9999-12-31'), ) + for count, date_string in date_pairs: + yield check_date_pair, count, date_string + + def test_1900_leap_year(self): + assert_raises(ValueError, self.sd.from_julian, 60) + assert_raises(ValueError, self.sd.to_julian, 1900, 2, 29) + + def test_bad_date(self): + + def check_bad_date(year, month, day): + assert_raises(ValueError, self.sd.to_julian, year, month, day) + + bad_dates = ((1776, 07, 04), (1899, 12, 31), ) + for year, month, day in bad_dates: + yield check_bad_date, year, month, day + + def test_bad_julian_date(self): + assert_raises(ValueError, self.sd.from_julian, -1) + + def test_mac_date(self): + self.sd.excel_base_date = self.sd.CALENDAR_MAC_1904 + assert_raises(NotImplementedError, self.sd.to_julian, 2000, 1, 1) + assert_raises(NotImplementedError, self.sd.from_julian, 1) + self.sd.excel_base_date = self.sd.CALENDAR_WINDOWS_1900 diff --git a/tablib/packages/openpyxl/tests/test_password_hash.py b/tablib/packages/openpyxl/tests/test_password_hash.py new file mode 100644 index 0000000..fd2fddf --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_password_hash.py @@ -0,0 +1,41 @@ +# file openpyxl/tests/test_password_hash.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# 3rd party imports +from nose.tools import eq_ + +# package imports +from openpyxl.shared.password_hasher import hash_password +from openpyxl.worksheet import SheetProtection + + +def test_hasher(): + eq_('CBEB', hash_password('test')) + + +def test_sheet_protection(): + protection = SheetProtection() + protection.password = 'test' + eq_('CBEB', protection.password) diff --git a/tablib/packages/openpyxl/tests/test_props.py b/tablib/packages/openpyxl/tests/test_props.py new file mode 100644 index 0000000..fb9c5d0 --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_props.py @@ -0,0 +1,124 @@ +# coding=utf-8 +# file openpyxl/tests/test_props.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +from zipfile import ZipFile, ZIP_DEFLATED +from datetime import datetime +import os.path + +# 3rd party imports +from nose.tools import eq_ + +# package imports +from openpyxl.tests.helper import DATADIR, TMPDIR, make_tmpdir, clean_tmpdir, \ + assert_equals_file_content +from openpyxl.reader.workbook import read_properties_core, \ + read_sheets_titles, get_number_of_parts +from openpyxl.writer.workbook import write_properties_core, \ + write_properties_app +from openpyxl.shared.ooxml import ARC_APP, ARC_CORE +from openpyxl.workbook import DocumentProperties, Workbook + + +class TestReaderProps(object): + + @classmethod + def setup_class(cls): + cls.genuine_filename = os.path.join(DATADIR, 'genuine', 'empty.xlsx') + cls.archive = ZipFile(cls.genuine_filename, 'r', ZIP_DEFLATED) + + @classmethod + def teardown_class(cls): + cls.archive.close() + + def test_read_properties_core(self): + content = self.archive.read(ARC_CORE) + prop = read_properties_core(content) + eq_(prop.creator, '*.*') + eq_(prop.last_modified_by, u'Aurélien Campéas') + eq_(prop.created, datetime(2010, 4, 9, 20, 43, 12)) + eq_(prop.modified, datetime(2011, 2, 9, 13, 49, 32)) + + def test_read_sheets_titles(self): + content = self.archive.read(ARC_APP) + sheet_titles = read_sheets_titles(content) + eq_(sheet_titles, \ + ['Sheet1 - Text', 'Sheet2 - Numbers', 'Sheet3 - Formulas', 'Sheet4 - Dates']) + + +class TestReaderPropsMixed(object): + + @classmethod + def setup_class(cls): + reference_filename = \ + os.path.join(DATADIR, 'reader', 'app-multi-titles.xml') + with open(reference_filename) as handle: + cls.content = handle.read() + + def test_read_sheet_titles_mixed(self): + sheet_titles = read_sheets_titles(self.content) + eq_(sheet_titles, + ['ToC', 'ContractYear', 'ContractTier', 'Demand', + 'LinearizedFunction', 'Market', 'Transmission']) + + def test_number_of_parts(self): + parts_number = get_number_of_parts(self.content) + eq_(parts_number, + ({'Worksheets': 7, 'Named Ranges': 7}, + ['Worksheets', 'Named Ranges'])) + + +class TestWriteProps(object): + + @classmethod + def setup_class(cls): + make_tmpdir() + cls.tmp_filename = os.path.join(TMPDIR, 'test.xlsx') + cls.prop = DocumentProperties() + + @classmethod + def teardown_class(cls): + clean_tmpdir() + + def test_write_properties_core(self): + self.prop.creator = 'TEST_USER' + self.prop.last_modified_by = 'SOMEBODY' + self.prop.created = datetime(2010, 4, 1, 20, 30, 00) + self.prop.modified = datetime(2010, 4, 5, 14, 5, 30) + content = write_properties_core(self.prop) + assert_equals_file_content( + os.path.join(DATADIR, 'writer', 'expected', 'core.xml'), + content) + + def test_write_properties_app(self): + wb = Workbook() + wb.create_sheet() + wb.create_sheet() + content = write_properties_app(wb) + assert_equals_file_content( + os.path.join(DATADIR, 'writer', 'expected', 'app.xml'), + content) diff --git a/tablib/packages/openpyxl/tests/test_read.py b/tablib/packages/openpyxl/tests/test_read.py new file mode 100644 index 0000000..eb6ab07 --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_read.py @@ -0,0 +1,134 @@ +# file openpyxl/tests/test_read.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +import os.path + +# 3rd party imports +from nose.tools import eq_, raises + +# package imports +from openpyxl.tests.helper import DATADIR +from openpyxl.worksheet import Worksheet +from openpyxl.workbook import Workbook +from openpyxl.style import NumberFormat, Style +from openpyxl.reader.worksheet import read_worksheet, read_dimension +from openpyxl.reader.excel import load_workbook +from openpyxl.shared.exc import InvalidFileException + + +def test_read_standalone_worksheet(): + + class DummyWb(object): + + def get_sheet_by_name(self, value): + return None + + path = os.path.join(DATADIR, 'reader', 'sheet2.xml') + with open(path) as handle: + ws = read_worksheet(handle.read(), DummyWb(), + 'Sheet 2', {1: 'hello'}, {1: Style()}) + assert isinstance(ws, Worksheet) + eq_(ws.cell('G5').value, 'hello') + eq_(ws.cell('D30').value, 30) + eq_(ws.cell('K9').value, 0.09) + + +def test_read_standard_workbook(): + path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') + wb = load_workbook(path) + assert isinstance(wb, Workbook) + +def test_read_standard_workbook_from_fileobj(): + path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') + fo = open(path, mode = 'rb') + wb = load_workbook(fo) + assert isinstance(wb, Workbook) + +def test_read_worksheet(): + path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') + wb = load_workbook(path) + sheet2 = wb.get_sheet_by_name('Sheet2 - Numbers') + assert isinstance(sheet2, Worksheet) + eq_('This is cell G5', sheet2.cell('G5').value) + eq_(18, sheet2.cell('D18').value) + + +def test_read_nostring_workbook(): + genuine_wb = os.path.join(DATADIR, 'genuine', 'empty-no-string.xlsx') + wb = load_workbook(genuine_wb) + assert isinstance(wb, Workbook) + +@raises(InvalidFileException) +def test_read_empty_file(): + + null_file = os.path.join(DATADIR, 'reader', 'null_file.xlsx') + wb = load_workbook(null_file) + +@raises(InvalidFileException) +def test_read_empty_archive(): + + null_file = os.path.join(DATADIR, 'reader', 'null_archive.xlsx') + wb = load_workbook(null_file) + +def test_read_dimension(): + + path = os.path.join(DATADIR, 'reader', 'sheet2.xml') + + with open(path) as handle: + + dimension = read_dimension(xml_source = handle.read()) + + eq_(('D', 1, 'K', 30), dimension) + +class TestReadWorkbookWithStyles(object): + + @classmethod + def setup_class(cls): + cls.genuine_wb = os.path.join(DATADIR, 'genuine', \ + 'empty-with-styles.xlsx') + wb = load_workbook(cls.genuine_wb) + cls.ws = wb.get_sheet_by_name('Sheet1') + + def test_read_general_style(self): + eq_(self.ws.cell('A1').style.number_format.format_code, + NumberFormat.FORMAT_GENERAL) + + def test_read_date_style(self): + eq_(self.ws.cell('A2').style.number_format.format_code, + NumberFormat.FORMAT_DATE_XLSX14) + + def test_read_number_style(self): + eq_(self.ws.cell('A3').style.number_format.format_code, + NumberFormat.FORMAT_NUMBER_00) + + def test_read_time_style(self): + eq_(self.ws.cell('A4').style.number_format.format_code, + NumberFormat.FORMAT_DATE_TIME3) + + def test_read_percentage_style(self): + eq_(self.ws.cell('A5').style.number_format.format_code, + NumberFormat.FORMAT_PERCENTAGE_00) diff --git a/tablib/packages/openpyxl/tests/test_strings.py b/tablib/packages/openpyxl/tests/test_strings.py new file mode 100644 index 0000000..bf3cc57 --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_strings.py @@ -0,0 +1,68 @@ +# file openpyxl/tests/test_strings.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +import os.path + +# 3rd party imports +from nose.tools import eq_ + +# package imports +from openpyxl.tests.helper import DATADIR +from openpyxl.workbook import Workbook +from openpyxl.writer.strings import create_string_table +from openpyxl.reader.strings import read_string_table + + +def test_create_string_table(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('B12').value = 'hello' + ws.cell('B13').value = 'world' + ws.cell('D28').value = 'hello' + table = create_string_table(wb) + eq_({'hello': 1, 'world': 0}, table) + + +def test_read_string_table(): + with open(os.path.join(DATADIR, 'reader', 'sharedStrings.xml')) as handle: + content = handle.read() + string_table = read_string_table(content) + eq_({0: 'This is cell A1 in Sheet 1', 1: 'This is cell G5'}, string_table) + +def test_empty_string(): + with open(os.path.join(DATADIR, 'reader', 'sharedStrings-emptystring.xml')) as handle: + content = handle.read() + string_table = read_string_table(content) + eq_({0: 'Testing empty cell', 1:''}, string_table) + +def test_formatted_string_table(): + with open(os.path.join(DATADIR, 'reader', 'shared-strings-rich.xml')) \ + as handle: + content = handle.read() + string_table = read_string_table(content) + eq_({0: 'Welcome', 1: 'to the best shop in town', + 2: " let's play "}, string_table) diff --git a/tablib/packages/openpyxl/tests/test_style.py b/tablib/packages/openpyxl/tests/test_style.py new file mode 100644 index 0000000..ee51123 --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_style.py @@ -0,0 +1,181 @@ +# file openpyxl/tests/test_style.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +import os.path +import datetime + +# 3rd party imports +from nose.tools import eq_, assert_false, ok_ + +# package imports +from openpyxl.tests.helper import DATADIR, assert_equals_file_content, get_xml +from openpyxl.reader.style import read_style_table +from openpyxl.workbook import Workbook +from openpyxl.style import NumberFormat +from openpyxl.writer.styles import StyleWriter +from openpyxl.style import NumberFormat, Border, Color + + +class TestCreateStyle(object): + + @classmethod + def setup_class(cls): + now = datetime.datetime.now() + cls.workbook = Workbook() + cls.worksheet = cls.workbook.create_sheet() + cls.worksheet.cell(coordinate = 'A1').value = '12.34%' + cls.worksheet.cell(coordinate = 'B4').value = now + cls.worksheet.cell(coordinate = 'B5').value = now + cls.worksheet.cell(coordinate = 'C14').value = u'This is a test' + cls.worksheet.cell(coordinate = 'D9').value = '31.31415' + cls.worksheet.cell(coordinate = 'D9').style.number_format.format_code = \ + NumberFormat.FORMAT_NUMBER_00 + cls.writer = StyleWriter(cls.workbook) + + def test_create_style_table(self): + eq_(3, len(self.writer.style_table)) + + def test_write_style_table(self): + reference_file = os.path.join(DATADIR, 'writer', 'expected', 'simple-styles.xml') + assert_equals_file_content(reference_file, self.writer.write_table()) + +class TestStyleWriter(object): + + def setUp(self): + + self.workbook = Workbook() + self.worksheet = self.workbook.create_sheet() + + def test_no_style(self): + + w = StyleWriter(self.workbook) + eq_(0, len(w.style_table)) + + def test_nb_style(self): + + for i in range(1, 6): + self.worksheet.cell(row=1, column=i).style.font.size += i + w = StyleWriter(self.workbook) + eq_(5, len(w.style_table)) + + self.worksheet.cell('A10').style.borders.top = Border.BORDER_THIN + w = StyleWriter(self.workbook) + eq_(6, len(w.style_table)) + + def test_style_unicity(self): + + for i in range(1, 6): + self.worksheet.cell(row=1, column=i).style.font.bold = True + w = StyleWriter(self.workbook) + eq_(1, len(w.style_table)) + + def test_fonts(self): + + self.worksheet.cell('A1').style.font.size = 12 + self.worksheet.cell('A1').style.font.bold = True + w = StyleWriter(self.workbook) + w._write_fonts() + eq_(get_xml(w._root), '') + + def test_fills(self): + + self.worksheet.cell('A1').style.fill.fill_type = 'solid' + self.worksheet.cell('A1').style.fill.start_color.index = Color.DARKYELLOW + w = StyleWriter(self.workbook) + w._write_fills() + eq_(get_xml(w._root), '') + + def test_borders(self): + + self.worksheet.cell('A1').style.borders.top.border_style = Border.BORDER_THIN + self.worksheet.cell('A1').style.borders.top.color.index = Color.DARKYELLOW + w = StyleWriter(self.workbook) + w._write_borders() + eq_(get_xml(w._root), '') + + def test_write_cell_xfs_1(self): + + self.worksheet.cell('A1').style.font.size = 12 + w = StyleWriter(self.workbook) + ft = w._write_fonts() + nft = w._write_number_formats() + w._write_cell_xfs(nft, ft, {}, {}) + xml = get_xml(w._root) + ok_('applyFont="1"' in xml) + ok_('applyFillId="1"' not in xml) + ok_('applyBorder="1"' not in xml) + ok_('applyAlignment="1"' not in xml) + + def test_alignment(self): + self.worksheet.cell('A1').style.alignment.horizontal = 'center' + self.worksheet.cell('A1').style.alignment.vertical = 'center' + w = StyleWriter(self.workbook) + nft = w._write_number_formats() + w._write_cell_xfs(nft,{},{},{}) + xml = get_xml(w._root) + ok_('applyAlignment="1"' in xml) + ok_('horizontal="center"' in xml) + ok_('vertical="center"' in xml) + + +#def test_format_comparisions(): +# format1 = NumberFormat() +# format2 = NumberFormat() +# format3 = NumberFormat() +# format1.format_code = 'm/d/yyyy' +# format2.format_code = 'm/d/yyyy' +# format3.format_code = 'mm/dd/yyyy' +# assert not format1 < format2 +# assert format1 < format3 +# assert format1 == format2 +# assert format1 != format3 + + +def test_builtin_format(): + format = NumberFormat() + format.format_code = '0.00' + eq_(format.builtin_format_code(2), format._format_code) + + +def test_read_style(): + reference_file = os.path.join(DATADIR, 'reader', 'simple-styles.xml') + with open(reference_file, 'r') as handle: + content = handle.read() + style_table = read_style_table(content) + eq_(4, len(style_table)) + eq_(NumberFormat._BUILTIN_FORMATS[9], + style_table[1].number_format.format_code) + eq_('yyyy-mm-dd', style_table[2].number_format.format_code) + + +def test_read_cell_style(): + reference_file = os.path.join( + DATADIR, 'reader', 'empty-workbook-styles.xml') + with open(reference_file, 'r') as handle: + content = handle.read() + style_table = read_style_table(content) + eq_(2, len(style_table)) diff --git a/tablib/packages/openpyxl/tests/test_theme.py b/tablib/packages/openpyxl/tests/test_theme.py new file mode 100644 index 0000000..b0f6f00 --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_theme.py @@ -0,0 +1,37 @@ +# file openpyxl/tests/test_theme.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +import os.path + +# package imports +from openpyxl.tests.helper import DATADIR, assert_equals_file_content +from openpyxl.writer.theme import write_theme + + +def test_write_theme(): + content = write_theme() + assert_equals_file_content( + os.path.join(DATADIR, 'writer', 'expected', 'theme1.xml'), content) diff --git a/tablib/packages/openpyxl/tests/test_workbook.py b/tablib/packages/openpyxl/tests/test_workbook.py new file mode 100644 index 0000000..20191d2 --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_workbook.py @@ -0,0 +1,148 @@ +# file openpyxl/tests/test_workbook.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# 3rd party imports +from nose.tools import eq_, with_setup, raises +import os.path as osp + +# package imports +from openpyxl.workbook import Workbook +from openpyxl.reader.excel import load_workbook +from openpyxl.namedrange import NamedRange +from openpyxl.shared.exc import ReadOnlyWorkbookException +from openpyxl.tests.helper import TMPDIR, clean_tmpdir, make_tmpdir + +import datetime + +def test_get_active_sheet(): + wb = Workbook() + active_sheet = wb.get_active_sheet() + eq_(active_sheet, wb.worksheets[0]) + + +def test_create_sheet(): + wb = Workbook() + new_sheet = wb.create_sheet(0) + eq_(new_sheet, wb.worksheets[0]) + + +@raises(ReadOnlyWorkbookException) +def test_create_sheet_readonly(): + wb = Workbook() + wb._set_optimized_read() + wb.create_sheet() + + +def test_remove_sheet(): + wb = Workbook() + new_sheet = wb.create_sheet(0) + wb.remove_sheet(new_sheet) + assert new_sheet not in wb.worksheets + + +def test_get_sheet_by_name(): + wb = Workbook() + new_sheet = wb.create_sheet() + title = 'my sheet' + new_sheet.title = title + found_sheet = wb.get_sheet_by_name(title) + eq_(new_sheet, found_sheet) + + +def test_get_index(): + wb = Workbook() + new_sheet = wb.create_sheet(0) + sheet_index = wb.get_index(new_sheet) + eq_(sheet_index, 0) + + +def test_get_sheet_names(): + wb = Workbook() + names = ['Sheet', 'Sheet1', 'Sheet2', 'Sheet3', 'Sheet4', 'Sheet5'] + for count in range(5): + wb.create_sheet(0) + actual_names = wb.get_sheet_names() + eq_(sorted(actual_names), sorted(names)) + + +def test_get_named_ranges(): + wb = Workbook() + eq_(wb.get_named_ranges(), wb._named_ranges) + + +def test_add_named_range(): + wb = Workbook() + new_sheet = wb.create_sheet() + named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) + wb.add_named_range(named_range) + named_ranges_list = wb.get_named_ranges() + assert named_range in named_ranges_list + + +def test_get_named_range(): + wb = Workbook() + new_sheet = wb.create_sheet() + named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) + wb.add_named_range(named_range) + found_named_range = wb.get_named_range('test_nr') + eq_(named_range, found_named_range) + + +def test_remove_named_range(): + wb = Workbook() + new_sheet = wb.create_sheet() + named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) + wb.add_named_range(named_range) + wb.remove_named_range(named_range) + named_ranges_list = wb.get_named_ranges() + assert named_range not in named_ranges_list + +@with_setup(setup = make_tmpdir, teardown = clean_tmpdir) +def test_add_local_named_range(): + wb = Workbook() + new_sheet = wb.create_sheet() + named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) + named_range.local_only = True + wb.add_named_range(named_range) + dest_filename = osp.join(TMPDIR, 'local_named_range_book.xlsx') + wb.save(dest_filename) + + +@with_setup(setup = make_tmpdir, teardown = clean_tmpdir) +def test_write_regular_date(): + + today = datetime.datetime(2010, 1, 18, 14, 15, 20, 1600) + + book = Workbook() + sheet = book.get_active_sheet() + sheet.cell("A1").value = today + dest_filename = osp.join(TMPDIR, 'date_read_write_issue.xlsx') + book.save(dest_filename) + + test_book = load_workbook(dest_filename) + test_sheet = test_book.get_active_sheet() + + eq_(test_sheet.cell("A1").value, today) + diff --git a/tablib/packages/openpyxl/tests/test_worksheet.py b/tablib/packages/openpyxl/tests/test_worksheet.py new file mode 100644 index 0000000..1894f1d --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_worksheet.py @@ -0,0 +1,258 @@ +# file openpyxl/tests/test_worksheet.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# 3rd party imports +from nose.tools import eq_, raises, assert_raises + +# package imports +from openpyxl.workbook import Workbook +from openpyxl.worksheet import Worksheet, Relationship, flatten +from openpyxl.cell import Cell +from openpyxl.shared.exc import CellCoordinatesException, \ + SheetTitleException, InsufficientCoordinatesException, \ + NamedRangeException + + +class TestWorksheet(): + + @classmethod + def setup_class(cls): + cls.wb = Workbook() + + def test_new_worksheet(self): + ws = Worksheet(self.wb) + eq_(self.wb, ws._parent) + + def test_new_sheet_name(self): + self.wb.worksheets = [] + ws = Worksheet(self.wb, title = '') + eq_(repr(ws), '') + + def test_get_cell(self): + ws = Worksheet(self.wb) + cell = ws.cell('A1') + eq_(cell.get_coordinate(), 'A1') + + @raises(SheetTitleException) + def test_set_bad_title(self): + Worksheet(self.wb, 'X' * 50) + + def test_set_bad_title_character(self): + assert_raises(SheetTitleException, Worksheet, self.wb, '[') + assert_raises(SheetTitleException, Worksheet, self.wb, ']') + assert_raises(SheetTitleException, Worksheet, self.wb, '*') + assert_raises(SheetTitleException, Worksheet, self.wb, ':') + assert_raises(SheetTitleException, Worksheet, self.wb, '?') + assert_raises(SheetTitleException, Worksheet, self.wb, '/') + assert_raises(SheetTitleException, Worksheet, self.wb, '\\') + + def test_worksheet_dimension(self): + ws = Worksheet(self.wb) + eq_('A1:A1', ws.calculate_dimension()) + ws.cell('B12').value = 'AAA' + eq_('A1:B12', ws.calculate_dimension()) + + def test_worksheet_range(self): + ws = Worksheet(self.wb) + xlrange = ws.range('A1:C4') + assert isinstance(xlrange, tuple) + eq_(4, len(xlrange)) + eq_(3, len(xlrange[0])) + + def test_worksheet_named_range(self): + ws = Worksheet(self.wb) + self.wb.create_named_range('test_range', ws, 'C5') + xlrange = ws.range('test_range') + assert isinstance(xlrange, Cell) + eq_(5, xlrange.row) + + @raises(NamedRangeException) + def test_bad_named_range(self): + ws = Worksheet(self.wb) + ws.range('bad_range') + + @raises(NamedRangeException) + def test_named_range_wrong_sheet(self): + ws1 = Worksheet(self.wb) + ws2 = Worksheet(self.wb) + self.wb.create_named_range('wrong_sheet_range', ws1, 'C5') + ws2.range('wrong_sheet_range') + + def test_cell_offset(self): + ws = Worksheet(self.wb) + eq_('C17', ws.cell('B15').offset(2, 1).get_coordinate()) + + def test_range_offset(self): + ws = Worksheet(self.wb) + xlrange = ws.range('A1:C4', 1, 3) + assert isinstance(xlrange, tuple) + eq_(4, len(xlrange)) + eq_(3, len(xlrange[0])) + eq_('D2', xlrange[0][0].get_coordinate()) + + def test_cell_alternate_coordinates(self): + ws = Worksheet(self.wb) + cell = ws.cell(row = 8, column = 4) + eq_('E9', cell.get_coordinate()) + + @raises(InsufficientCoordinatesException) + def test_cell_insufficient_coordinates(self): + ws = Worksheet(self.wb) + cell = ws.cell(row = 8) + + def test_cell_range_name(self): + ws = Worksheet(self.wb) + self.wb.create_named_range('test_range_single', ws, 'B12') + assert_raises(CellCoordinatesException, ws.cell, 'test_range_single') + c_range_name = ws.range('test_range_single') + c_range_coord = ws.range('B12') + c_cell = ws.cell('B12') + eq_(c_range_coord, c_range_name) + eq_(c_range_coord, c_cell) + + def test_garbage_collect(self): + ws = Worksheet(self.wb) + ws.cell('A1').value = '' + ws.cell('B2').value = '0' + ws.cell('C4').value = 0 + ws.garbage_collect() + eq_(ws.get_cell_collection(), [ws.cell('B2'), ws.cell('C4')]) + + def test_hyperlink_relationships(self): + ws = Worksheet(self.wb) + eq_(len(ws.relationships), 0) + + ws.cell('A1').hyperlink = "http://test.com" + eq_(len(ws.relationships), 1) + eq_("rId1", ws.cell('A1').hyperlink_rel_id) + eq_("rId1", ws.relationships[0].id) + eq_("http://test.com", ws.relationships[0].target) + eq_("External", ws.relationships[0].target_mode) + + ws.cell('A2').hyperlink = "http://test2.com" + eq_(len(ws.relationships), 2) + eq_("rId2", ws.cell('A2').hyperlink_rel_id) + eq_("rId2", ws.relationships[1].id) + eq_("http://test2.com", ws.relationships[1].target) + eq_("External", ws.relationships[1].target_mode) + + @raises(ValueError) + def test_bad_relationship_type(self): + rel = Relationship('bad_type') + + def test_append_list(self): + ws = Worksheet(self.wb) + + ws.append(['This is A1', 'This is B1']) + + eq_('This is A1', ws.cell('A1').value) + eq_('This is B1', ws.cell('B1').value) + + def test_append_dict_letter(self): + ws = Worksheet(self.wb) + + ws.append({'A' : 'This is A1', 'C' : 'This is C1'}) + + eq_('This is A1', ws.cell('A1').value) + eq_('This is C1', ws.cell('C1').value) + + def test_append_dict_index(self): + ws = Worksheet(self.wb) + + ws.append({0 : 'This is A1', 2 : 'This is C1'}) + + eq_('This is A1', ws.cell('A1').value) + eq_('This is C1', ws.cell('C1').value) + + @raises(TypeError) + def test_bad_append(self): + ws = Worksheet(self.wb) + ws.append("test") + + def test_append_2d_list(self): + + ws = Worksheet(self.wb) + + ws.append(['This is A1', 'This is B1']) + ws.append(['This is A2', 'This is B2']) + + vals = ws.range('A1:B2') + + eq_((('This is A1', 'This is B1'), + ('This is A2', 'This is B2'),), flatten(vals)) + + def test_rows(self): + + ws = Worksheet(self.wb) + + ws.cell('A1').value = 'first' + ws.cell('C9').value = 'last' + + rows = ws.rows + + eq_(len(rows), 9) + + eq_(rows[0][0].value, 'first') + eq_(rows[-1][-1].value, 'last') + + def test_cols(self): + + ws = Worksheet(self.wb) + + ws.cell('A1').value = 'first' + ws.cell('C9').value = 'last' + + cols = ws.columns + + eq_(len(cols), 3) + + eq_(cols[0][0].value, 'first') + eq_(cols[-1][-1].value, 'last') + + def test_auto_filter(self): + ws = Worksheet(self.wb) + ws.auto_filter = ws.range('a1:f1') + assert ws.auto_filter == 'A1:F1' + + ws.auto_filter = '' + assert ws.auto_filter is None + + ws.auto_filter = 'c1:g9' + assert ws.auto_filter == 'C1:G9' + + def test_freeze(self): + ws = Worksheet(self.wb) + ws.freeze_panes = ws.cell('b2') + assert ws.freeze_panes == 'B2' + + ws.freeze_panes = '' + assert ws.freeze_panes is None + + ws.freeze_panes = 'c5' + assert ws.freeze_panes == 'C5' + + ws.freeze_panes = ws.cell('A1') + assert ws.freeze_panes is None + diff --git a/tablib/packages/openpyxl/tests/test_write.py b/tablib/packages/openpyxl/tests/test_write.py new file mode 100644 index 0000000..834e73c --- /dev/null +++ b/tablib/packages/openpyxl/tests/test_write.py @@ -0,0 +1,203 @@ +# file openpyxl/tests/test_write.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +from StringIO import StringIO +import os.path + +# 3rd party imports +from nose.tools import eq_, with_setup, raises + +# package imports +from openpyxl.tests.helper import TMPDIR, DATADIR, \ + assert_equals_file_content, clean_tmpdir, make_tmpdir +from openpyxl.workbook import Workbook +from openpyxl.reader.excel import load_workbook +from openpyxl.writer.excel import save_workbook, save_virtual_workbook, \ + ExcelWriter +from openpyxl.writer.workbook import write_workbook, write_workbook_rels +from openpyxl.writer.worksheet import write_worksheet, write_worksheet_rels +from openpyxl.writer.strings import write_string_table +from openpyxl.writer.styles import StyleWriter + + +@with_setup(setup = make_tmpdir, teardown = clean_tmpdir) +def test_write_empty_workbook(): + wb = Workbook() + dest_filename = os.path.join(TMPDIR, 'empty_book.xlsx') + save_workbook(wb, dest_filename) + assert os.path.isfile(dest_filename) + + +def test_write_virtual_workbook(): + old_wb = Workbook() + saved_wb = save_virtual_workbook(old_wb) + new_wb = load_workbook(StringIO(saved_wb)) + assert new_wb + + +def test_write_workbook_rels(): + wb = Workbook() + content = write_workbook_rels(wb) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'workbook.xml.rels'), content) + + +def test_write_workbook(): + wb = Workbook() + content = write_workbook(wb) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'workbook.xml'), content) + + +def test_write_string_table(): + table = {'hello': 1, 'world': 2, 'nice': 3} + content = write_string_table(table) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sharedStrings.xml'), content) + + +def test_write_worksheet(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F42').value = 'hello' + content = write_worksheet(ws, {'hello': 0}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1.xml'), content) + + +def test_write_hidden_worksheet(): + wb = Workbook() + ws = wb.create_sheet() + ws.sheet_state = ws.SHEETSTATE_HIDDEN + ws.cell('F42').value = 'hello' + content = write_worksheet(ws, {'hello': 0}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1.xml'), content) + + +def test_write_formula(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F1').value = 10 + ws.cell('F2').value = 32 + ws.cell('F3').value = '=F1+F2' + content = write_worksheet(ws, {}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_formula.xml'), content) + + +def test_write_style(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F1').value = '13%' + style_id_by_hash = StyleWriter(wb).get_style_by_hash() + content = write_worksheet(ws, {}, style_id_by_hash) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_style.xml'), content) + + +def test_write_height(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F1').value = 10 + ws.row_dimensions[ws.cell('F1').row].height = 30 + content = write_worksheet(ws, {}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_height.xml'), content) + + +def test_write_hyperlink(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('A1').value = "test" + ws.cell('A1').hyperlink = "http://test.com" + content = write_worksheet(ws, {'test': 0}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_hyperlink.xml'), content) + + +def test_write_hyperlink_rels(): + wb = Workbook() + ws = wb.create_sheet() + eq_(0, len(ws.relationships)) + ws.cell('A1').value = "test" + ws.cell('A1').hyperlink = "http://test.com/" + eq_(1, len(ws.relationships)) + ws.cell('A2').value = "test" + ws.cell('A2').hyperlink = "http://test2.com/" + eq_(2, len(ws.relationships)) + content = write_worksheet_rels(ws, 1) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_hyperlink.xml.rels'), content) + + +def test_hyperlink_value(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('A1').hyperlink = "http://test.com" + eq_("http://test.com", ws.cell('A1').value) + ws.cell('A1').value = "test" + eq_("test", ws.cell('A1').value) + +def test_write_auto_filter(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F42').value = 'hello' + ws.auto_filter = 'A1:F1' + content = write_worksheet(ws, {'hello': 0}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_auto_filter.xml'), content) + +def test_freeze_panes_horiz(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F42').value = 'hello' + ws.freeze_panes = 'A4' + content = write_worksheet(ws, {'hello': 0}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_freeze_panes_horiz.xml'), content) + +def test_freeze_panes_vert(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F42').value = 'hello' + ws.freeze_panes = 'D1' + content = write_worksheet(ws, {'hello': 0}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_freeze_panes_vert.xml'), content) + pass + +def test_freeze_panes_both(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F42').value = 'hello' + ws.freeze_panes = 'D4' + content = write_worksheet(ws, {'hello': 0}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_freeze_panes_both.xml'), content) + + diff --git a/tablib/packages/openpyxl/workbook.py b/tablib/packages/openpyxl/workbook.py new file mode 100644 index 0000000..10a4564 --- /dev/null +++ b/tablib/packages/openpyxl/workbook.py @@ -0,0 +1,186 @@ +# file openpyxl/workbook.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Workbook is the top-level container for all document information.""" + +__docformat__ = "restructuredtext en" + +# Python stdlib imports +import datetime +import os + +# package imports +from openpyxl.worksheet import Worksheet +from openpyxl.writer.dump_worksheet import DumpWorksheet, save_dump +from openpyxl.writer.strings import StringTableBuilder +from openpyxl.namedrange import NamedRange +from openpyxl.style import Style +from openpyxl.writer.excel import save_workbook +from openpyxl.shared.exc import ReadOnlyWorkbookException + + +class DocumentProperties(object): + """High-level properties of the document.""" + + def __init__(self): + self.creator = 'Unknown' + self.last_modified_by = self.creator + self.created = datetime.datetime.now() + self.modified = datetime.datetime.now() + self.title = 'Untitled' + self.subject = '' + self.description = '' + self.keywords = '' + self.category = '' + self.company = 'Microsoft Corporation' + + +class DocumentSecurity(object): + """Security information about the document.""" + + def __init__(self): + self.lock_revision = False + self.lock_structure = False + self.lock_windows = False + self.revision_password = '' + self.workbook_password = '' + + +class Workbook(object): + """Workbook is the container for all other parts of the document.""" + + def __init__(self, optimized_write = False): + self.worksheets = [] + self._active_sheet_index = 0 + self._named_ranges = [] + self.properties = DocumentProperties() + self.style = Style() + self.security = DocumentSecurity() + self.__optimized_write = optimized_write + self.__optimized_read = False + self.strings_table_builder = StringTableBuilder() + + if not optimized_write: + self.worksheets.append(Worksheet(self)) + + def _set_optimized_read(self): + self.__optimized_read = True + + def get_active_sheet(self): + """Returns the current active sheet.""" + return self.worksheets[self._active_sheet_index] + + def create_sheet(self, index = None): + """Create a worksheet (at an optional index). + + :param index: optional position at which the sheet will be inserted + :type index: int + + """ + + if self.__optimized_read: + raise ReadOnlyWorkbookException('Cannot create new sheet in a read-only workbook') + + if self.__optimized_write : + new_ws = DumpWorksheet(parent_workbook = self) + else: + new_ws = Worksheet(parent_workbook = self) + + self.add_sheet(worksheet = new_ws, index = index) + return new_ws + + def add_sheet(self, worksheet, index = None): + """Add an existing worksheet (at an optional index).""" + if index is None: + index = len(self.worksheets) + self.worksheets.insert(index, worksheet) + + def remove_sheet(self, worksheet): + """Remove a worksheet from this workbook.""" + self.worksheets.remove(worksheet) + + def get_sheet_by_name(self, name): + """Returns a worksheet by its name. + + Returns None if no worksheet has the name specified. + + :param name: the name of the worksheet to look for + :type name: string + + """ + requested_sheet = None + for sheet in self.worksheets: + if sheet.title == name: + requested_sheet = sheet + break + return requested_sheet + + def get_index(self, worksheet): + """Return the index of the worksheet.""" + return self.worksheets.index(worksheet) + + def get_sheet_names(self): + """Returns the list of the names of worksheets in the workbook. + + Names are returned in the worksheets order. + + :rtype: list of strings + + """ + return [s.title for s in self.worksheets] + + def create_named_range(self, name, worksheet, range): + """Create a new named_range on a worksheet""" + assert isinstance(worksheet, Worksheet) + named_range = NamedRange(name, [(worksheet, range)]) + self.add_named_range(named_range) + + def get_named_ranges(self): + """Return all named ranges""" + return self._named_ranges + + def add_named_range(self, named_range): + """Add an existing named_range to the list of named_ranges.""" + self._named_ranges.append(named_range) + + def get_named_range(self, name): + """Return the range specified by name.""" + requested_range = None + for named_range in self._named_ranges: + if named_range.name == name: + requested_range = named_range + break + return requested_range + + def remove_named_range(self, named_range): + """Remove a named_range from this workbook.""" + self._named_ranges.remove(named_range) + + def save(self, filename): + """ shortcut """ + if self.__optimized_write: + save_dump(self, filename) + else: + save_workbook(self, filename) diff --git a/tablib/packages/openpyxl/worksheet.py b/tablib/packages/openpyxl/worksheet.py new file mode 100644 index 0000000..bfc873c --- /dev/null +++ b/tablib/packages/openpyxl/worksheet.py @@ -0,0 +1,534 @@ +# file openpyxl/worksheet.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Worksheet is the 2nd-level container in Excel.""" + +# Python stdlib imports +import re + +# package imports +import openpyxl.cell +from openpyxl.cell import coordinate_from_string, \ + column_index_from_string, get_column_letter +from openpyxl.shared.exc import SheetTitleException, \ + InsufficientCoordinatesException, CellCoordinatesException, \ + NamedRangeException +from openpyxl.shared.password_hasher import hash_password +from openpyxl.style import Style, DEFAULTS as DEFAULTS_STYLE +from openpyxl.drawing import Drawing + +_DEFAULTS_STYLE_HASH = hash(DEFAULTS_STYLE) + +def flatten(results): + + rows = [] + + for row in results: + + cells = [] + + for cell in row: + + cells.append(cell.value) + + rows.append(tuple(cells)) + + return tuple(rows) + + +class Relationship(object): + """Represents many kinds of relationships.""" + # TODO: Use this object for workbook relationships as well as + # worksheet relationships + TYPES = { + 'hyperlink': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', + 'drawing':'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing', + #'worksheet': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet', + #'sharedStrings': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings', + #'styles': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles', + #'theme': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme', + } + + def __init__(self, rel_type): + if rel_type not in self.TYPES: + raise ValueError("Invalid relationship type %s" % rel_type) + self.type = self.TYPES[rel_type] + self.target = "" + self.target_mode = "" + self.id = "" + + +class PageSetup(object): + """Information about page layout for this sheet""" + pass + + +class HeaderFooter(object): + """Information about the header/footer for this sheet.""" + pass + + +class SheetView(object): + """Information about the visible portions of this sheet.""" + pass + + +class RowDimension(object): + """Information about the display properties of a row.""" + __slots__ = ('row_index', + 'height', + 'visible', + 'outline_level', + 'collapsed', + 'style_index',) + + def __init__(self, index = 0): + self.row_index = index + self.height = -1 + self.visible = True + self.outline_level = 0 + self.collapsed = False + self.style_index = None + + +class ColumnDimension(object): + """Information about the display properties of a column.""" + __slots__ = ('column_index', + 'width', + 'auto_size', + 'visible', + 'outline_level', + 'collapsed', + 'style_index',) + + def __init__(self, index = 'A'): + self.column_index = index + self.width = -1 + self.auto_size = False + self.visible = True + self.outline_level = 0 + self.collapsed = False + self.style_index = 0 + + +class PageMargins(object): + """Information about page margins for view/print layouts.""" + + def __init__(self): + self.left = self.right = 0.7 + self.top = self.bottom = 0.75 + self.header = self.footer = 0.3 + + +class SheetProtection(object): + """Information about protection of various aspects of a sheet.""" + + def __init__(self): + self.sheet = False + self.objects = False + self.scenarios = False + self.format_cells = False + self.format_columns = False + self.format_rows = False + self.insert_columns = False + self.insert_rows = False + self.insert_hyperlinks = False + self.delete_columns = False + self.delete_rows = False + self.select_locked_cells = False + self.sort = False + self.auto_filter = False + self.pivot_tables = False + self.select_unlocked_cells = False + self._password = '' + + def set_password(self, value = '', already_hashed = False): + """Set a password on this sheet.""" + if not already_hashed: + value = hash_password(value) + self._password = value + + def _set_raw_password(self, value): + """Set a password directly, forcing a hash step.""" + self.set_password(value, already_hashed = False) + + def _get_raw_password(self): + """Return the password value, regardless of hash.""" + return self._password + + password = property(_get_raw_password, _set_raw_password, + 'get/set the password (if already hashed, ' + 'use set_password() instead)') + + +class Worksheet(object): + """Represents a worksheet. + + Do not create worksheets yourself, + use :func:`openpyxl.workbook.Workbook.create_sheet` instead + + """ + BREAK_NONE = 0 + BREAK_ROW = 1 + BREAK_COLUMN = 2 + + SHEETSTATE_VISIBLE = 'visible' + SHEETSTATE_HIDDEN = 'hidden' + SHEETSTATE_VERYHIDDEN = 'veryHidden' + + def __init__(self, parent_workbook, title = 'Sheet'): + self._parent = parent_workbook + self._title = '' + if not title: + self.title = 'Sheet%d' % (1 + len(self._parent.worksheets)) + else: + self.title = title + self.row_dimensions = {} + self.column_dimensions = {} + self._cells = {} + self._styles = {} + self._charts = [] + self.relationships = [] + self.selected_cell = 'A1' + self.active_cell = 'A1' + self.sheet_state = self.SHEETSTATE_VISIBLE + self.page_setup = PageSetup() + self.page_margins = PageMargins() + self.header_footer = HeaderFooter() + self.sheet_view = SheetView() + self.protection = SheetProtection() + self.show_gridlines = True + self.print_gridlines = False + self.show_summary_below = True + self.show_summary_right = True + self.default_row_dimension = RowDimension() + self.default_column_dimension = ColumnDimension() + self._auto_filter = None + self._freeze_panes = None + + def __repr__(self): + return u'' % self.title + + def garbage_collect(self): + """Delete cells that are not storing a value.""" + delete_list = [coordinate for coordinate, cell in \ + self._cells.iteritems() if (cell.value in ('', None) and \ + hash(cell.style) == _DEFAULTS_STYLE_HASH)] + for coordinate in delete_list: + del self._cells[coordinate] + + def get_cell_collection(self): + """Return an unordered list of the cells in this worksheet.""" + return self._cells.values() + + def _set_title(self, value): + """Set a sheet title, ensuring it is valid.""" + bad_title_char_re = re.compile(r'[\\*?:/\[\]]') + if bad_title_char_re.search(value): + msg = 'Invalid character found in sheet title' + raise SheetTitleException(msg) + + # check if sheet_name already exists + # do this *before* length check + if self._parent.get_sheet_by_name(value): + # use name, but append with lowest possible integer + i = 1 + while self._parent.get_sheet_by_name('%s%d' % (value, i)): + i += 1 + value = '%s%d' % (value, i) + if len(value) > 31: + msg = 'Maximum 31 characters allowed in sheet title' + raise SheetTitleException(msg) + self._title = value + + def _get_title(self): + """Return the title for this sheet.""" + return self._title + + title = property(_get_title, _set_title, doc = + 'Get or set the title of the worksheet. ' + 'Limited to 31 characters, no special characters.') + + def _set_auto_filter(self, range): + # Normalize range to a str or None + if not range: + range = None + elif isinstance(range, str): + range = range.upper() + else: # Assume a range + range = range[0][0].address + ':' + range[-1][-1].address + self._auto_filter = range + + def _get_auto_filter(self): + return self._auto_filter + + auto_filter = property(_get_auto_filter, _set_auto_filter, doc = + 'get or set auto filtering on columns') + def _set_freeze_panes(self, topLeftCell): + if not topLeftCell: + topLeftCell = None + elif isinstance(topLeftCell, str): + topLeftCell = topLeftCell.upper() + else: # Assume a cell + topLeftCell = topLeftCell.address + if topLeftCell == 'A1': + topLeftCell = None + self._freeze_panes = topLeftCell + + def _get_freeze_panes(self): + return self._freeze_panes + + freeze_panes = property(_get_freeze_panes,_set_freeze_panes, doc = + "Get or set frozen panes") + + def cell(self, coordinate = None, row = None, column = None): + """Returns a cell object based on the given coordinates. + + Usage: cell(coodinate='A15') **or** cell(row=15, column=1) + + If `coordinates` are not given, then row *and* column must be given. + + Cells are kept in a dictionary which is empty at the worksheet + creation. Calling `cell` creates the cell in memory when they + are first accessed, to reduce memory usage. + + :param coordinate: coordinates of the cell (e.g. 'B12') + :type coordinate: string + + :param row: row index of the cell (e.g. 4) + :type row: int + + :param column: column index of the cell (e.g. 3) + :type column: int + + :raise: InsufficientCoordinatesException when coordinate or (row and column) are not given + + :rtype: :class:`openpyxl.cell.Cell` + + """ + if not coordinate: + if (row is None or column is None): + msg = "You have to provide a value either for " \ + "'coordinate' or for 'row' *and* 'column'" + raise InsufficientCoordinatesException(msg) + else: + coordinate = '%s%s' % (get_column_letter(column + 1), row + 1) + else: + coordinate = coordinate.replace('$', '') + + return self._get_cell(coordinate) + + def _get_cell(self, coordinate): + + if not coordinate in self._cells: + column, row = coordinate_from_string(coordinate) + new_cell = openpyxl.cell.Cell(self, column, row) + self._cells[coordinate] = new_cell + if column not in self.column_dimensions: + self.column_dimensions[column] = ColumnDimension(column) + if row not in self.row_dimensions: + self.row_dimensions[row] = RowDimension(row) + return self._cells[coordinate] + + def get_highest_row(self): + """Returns the maximum row index containing data + + :rtype: int + """ + if self.row_dimensions: + return max(self.row_dimensions.keys()) + else: + return 1 + + def get_highest_column(self): + """Get the largest value for column currently stored. + + :rtype: int + """ + if self.column_dimensions: + return max([column_index_from_string(column_index) + for column_index in self.column_dimensions]) + else: + return 1 + + def calculate_dimension(self): + """Return the minimum bounding range for all cells containing data.""" + return 'A1:%s%d' % (get_column_letter(self.get_highest_column()), + self.get_highest_row()) + + def range(self, range_string, row = 0, column = 0): + """Returns a 2D array of cells, with optional row and column offsets. + + :param range_string: cell range string or `named range` name + :type range_string: string + + :param row: number of rows to offset + :type row: int + + :param column: number of columns to offset + :type column: int + + :rtype: tuples of tuples of :class:`openpyxl.cell.Cell` + + """ + if ':' in range_string: + # R1C1 range + result = [] + min_range, max_range = range_string.split(':') + min_col, min_row = coordinate_from_string(min_range) + max_col, max_row = coordinate_from_string(max_range) + if column: + min_col = get_column_letter( + column_index_from_string(min_col) + column) + max_col = get_column_letter( + column_index_from_string(max_col) + column) + min_col = column_index_from_string(min_col) + max_col = column_index_from_string(max_col) + cache_cols = {} + for col in xrange(min_col, max_col + 1): + cache_cols[col] = get_column_letter(col) + rows = xrange(min_row + row, max_row + row + 1) + cols = xrange(min_col, max_col + 1) + for row in rows: + new_row = [] + for col in cols: + new_row.append(self.cell('%s%s' % (cache_cols[col], row))) + result.append(tuple(new_row)) + return tuple(result) + else: + try: + return self.cell(coordinate = range_string, row = row, + column = column) + except CellCoordinatesException: + pass + + # named range + named_range = self._parent.get_named_range(range_string) + if named_range is None: + msg = '%s is not a valid range name' % range_string + raise NamedRangeException(msg) + + result = [] + for destination in named_range.destinations: + + worksheet, cells_range = destination + + if worksheet is not self: + msg = 'Range %s is not defined on worksheet %s' % \ + (cells_range, self.title) + raise NamedRangeException(msg) + + content = self.range(cells_range) + + if isinstance(content, tuple): + for cells in content: + result.extend(cells) + else: + result.append(content) + + if len(result) == 1: + return result[0] + else: + return tuple(result) + + def get_style(self, coordinate): + """Return the style object for the specified cell.""" + if not coordinate in self._styles: + self._styles[coordinate] = Style() + return self._styles[coordinate] + + def create_relationship(self, rel_type): + """Add a relationship for this sheet.""" + rel = Relationship(rel_type) + self.relationships.append(rel) + rel_id = self.relationships.index(rel) + rel.id = 'rId' + str(rel_id + 1) + return self.relationships[rel_id] + + def add_chart(self, chart): + """ Add a chart to the sheet """ + + chart._sheet = self + self._charts.append(chart) + + def append(self, list_or_dict): + """Appends a group of values at the bottom of the current sheet. + + * If it's a list: all values are added in order, starting from the first column + * If it's a dict: values are assigned to the columns indicated by the keys (numbers or letters) + + :param list_or_dict: list or dict containing values to append + :type list_or_dict: list/tuple or dict + + Usage: + + * append(['This is A1', 'This is B1', 'This is C1']) + * **or** append({'A' : 'This is A1', 'C' : 'This is C1'}) + * **or** append({0 : 'This is A1', 2 : 'This is C1'}) + + :raise: TypeError when list_or_dict is neither a list/tuple nor a dict + + """ + + row_idx = len(self.row_dimensions) + + if isinstance(list_or_dict, (list, tuple)): + + for col_idx, content in enumerate(list_or_dict): + + self.cell(row = row_idx, column = col_idx).value = content + + elif isinstance(list_or_dict, dict): + + for col_idx, content in list_or_dict.iteritems(): + + if isinstance(col_idx, basestring): + col_idx = column_index_from_string(col_idx) - 1 + + self.cell(row = row_idx, column = col_idx).value = content + + else: + raise TypeError('list_or_dict must be a list or a dict') + + @property + def rows(self): + + return self.range(self.calculate_dimension()) + + @property + def columns(self): + + max_row = self.get_highest_row() + + cols = [] + + for col_idx in range(self.get_highest_column()): + col = get_column_letter(col_idx+1) + res = self.range('%s1:%s%d' % (col, col, max_row)) + cols.append(tuple([x[0] for x in res])) + + + return tuple(cols) + diff --git a/tablib/packages/openpyxl/writer/__init__.py b/tablib/packages/openpyxl/writer/__init__.py new file mode 100644 index 0000000..fff1f92 --- /dev/null +++ b/tablib/packages/openpyxl/writer/__init__.py @@ -0,0 +1,34 @@ +# file openpyxl/writer/__init__.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Imports for the openpyxl.writer namespace.""" + +# package imports +from openpyxl.writer import excel +from openpyxl.writer import strings +from openpyxl.writer import styles +from openpyxl.writer import theme +from openpyxl.writer import workbook +from openpyxl.writer import worksheet diff --git a/tablib/packages/openpyxl/writer/charts.py b/tablib/packages/openpyxl/writer/charts.py new file mode 100644 index 0000000..1d3dbd9 --- /dev/null +++ b/tablib/packages/openpyxl/writer/charts.py @@ -0,0 +1,261 @@ +# coding=UTF-8 +''' +Copyright (c) 2010 openpyxl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license: http://www.opensource.org/licenses/mit-license.php +@author: Eric Gazoni +''' + +from openpyxl.shared.xmltools import Element, SubElement, get_document_content +from openpyxl.chart import Chart, ErrorBar + +class ChartWriter(object): + + def __init__(self, chart): + self.chart = chart + + def write(self): + """ write a chart """ + + root = Element('c:chartSpace', + {'xmlns:c':"http://schemas.openxmlformats.org/drawingml/2006/chart", + 'xmlns:a':"http://schemas.openxmlformats.org/drawingml/2006/main", + 'xmlns:r':"http://schemas.openxmlformats.org/officeDocument/2006/relationships"}) + + SubElement(root, 'c:lang', {'val':self.chart.lang}) + self._write_chart(root) + self._write_print_settings(root) + self._write_shapes(root) + + return get_document_content(root) + + def _write_chart(self, root): + + chart = self.chart + + ch = SubElement(root, 'c:chart') + self._write_title(ch) + plot_area = SubElement(ch, 'c:plotArea') + layout = SubElement(plot_area, 'c:layout') + mlayout = SubElement(layout, 'c:manualLayout') + SubElement(mlayout, 'c:layoutTarget', {'val':'inner'}) + SubElement(mlayout, 'c:xMode', {'val':'edge'}) + SubElement(mlayout, 'c:yMode', {'val':'edge'}) + SubElement(mlayout, 'c:x', {'val':str(chart._get_margin_left())}) + SubElement(mlayout, 'c:y', {'val':str(chart._get_margin_top())}) + SubElement(mlayout, 'c:w', {'val':str(chart.width)}) + SubElement(mlayout, 'c:h', {'val':str(chart.height)}) + + if chart.type == Chart.SCATTER_CHART: + subchart = SubElement(plot_area, 'c:scatterChart') + SubElement(subchart, 'c:scatterStyle', {'val':str('lineMarker')}) + else: + if chart.type == Chart.BAR_CHART: + subchart = SubElement(plot_area, 'c:barChart') + SubElement(subchart, 'c:barDir', {'val':'col'}) + else: + subchart = SubElement(plot_area, 'c:lineChart') + + SubElement(subchart, 'c:grouping', {'val':chart.grouping}) + + self._write_series(subchart) + + SubElement(subchart, 'c:marker', {'val':'1'}) + SubElement(subchart, 'c:axId', {'val':str(chart.x_axis.id)}) + SubElement(subchart, 'c:axId', {'val':str(chart.y_axis.id)}) + + if chart.type == Chart.SCATTER_CHART: + self._write_axis(plot_area, chart.x_axis, 'c:valAx') + else: + self._write_axis(plot_area, chart.x_axis, 'c:catAx') + self._write_axis(plot_area, chart.y_axis, 'c:valAx') + + self._write_legend(ch) + + SubElement(ch, 'c:plotVisOnly', {'val':'1'}) + + def _write_title(self, chart): + if self.chart.title != '': + title = SubElement(chart, 'c:title') + tx = SubElement(title, 'c:tx') + rich = SubElement(tx, 'c:rich') + SubElement(rich, 'a:bodyPr') + SubElement(rich, 'a:lstStyle') + p = SubElement(rich, 'a:p') + pPr = SubElement(p, 'a:pPr') + SubElement(pPr, 'a:defRPr') + r = SubElement(p, 'a:r') + SubElement(r, 'a:rPr', {'lang':self.chart.lang}) + t = SubElement(r, 'a:t').text = self.chart.title + SubElement(title, 'c:layout') + + def _write_axis(self, plot_area, axis, label): + + ax = SubElement(plot_area, label) + SubElement(ax, 'c:axId', {'val':str(axis.id)}) + + scaling = SubElement(ax, 'c:scaling') + SubElement(scaling, 'c:orientation', {'val':axis.orientation}) + if label == 'c:valAx': + SubElement(scaling, 'c:max', {'val':str(axis.max)}) + SubElement(scaling, 'c:min', {'val':str(axis.min)}) + + SubElement(ax, 'c:axPos', {'val':axis.position}) + if label == 'c:valAx': + SubElement(ax, 'c:majorGridlines') + SubElement(ax, 'c:numFmt', {'formatCode':"General", 'sourceLinked':'1'}) + SubElement(ax, 'c:tickLblPos', {'val':axis.tick_label_position}) + SubElement(ax, 'c:crossAx', {'val':str(axis.cross)}) + SubElement(ax, 'c:crosses', {'val':axis.crosses}) + if axis.auto: + SubElement(ax, 'c:auto', {'val':'1'}) + if axis.label_align: + SubElement(ax, 'c:lblAlgn', {'val':axis.label_align}) + if axis.label_offset: + SubElement(ax, 'c:lblOffset', {'val':str(axis.label_offset)}) + if label == 'c:valAx': + if self.chart.type == Chart.SCATTER_CHART: + SubElement(ax, 'c:crossBetween', {'val':'midCat'}) + else: + SubElement(ax, 'c:crossBetween', {'val':'between'}) + SubElement(ax, 'c:majorUnit', {'val':str(axis.unit)}) + + def _write_series(self, subchart): + + for i, serie in enumerate(self.chart._series): + ser = SubElement(subchart, 'c:ser') + SubElement(ser, 'c:idx', {'val':str(i)}) + SubElement(ser, 'c:order', {'val':str(i)}) + + if serie.legend: + tx = SubElement(ser, 'c:tx') + self._write_serial(tx, serie.legend) + + if serie.color: + sppr = SubElement(ser, 'c:spPr') + if self.chart.type == Chart.BAR_CHART: + # fill color + fillc = SubElement(sppr, 'a:solidFill') + SubElement(fillc, 'a:srgbClr', {'val':serie.color}) + # edge color + ln = SubElement(sppr, 'a:ln') + fill = SubElement(ln, 'a:solidFill') + SubElement(fill, 'a:srgbClr', {'val':serie.color}) + + if serie.error_bar: + self._write_error_bar(ser, serie) + + marker = SubElement(ser, 'c:marker') + SubElement(marker, 'c:symbol', {'val':serie.marker}) + + if serie.labels: + cat = SubElement(ser, 'c:cat') + self._write_serial(cat, serie.labels) + + if self.chart.type == Chart.SCATTER_CHART: + if serie.xvalues: + xval = SubElement(ser, 'c:xVal') + self._write_serial(xval, serie.xvalues) + + yval = SubElement(ser, 'c:yVal') + self._write_serial(yval, serie.values) + else: + val = SubElement(ser, 'c:val') + self._write_serial(val, serie.values) + + def _write_serial(self, node, serie, literal=False): + + cache = serie._get_cache() + if isinstance(cache[0], basestring): + typ = 'str' + else: + typ = 'num' + + if not literal: + if typ == 'num': + ref = SubElement(node, 'c:numRef') + else: + ref = SubElement(node, 'c:strRef') + SubElement(ref, 'c:f').text = serie._get_ref() + if typ == 'num': + data = SubElement(ref, 'c:numCache') + else: + data = SubElement(ref, 'c:strCache') + else: + data = SubElement(node, 'c:numLit') + + if typ == 'num': + SubElement(data, 'c:formatCode').text = 'General' + if literal: + values = (1,) + else: + values = cache + + SubElement(data, 'c:ptCount', {'val':str(len(values))}) + for j, val in enumerate(values): + point = SubElement(data, 'c:pt', {'idx':str(j)}) + SubElement(point, 'c:v').text = str(val) + + def _write_error_bar(self, node, serie): + + flag = {ErrorBar.PLUS_MINUS:'both', + ErrorBar.PLUS:'plus', + ErrorBar.MINUS:'minus'} + + eb = SubElement(node, 'c:errBars') + SubElement(eb, 'c:errBarType', {'val':flag[serie.error_bar.type]}) + SubElement(eb, 'c:errValType', {'val':'cust'}) + + plus = SubElement(eb, 'c:plus') + self._write_serial(plus, serie.error_bar.values, + literal=(serie.error_bar.type==ErrorBar.MINUS)) + + minus = SubElement(eb, 'c:minus') + self._write_serial(minus, serie.error_bar.values, + literal=(serie.error_bar.type==ErrorBar.PLUS)) + + def _write_legend(self, chart): + + legend = SubElement(chart, 'c:legend') + SubElement(legend, 'c:legendPos', {'val':self.chart.legend.position}) + SubElement(legend, 'c:layout') + + def _write_print_settings(self, root): + + settings = SubElement(root, 'c:printSettings') + SubElement(settings, 'c:headerFooter') + margins = dict([(k, str(v)) for (k,v) in self.chart.print_margins.iteritems()]) + SubElement(settings, 'c:pageMargins', margins) + SubElement(settings, 'c:pageSetup') + + def _write_shapes(self, root): + + if self.chart._shapes: + SubElement(root, 'c:userShapes', {'r:id':'rId1'}) + + def write_rels(self, drawing_id): + + root = Element('Relationships', {'xmlns' : 'http://schemas.openxmlformats.org/package/2006/relationships'}) + attrs = {'Id' : 'rId1', + 'Type' : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUserShapes', + 'Target' : '../drawings/drawing%s.xml' % drawing_id } + SubElement(root, 'Relationship', attrs) + return get_document_content(root) diff --git a/tablib/packages/openpyxl/writer/drawings.py b/tablib/packages/openpyxl/writer/drawings.py new file mode 100644 index 0000000..e6e5b30 --- /dev/null +++ b/tablib/packages/openpyxl/writer/drawings.py @@ -0,0 +1,192 @@ +# coding=UTF-8 +''' +Copyright (c) 2010 openpyxl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license: http://www.opensource.org/licenses/mit-license.php +@author: Eric Gazoni +''' + +from openpyxl.shared.xmltools import Element, SubElement, get_document_content + + +class DrawingWriter(object): + """ one main drawing file per sheet """ + + def __init__(self, sheet): + self._sheet = sheet + + def write(self): + """ write drawings for one sheet in one file """ + + root = Element('xdr:wsDr', + {'xmlns:xdr' : "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing", + 'xmlns:a' : "http://schemas.openxmlformats.org/drawingml/2006/main"}) + + for i, chart in enumerate(self._sheet._charts): + + drawing = chart.drawing + +# anchor = SubElement(root, 'xdr:twoCellAnchor') +# (start_row, start_col), (end_row, end_col) = drawing.coordinates +# # anchor coordinates +# _from = SubElement(anchor, 'xdr:from') +# x = SubElement(_from, 'xdr:col').text = str(start_col) +# x = SubElement(_from, 'xdr:colOff').text = '0' +# x = SubElement(_from, 'xdr:row').text = str(start_row) +# x = SubElement(_from, 'xdr:rowOff').text = '0' + +# _to = SubElement(anchor, 'xdr:to') +# x = SubElement(_to, 'xdr:col').text = str(end_col) +# x = SubElement(_to, 'xdr:colOff').text = '0' +# x = SubElement(_to, 'xdr:row').text = str(end_row) +# x = SubElement(_to, 'xdr:rowOff').text = '0' + + # we only support absolute anchor atm (TODO: oneCellAnchor, twoCellAnchor + x, y, w, h = drawing.get_emu_dimensions() + anchor = SubElement(root, 'xdr:absoluteAnchor') + SubElement(anchor, 'xdr:pos', {'x':str(x), 'y':str(y)}) + SubElement(anchor, 'xdr:ext', {'cx':str(w), 'cy':str(h)}) + + # graph frame + frame = SubElement(anchor, 'xdr:graphicFrame', {'macro':''}) + + name = SubElement(frame, 'xdr:nvGraphicFramePr') + SubElement(name, 'xdr:cNvPr', {'id':'%s' % i, 'name':'Graphique %s' % i}) + SubElement(name, 'xdr:cNvGraphicFramePr') + + frm = SubElement(frame, 'xdr:xfrm') + # no transformation + SubElement(frm, 'a:off', {'x':'0', 'y':'0'}) + SubElement(frm, 'a:ext', {'cx':'0', 'cy':'0'}) + + graph = SubElement(frame, 'a:graphic') + data = SubElement(graph, 'a:graphicData', + {'uri':'http://schemas.openxmlformats.org/drawingml/2006/chart'}) + SubElement(data, 'c:chart', + { 'xmlns:c':'http://schemas.openxmlformats.org/drawingml/2006/chart', + 'xmlns:r':'http://schemas.openxmlformats.org/officeDocument/2006/relationships', + 'r:id':'rId%s' % (i + 1)}) + + SubElement(anchor, 'xdr:clientData') + + return get_document_content(root) + + def write_rels(self, chart_id): + + root = Element('Relationships', + {'xmlns' : 'http://schemas.openxmlformats.org/package/2006/relationships'}) + for i, chart in enumerate(self._sheet._charts): + attrs = {'Id' : 'rId%s' % (i + 1), + 'Type' : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart', + 'Target' : '../charts/chart%s.xml' % (chart_id + i) } + SubElement(root, 'Relationship', attrs) + return get_document_content(root) + +class ShapeWriter(object): + """ one file per shape """ + + schema = "http://schemas.openxmlformats.org/drawingml/2006/main" + + def __init__(self, shapes): + + self._shapes = shapes + + def write(self, shape_id): + + root = Element('c:userShapes', {'xmlns:c' : 'http://schemas.openxmlformats.org/drawingml/2006/chart'}) + + for shape in self._shapes: + anchor = SubElement(root, 'cdr:relSizeAnchor', + {'xmlns:cdr' : "http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"}) + + xstart, ystart, xend, yend = shape.get_coordinates() + + _from = SubElement(anchor, 'cdr:from') + SubElement(_from, 'cdr:x').text = str(xstart) + SubElement(_from, 'cdr:y').text = str(ystart) + + _to = SubElement(anchor, 'cdr:to') + SubElement(_to, 'cdr:x').text = str(xend) + SubElement(_to, 'cdr:y').text = str(yend) + + sp = SubElement(anchor, 'cdr:sp', {'macro':'', 'textlink':''}) + nvspr = SubElement(sp, 'cdr:nvSpPr') + SubElement(nvspr, 'cdr:cNvPr', {'id':str(shape_id), 'name':'shape %s' % shape_id}) + SubElement(nvspr, 'cdr:cNvSpPr') + + sppr = SubElement(sp, 'cdr:spPr') + frm = SubElement(sppr, 'a:xfrm', {'xmlns:a':self.schema}) + # no transformation + SubElement(frm, 'a:off', {'x':'0', 'y':'0'}) + SubElement(frm, 'a:ext', {'cx':'0', 'cy':'0'}) + + prstgeom = SubElement(sppr, 'a:prstGeom', {'xmlns:a':self.schema, 'prst':str(shape.style)}) + SubElement(prstgeom, 'a:avLst') + + fill = SubElement(sppr, 'a:solidFill', {'xmlns:a':self.schema}) + SubElement(fill, 'a:srgbClr', {'val':shape.color}) + + border = SubElement(sppr, 'a:ln', {'xmlns:a':self.schema, 'w':str(shape._border_width)}) + sf = SubElement(border, 'a:solidFill') + SubElement(sf, 'a:srgbClr', {'val':shape.border_color}) + + self._write_style(sp) + self._write_text(sp, shape) + + shape_id += 1 + + return get_document_content(root) + + def _write_text(self, node, shape): + """ write text in the shape """ + + tx_body = SubElement(node, 'cdr:txBody') + SubElement(tx_body, 'a:bodyPr', {'xmlns:a':self.schema, 'vertOverflow':'clip'}) + SubElement(tx_body, 'a:lstStyle', + {'xmlns:a':self.schema}) + p = SubElement(tx_body, 'a:p', {'xmlns:a':self.schema}) + if shape.text: + r = SubElement(p, 'a:r') + rpr = SubElement(r, 'a:rPr', {'lang':'en-US'}) + fill = SubElement(rpr, 'a:solidFill') + SubElement(fill, 'a:srgbClr', {'val':shape.text_color}) + + SubElement(r, 'a:t').text = shape.text + else: + SubElement(p, 'a:endParaRPr', {'lang':'en-US'}) + + def _write_style(self, node): + """ write style theme """ + + style = SubElement(node, 'cdr:style') + + ln_ref = SubElement(style, 'a:lnRef', {'xmlns:a':self.schema, 'idx':'2'}) + scheme_clr = SubElement(ln_ref, 'a:schemeClr', {'val':'accent1'}) + SubElement(scheme_clr, 'a:shade', {'val':'50000'}) + + fill_ref = SubElement(style, 'a:fillRef', {'xmlns:a':self.schema, 'idx':'1'}) + SubElement(fill_ref, 'a:schemeClr', {'val':'accent1'}) + + effect_ref = SubElement(style, 'a:effectRef', {'xmlns:a':self.schema, 'idx':'0'}) + SubElement(effect_ref, 'a:schemeClr', {'val':'accent1'}) + + font_ref = SubElement(style, 'a:fontRef', {'xmlns:a':self.schema, 'idx':'minor'}) + SubElement(font_ref, 'a:schemeClr', {'val':'lt1'}) diff --git a/tablib/packages/openpyxl/writer/dump_worksheet.py b/tablib/packages/openpyxl/writer/dump_worksheet.py new file mode 100644 index 0000000..da4ef56 --- /dev/null +++ b/tablib/packages/openpyxl/writer/dump_worksheet.py @@ -0,0 +1,256 @@ +# file openpyxl/writer/straight_worksheet.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Write worksheets to xml representations in an optimized way""" + +import datetime +import os + +from openpyxl.cell import column_index_from_string, get_column_letter, Cell +from openpyxl.worksheet import Worksheet +from openpyxl.shared.xmltools import XMLGenerator, get_document_content, \ + start_tag, end_tag, tag +from openpyxl.shared.date_time import SharedDate +from openpyxl.shared.ooxml import MAX_COLUMN, MAX_ROW +from tempfile import NamedTemporaryFile +from openpyxl.writer.excel import ExcelWriter +from openpyxl.writer.strings import write_string_table +from openpyxl.writer.styles import StyleWriter +from openpyxl.style import Style, NumberFormat + +from openpyxl.shared.ooxml import ARC_SHARED_STRINGS, ARC_CONTENT_TYPES, \ + ARC_ROOT_RELS, ARC_WORKBOOK_RELS, ARC_APP, ARC_CORE, ARC_THEME, \ + ARC_STYLE, ARC_WORKBOOK, \ + PACKAGE_WORKSHEETS, PACKAGE_DRAWINGS, PACKAGE_CHARTS + +STYLES = {'datetime' : {'type':Cell.TYPE_NUMERIC, + 'style':'1'}, + 'string':{'type':Cell.TYPE_STRING, + 'style':'0'}, + 'numeric':{'type':Cell.TYPE_NUMERIC, + 'style':'0'}, + 'formula':{'type':Cell.TYPE_FORMULA, + 'style':'0'}, + 'boolean':{'type':Cell.TYPE_BOOL, + 'style':'0'}, + } + +DATETIME_STYLE = Style() +DATETIME_STYLE.number_format.format_code = NumberFormat.FORMAT_DATE_YYYYMMDD2 +BOUNDING_BOX_PLACEHOLDER = 'A1:%s%d' % (get_column_letter(MAX_COLUMN), MAX_ROW) + +class DumpWorksheet(Worksheet): + + """ + .. warning:: + + You shouldn't initialize this yourself, use :class:`openpyxl.workbook.Workbook` constructor instead, + with `optimized_write = True`. + """ + + def __init__(self, parent_workbook): + + Worksheet.__init__(self, parent_workbook) + + self._max_col = 0 + self._max_row = 0 + self._parent = parent_workbook + self._fileobj_header = NamedTemporaryFile(mode='r+', prefix='openpyxl.', suffix='.header', delete=False) + self._fileobj_content = NamedTemporaryFile(mode='r+', prefix='openpyxl.', suffix='.content', delete=False) + self._fileobj = NamedTemporaryFile(mode='w', prefix='openpyxl.', delete=False) + self.doc = XMLGenerator(self._fileobj_content, 'utf-8') + self.header = XMLGenerator(self._fileobj_header, 'utf-8') + self.title = 'Sheet' + + self._shared_date = SharedDate() + self._string_builder = self._parent.strings_table_builder + + @property + def filename(self): + return self._fileobj.name + + def write_header(self): + + doc = self.header + + start_tag(doc, 'worksheet', + {'xml:space': 'preserve', + 'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', + 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}) + start_tag(doc, 'sheetPr') + tag(doc, 'outlinePr', + {'summaryBelow': '1', + 'summaryRight': '1'}) + end_tag(doc, 'sheetPr') + tag(doc, 'dimension', {'ref': 'A1:%s' % (self.get_dimensions())}) + start_tag(doc, 'sheetViews') + start_tag(doc, 'sheetView', {'workbookViewId': '0'}) + tag(doc, 'selection', {'activeCell': 'A1', + 'sqref': 'A1'}) + end_tag(doc, 'sheetView') + end_tag(doc, 'sheetViews') + tag(doc, 'sheetFormatPr', {'defaultRowHeight': '15'}) + start_tag(doc, 'sheetData') + + def close(self): + + self._close_content() + self._close_header() + + self._write_fileobj(self._fileobj_header) + self._write_fileobj(self._fileobj_content) + + self._fileobj.close() + + def _write_fileobj(self, fobj): + + fobj.flush() + fobj.seek(0) + + while True: + chunk = fobj.read(4096) + if not chunk: + break + self._fileobj.write(chunk) + + fobj.close() + os.remove(fobj.name) + + self._fileobj.flush() + + def _close_header(self): + + doc = self.header + #doc.endDocument() + + def _close_content(self): + + doc = self.doc + end_tag(doc, 'sheetData') + + end_tag(doc, 'worksheet') + #doc.endDocument() + + def get_dimensions(self): + + if not self._max_col or not self._max_row: + return 'A1' + else: + return '%s%d' % (get_column_letter(self._max_col), (self._max_row)) + + def append(self, row): + + """ + :param row: iterable containing values to append + :type row: iterable + """ + + doc = self.doc + + self._max_row += 1 + span = len(row) + self._max_col = max(self._max_col, span) + + row_idx = self._max_row + + attrs = {'r': '%d' % row_idx, + 'spans': '1:%d' % span} + + start_tag(doc, 'row', attrs) + + for col_idx, cell in enumerate(row): + + if cell is None: + continue + + coordinate = '%s%d' % (get_column_letter(col_idx+1), row_idx) + attributes = {'r': coordinate} + + if isinstance(cell, bool): + dtype = 'boolean' + elif isinstance(cell, (int, float)): + dtype = 'numeric' + elif isinstance(cell, (datetime.datetime, datetime.date)): + dtype = 'datetime' + cell = self._shared_date.datetime_to_julian(cell) + attributes['s'] = STYLES[dtype]['style'] + elif cell and cell[0] == '=': + dtype = 'formula' + else: + dtype = 'string' + cell = self._string_builder.add(cell) + + attributes['t'] = STYLES[dtype]['type'] + + start_tag(doc, 'c', attributes) + + if dtype == 'formula': + tag(doc, 'f', body = '%s' % cell[1:]) + tag(doc, 'v') + else: + tag(doc, 'v', body = '%s' % cell) + + end_tag(doc, 'c') + + + end_tag(doc, 'row') + + +def save_dump(workbook, filename): + + writer = ExcelDumpWriter(workbook) + writer.save(filename) + return True + +class ExcelDumpWriter(ExcelWriter): + + def __init__(self, workbook): + + self.workbook = workbook + self.style_writer = StyleDumpWriter(workbook) + self.style_writer._style_list.append(DATETIME_STYLE) + + def _write_string_table(self, archive): + + shared_string_table = self.workbook.strings_table_builder.get_table() + archive.writestr(ARC_SHARED_STRINGS, + write_string_table(shared_string_table)) + + return shared_string_table + + def _write_worksheets(self, archive, shared_string_table, style_writer): + + for i, sheet in enumerate(self.workbook.worksheets): + sheet.write_header() + sheet.close() + archive.write(sheet.filename, PACKAGE_WORKSHEETS + '/sheet%d.xml' % (i + 1)) + os.remove(sheet.filename) + + +class StyleDumpWriter(StyleWriter): + + def _get_style_list(self, workbook): + return [] + diff --git a/tablib/packages/openpyxl/writer/excel.py b/tablib/packages/openpyxl/writer/excel.py new file mode 100644 index 0000000..192716e --- /dev/null +++ b/tablib/packages/openpyxl/writer/excel.py @@ -0,0 +1,156 @@ +# file openpyxl/writer/excel.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Write a .xlsx file.""" + +# Python stdlib imports +from zipfile import ZipFile, ZIP_DEFLATED +from StringIO import StringIO + +# package imports +from openpyxl.shared.ooxml import ARC_SHARED_STRINGS, ARC_CONTENT_TYPES, \ + ARC_ROOT_RELS, ARC_WORKBOOK_RELS, ARC_APP, ARC_CORE, ARC_THEME, \ + ARC_STYLE, ARC_WORKBOOK, \ + PACKAGE_WORKSHEETS, PACKAGE_DRAWINGS, PACKAGE_CHARTS +from openpyxl.writer.strings import create_string_table, write_string_table +from openpyxl.writer.workbook import write_content_types, write_root_rels, \ + write_workbook_rels, write_properties_app, write_properties_core, \ + write_workbook +from openpyxl.writer.theme import write_theme +from openpyxl.writer.styles import StyleWriter +from openpyxl.writer.drawings import DrawingWriter, ShapeWriter +from openpyxl.writer.charts import ChartWriter +from openpyxl.writer.worksheet import write_worksheet, write_worksheet_rels + + +class ExcelWriter(object): + """Write a workbook object to an Excel file.""" + + def __init__(self, workbook): + self.workbook = workbook + self.style_writer = StyleWriter(self.workbook) + + def write_data(self, archive): + """Write the various xml files into the zip archive.""" + # cleanup all worksheets + shared_string_table = self._write_string_table(archive) + + archive.writestr(ARC_CONTENT_TYPES, write_content_types(self.workbook)) + archive.writestr(ARC_ROOT_RELS, write_root_rels(self.workbook)) + archive.writestr(ARC_WORKBOOK_RELS, write_workbook_rels(self.workbook)) + archive.writestr(ARC_APP, write_properties_app(self.workbook)) + archive.writestr(ARC_CORE, + write_properties_core(self.workbook.properties)) + archive.writestr(ARC_THEME, write_theme()) + archive.writestr(ARC_STYLE, self.style_writer.write_table()) + archive.writestr(ARC_WORKBOOK, write_workbook(self.workbook)) + + self._write_worksheets(archive, shared_string_table, self.style_writer) + + def _write_string_table(self, archive): + + for ws in self.workbook.worksheets: + ws.garbage_collect() + shared_string_table = create_string_table(self.workbook) + archive.writestr(ARC_SHARED_STRINGS, + write_string_table(shared_string_table)) + + return shared_string_table + + def _write_worksheets(self, archive, shared_string_table, style_writer): + + drawing_id = 1 + chart_id = 1 + shape_id = 1 + + for i, sheet in enumerate(self.workbook.worksheets): + archive.writestr(PACKAGE_WORKSHEETS + '/sheet%d.xml' % (i + 1), + write_worksheet(sheet, shared_string_table, + style_writer.get_style_by_hash())) + if sheet._charts or sheet.relationships: + archive.writestr(PACKAGE_WORKSHEETS + + '/_rels/sheet%d.xml.rels' % (i + 1), + write_worksheet_rels(sheet, drawing_id)) + if sheet._charts: + dw = DrawingWriter(sheet) + archive.writestr(PACKAGE_DRAWINGS + '/drawing%d.xml' % drawing_id, + dw.write()) + archive.writestr(PACKAGE_DRAWINGS + '/_rels/drawing%d.xml.rels' % drawing_id, + dw.write_rels(chart_id)) + drawing_id += 1 + + for chart in sheet._charts: + cw = ChartWriter(chart) + archive.writestr(PACKAGE_CHARTS + '/chart%d.xml' % chart_id, + cw.write()) + + if chart._shapes: + archive.writestr(PACKAGE_CHARTS + '/_rels/chart%d.xml.rels' % chart_id, + cw.write_rels(drawing_id)) + sw = ShapeWriter(chart._shapes) + archive.writestr(PACKAGE_DRAWINGS + '/drawing%d.xml' % drawing_id, + sw.write(shape_id)) + shape_id += len(chart._shapes) + drawing_id += 1 + + chart_id += 1 + + + def save(self, filename): + """Write data into the archive.""" + archive = ZipFile(filename, 'w', ZIP_DEFLATED) + self.write_data(archive) + archive.close() + + +def save_workbook(workbook, filename): + """Save the given workbook on the filesystem under the name filename. + + :param workbook: the workbook to save + :type workbook: :class:`openpyxl.workbook.Workbook` + + :param filename: the path to which save the workbook + :type filename: string + + :rtype: bool + + """ + writer = ExcelWriter(workbook) + writer.save(filename) + return True + + +def save_virtual_workbook(workbook): + """Return an in-memory workbook, suitable for a Django response.""" + writer = ExcelWriter(workbook) + temp_buffer = StringIO() + try: + archive = ZipFile(temp_buffer, 'w', ZIP_DEFLATED) + writer.write_data(archive) + finally: + archive.close() + virtual_workbook = temp_buffer.getvalue() + temp_buffer.close() + return virtual_workbook diff --git a/tablib/packages/openpyxl/writer/strings.py b/tablib/packages/openpyxl/writer/strings.py new file mode 100644 index 0000000..245c48b --- /dev/null +++ b/tablib/packages/openpyxl/writer/strings.py @@ -0,0 +1,86 @@ +# file openpyxl/writer/strings.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Write the shared string table.""" + +# Python stdlib imports +from StringIO import StringIO + +# package imports +from openpyxl.shared.xmltools import start_tag, end_tag, tag, XMLGenerator + + +def create_string_table(workbook): + """Compile the string table for a workbook.""" + strings = set() + for sheet in workbook.worksheets: + for cell in sheet.get_cell_collection(): + if cell.data_type == cell.TYPE_STRING and cell._value is not None: + strings.add(cell.value) + return dict((key, i) for i, key in enumerate(strings)) + + +def write_string_table(string_table): + """Write the string table xml.""" + temp_buffer = StringIO() + doc = XMLGenerator(temp_buffer, 'utf-8') + start_tag(doc, 'sst', {'xmlns': + 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', + 'uniqueCount': '%d' % len(string_table)}) + strings_to_write = sorted(string_table.iteritems(), + key=lambda pair: pair[1]) + for key in [pair[0] for pair in strings_to_write]: + start_tag(doc, 'si') + if key.strip() != key: + attr = {'xml:space': 'preserve'} + else: + attr = {} + tag(doc, 't', attr, key) + end_tag(doc, 'si') + end_tag(doc, 'sst') + string_table_xml = temp_buffer.getvalue() + temp_buffer.close() + return string_table_xml + +class StringTableBuilder(object): + + def __init__(self): + + self.counter = 0 + self.dct = {} + + def add(self, key): + + key = key.strip() + try: + return self.dct[key] + except KeyError: + res = self.dct[key] = self.counter + self.counter += 1 + return res + + def get_table(self): + + return self.dct diff --git a/tablib/packages/openpyxl/writer/styles.py b/tablib/packages/openpyxl/writer/styles.py new file mode 100644 index 0000000..7bf91dd --- /dev/null +++ b/tablib/packages/openpyxl/writer/styles.py @@ -0,0 +1,256 @@ +# file openpyxl/writer/styles.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Write the shared style table.""" + +# package imports +from openpyxl.shared.xmltools import Element, SubElement +from openpyxl.shared.xmltools import get_document_content +from openpyxl import style + +class StyleWriter(object): + + def __init__(self, workbook): + self._style_list = self._get_style_list(workbook) + self._root = Element('styleSheet', + {'xmlns':'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}) + + def _get_style_list(self, workbook): + crc = {} + for worksheet in workbook.worksheets: + for style in worksheet._styles.values(): + crc[hash(style)] = style + self.style_table = dict([(style, i+1) \ + for i, style in enumerate(crc.values())]) + sorted_styles = sorted(self.style_table.iteritems(), \ + key = lambda pair:pair[1]) + return [s[0] for s in sorted_styles] + + def get_style_by_hash(self): + return dict([(hash(style), id) \ + for style, id in self.style_table.iteritems()]) + + def write_table(self): + number_format_table = self._write_number_formats() + fonts_table = self._write_fonts() + fills_table = self._write_fills() + borders_table = self._write_borders() + self._write_cell_style_xfs() + self._write_cell_xfs(number_format_table, fonts_table, fills_table, borders_table) + self._write_cell_style() + self._write_dxfs() + self._write_table_styles() + + return get_document_content(xml_node=self._root) + + def _write_fonts(self): + """ add fonts part to root + return {font.crc => index} + """ + + fonts = SubElement(self._root, 'fonts') + + # default + font_node = SubElement(fonts, 'font') + SubElement(font_node, 'sz', {'val':'11'}) + SubElement(font_node, 'color', {'theme':'1'}) + SubElement(font_node, 'name', {'val':'Calibri'}) + SubElement(font_node, 'family', {'val':'2'}) + SubElement(font_node, 'scheme', {'val':'minor'}) + + # others + table = {} + index = 1 + for st in self._style_list: + if hash(st.font) != hash(style.DEFAULTS.font) and hash(st.font) not in table: + table[hash(st.font)] = str(index) + font_node = SubElement(fonts, 'font') + SubElement(font_node, 'sz', {'val':str(st.font.size)}) + SubElement(font_node, 'color', {'rgb':str(st.font.color.index)}) + SubElement(font_node, 'name', {'val':st.font.name}) + SubElement(font_node, 'family', {'val':'2'}) + SubElement(font_node, 'scheme', {'val':'minor'}) + if st.font.bold: + SubElement(font_node, 'b') + if st.font.italic: + SubElement(font_node, 'i') + index += 1 + + fonts.attrib["count"] = str(index) + return table + + def _write_fills(self): + fills = SubElement(self._root, 'fills', {'count':'2'}) + fill = SubElement(fills, 'fill') + SubElement(fill, 'patternFill', {'patternType':'none'}) + fill = SubElement(fills, 'fill') + SubElement(fill, 'patternFill', {'patternType':'gray125'}) + + table = {} + index = 2 + for st in self._style_list: + if hash(st.fill) != hash(style.DEFAULTS.fill) and hash(st.fill) not in table: + table[hash(st.fill)] = str(index) + fill = SubElement(fills, 'fill') + if hash(st.fill.fill_type) != hash(style.DEFAULTS.fill.fill_type): + node = SubElement(fill,'patternFill', {'patternType':st.fill.fill_type}) + if hash(st.fill.start_color) != hash(style.DEFAULTS.fill.start_color): + + SubElement(node, 'fgColor', {'rgb':str(st.fill.start_color.index)}) + if hash(st.fill.end_color) != hash(style.DEFAULTS.fill.end_color): + SubElement(node, 'bgColor', {'rgb':str(st.fill.start_color.index)}) + index += 1 + + fills.attrib["count"] = str(index) + return table + + def _write_borders(self): + borders = SubElement(self._root, 'borders') + + # default + border = SubElement(borders, 'border') + SubElement(border, 'left') + SubElement(border, 'right') + SubElement(border, 'top') + SubElement(border, 'bottom') + SubElement(border, 'diagonal') + + # others + table = {} + index = 1 + for st in self._style_list: + if hash(st.borders) != hash(style.DEFAULTS.borders) and hash(st.borders) not in table: + table[hash(st.borders)] = str(index) + border = SubElement(borders, 'border') + # caution: respect this order + for side in ('left','right','top','bottom','diagonal'): + obj = getattr(st.borders, side) + node = SubElement(border, side, {'style':obj.border_style}) + SubElement(node, 'color', {'rgb':str(obj.color.index)}) + index += 1 + + borders.attrib["count"] = str(index) + return table + + def _write_cell_style_xfs(self): + cell_style_xfs = SubElement(self._root, 'cellStyleXfs', {'count':'1'}) + xf = SubElement(cell_style_xfs, 'xf', + {'numFmtId':"0", 'fontId':"0", 'fillId':"0", 'borderId':"0"}) + + def _write_cell_xfs(self, number_format_table, fonts_table, fills_table, borders_table): + """ write styles combinations based on ids found in tables """ + + # writing the cellXfs + cell_xfs = SubElement(self._root, 'cellXfs', + {'count':'%d' % (len(self._style_list) + 1)}) + + # default + def _get_default_vals(): + return dict(numFmtId='0', fontId='0', fillId='0', + xfId='0', borderId='0') + + SubElement(cell_xfs, 'xf', _get_default_vals()) + + for st in self._style_list: + vals = _get_default_vals() + + if hash(st.font) != hash(style.DEFAULTS.font): + vals['fontId'] = fonts_table[hash(st.font)] + vals['applyFont'] = '1' + + if hash(st.borders) != hash(style.DEFAULTS.borders): + vals['borderId'] = borders_table[hash(st.borders)] + vals['applyBorder'] = '1' + + if hash(st.fill) != hash(style.DEFAULTS.fill): + vals['fillId'] = fills_table[hash(st.fill)] + vals['applyFillId'] = '1' + + if st.number_format != style.DEFAULTS.number_format: + vals['numFmtId'] = '%d' % number_format_table[st.number_format] + vals['applyNumberFormat'] = '1' + + if hash(st.alignment) != hash(style.DEFAULTS.alignment): + vals['applyAlignment'] = '1' + + node = SubElement(cell_xfs, 'xf', vals) + + if hash(st.alignment) != hash(style.DEFAULTS.alignment): + alignments = {} + + for align_attr in ['horizontal','vertical']: + if hash(getattr(st.alignment, align_attr)) != hash(getattr(style.DEFAULTS.alignment, align_attr)): + alignments[align_attr] = getattr(st.alignment, align_attr) + + SubElement(node, 'alignment', alignments) + + + def _write_cell_style(self): + cell_styles = SubElement(self._root, 'cellStyles', {'count':'1'}) + cell_style = SubElement(cell_styles, 'cellStyle', + {'name':"Normal", 'xfId':"0", 'builtinId':"0"}) + + def _write_dxfs(self): + dxfs = SubElement(self._root, 'dxfs', {'count':'0'}) + + def _write_table_styles(self): + + table_styles = SubElement(self._root, 'tableStyles', + {'count':'0', 'defaultTableStyle':'TableStyleMedium9', + 'defaultPivotStyle':'PivotStyleLight16'}) + + def _write_number_formats(self): + + number_format_table = {} + + number_format_list = [] + exceptions_list = [] + num_fmt_id = 165 # start at a greatly higher value as any builtin can go + num_fmt_offset = 0 + + for style in self._style_list: + + if not style.number_format in number_format_list : + number_format_list.append(style.number_format) + + for number_format in number_format_list: + + if number_format.is_builtin(): + btin = number_format.builtin_format_id(number_format.format_code) + number_format_table[number_format] = btin + else: + number_format_table[number_format] = num_fmt_id + num_fmt_offset + num_fmt_offset += 1 + exceptions_list.append(number_format) + + num_fmts = SubElement(self._root, 'numFmts', + {'count':'%d' % len(exceptions_list)}) + + for number_format in exceptions_list : + SubElement(num_fmts, 'numFmt', + {'numFmtId':'%d' % number_format_table[number_format], + 'formatCode':'%s' % number_format.format_code}) + + return number_format_table diff --git a/tablib/packages/openpyxl/writer/theme.py b/tablib/packages/openpyxl/writer/theme.py new file mode 100644 index 0000000..4b1723b --- /dev/null +++ b/tablib/packages/openpyxl/writer/theme.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +# file openpyxl/writer/theme.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Write the theme xml based on a fixed string.""" + +# package imports +from openpyxl.shared.xmltools import fromstring, get_document_content + + +def write_theme(): + """Write the theme xml.""" + xml_node = fromstring( + '\n' + + '' + '' + + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '') + return get_document_content(xml_node) diff --git a/tablib/packages/openpyxl/writer/workbook.py b/tablib/packages/openpyxl/writer/workbook.py new file mode 100644 index 0000000..5d05b31 --- /dev/null +++ b/tablib/packages/openpyxl/writer/workbook.py @@ -0,0 +1,204 @@ +# file openpyxl/writer/workbook.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Write the workbook global settings to the archive.""" + +# package imports +from openpyxl.shared.xmltools import Element, SubElement +from openpyxl.cell import absolute_coordinate +from openpyxl.shared.xmltools import get_document_content +from openpyxl.shared.ooxml import NAMESPACES, ARC_CORE, ARC_WORKBOOK, \ + ARC_APP, ARC_THEME, ARC_STYLE, ARC_SHARED_STRINGS +from openpyxl.shared.date_time import datetime_to_W3CDTF + + +def write_properties_core(properties): + """Write the core properties to xml.""" + root = Element('cp:coreProperties', {'xmlns:cp': NAMESPACES['cp'], + 'xmlns:xsi': NAMESPACES['xsi'], 'xmlns:dc': NAMESPACES['dc'], + 'xmlns:dcterms': NAMESPACES['dcterms'], + 'xmlns:dcmitype': NAMESPACES['dcmitype'], }) + SubElement(root, 'dc:creator').text = properties.creator + SubElement(root, 'cp:lastModifiedBy').text = properties.last_modified_by + SubElement(root, 'dcterms:created', \ + {'xsi:type': 'dcterms:W3CDTF'}).text = \ + datetime_to_W3CDTF(properties.created) + SubElement(root, 'dcterms:modified', + {'xsi:type': 'dcterms:W3CDTF'}).text = \ + datetime_to_W3CDTF(properties.modified) + return get_document_content(root) + + +def write_content_types(workbook): + """Write the content-types xml.""" + root = Element('Types', {'xmlns': 'http://schemas.openxmlformats.org/package/2006/content-types'}) + SubElement(root, 'Override', {'PartName': '/' + ARC_THEME, 'ContentType': 'application/vnd.openxmlformats-officedocument.theme+xml'}) + SubElement(root, 'Override', {'PartName': '/' + ARC_STYLE, 'ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml'}) + SubElement(root, 'Default', {'Extension': 'rels', 'ContentType': 'application/vnd.openxmlformats-package.relationships+xml'}) + SubElement(root, 'Default', {'Extension': 'xml', 'ContentType': 'application/xml'}) + SubElement(root, 'Override', {'PartName': '/' + ARC_WORKBOOK, 'ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml'}) + SubElement(root, 'Override', {'PartName': '/' + ARC_APP, 'ContentType': 'application/vnd.openxmlformats-officedocument.extended-properties+xml'}) + SubElement(root, 'Override', {'PartName': '/' + ARC_CORE, 'ContentType': 'application/vnd.openxmlformats-package.core-properties+xml'}) + SubElement(root, 'Override', {'PartName': '/' + ARC_SHARED_STRINGS, 'ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml'}) + + drawing_id = 1 + chart_id = 1 + + for sheet_id, sheet in enumerate(workbook.worksheets): + SubElement(root, 'Override', + {'PartName': '/xl/worksheets/sheet%d.xml' % (sheet_id + 1), + 'ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml'}) + if sheet._charts: + SubElement(root, 'Override', + {'PartName' : '/xl/drawings/drawing%d.xml' % (sheet_id + 1), + 'ContentType' : 'application/vnd.openxmlformats-officedocument.drawing+xml'}) + drawing_id += 1 + + for chart in sheet._charts: + SubElement(root, 'Override', + {'PartName' : '/xl/charts/chart%d.xml' % chart_id, + 'ContentType' : 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml'}) + chart_id += 1 + if chart._shapes: + SubElement(root, 'Override', + {'PartName' : '/xl/drawings/drawing%d.xml' % drawing_id, + 'ContentType' : 'application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml'}) + drawing_id += 1 + + return get_document_content(root) + + +def write_properties_app(workbook): + """Write the properties xml.""" + worksheets_count = len(workbook.worksheets) + root = Element('Properties', {'xmlns': 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties', + 'xmlns:vt': 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'}) + SubElement(root, 'Application').text = 'Microsoft Excel' + SubElement(root, 'DocSecurity').text = '0' + SubElement(root, 'ScaleCrop').text = 'false' + SubElement(root, 'Company') + SubElement(root, 'LinksUpToDate').text = 'false' + SubElement(root, 'SharedDoc').text = 'false' + SubElement(root, 'HyperlinksChanged').text = 'false' + SubElement(root, 'AppVersion').text = '12.0000' + + # heading pairs part + heading_pairs = SubElement(root, 'HeadingPairs') + vector = SubElement(heading_pairs, 'vt:vector', + {'size': '2', 'baseType': 'variant'}) + variant = SubElement(vector, 'vt:variant') + SubElement(variant, 'vt:lpstr').text = 'Worksheets' + variant = SubElement(vector, 'vt:variant') + SubElement(variant, 'vt:i4').text = '%d' % worksheets_count + + # title of parts + title_of_parts = SubElement(root, 'TitlesOfParts') + vector = SubElement(title_of_parts, 'vt:vector', + {'size': '%d' % worksheets_count, 'baseType': 'lpstr'}) + for ws in workbook.worksheets: + SubElement(vector, 'vt:lpstr').text = '%s' % ws.title + return get_document_content(root) + + +def write_root_rels(workbook): + """Write the relationships xml.""" + root = Element('Relationships', {'xmlns': + 'http://schemas.openxmlformats.org/package/2006/relationships'}) + SubElement(root, 'Relationship', {'Id': 'rId1', 'Target': ARC_WORKBOOK, + 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument'}) + SubElement(root, 'Relationship', {'Id': 'rId2', 'Target': ARC_CORE, + 'Type': 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties'}) + SubElement(root, 'Relationship', {'Id': 'rId3', 'Target': ARC_APP, + 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties'}) + return get_document_content(root) + + +def write_workbook(workbook): + """Write the core workbook xml.""" + root = Element('workbook', {'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', + 'xml:space': 'preserve', 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}) + SubElement(root, 'fileVersion', {'appName': 'xl', 'lastEdited': '4', + 'lowestEdited': '4', 'rupBuild': '4505'}) + SubElement(root, 'workbookPr', {'defaultThemeVersion': '124226', + 'codeName': 'ThisWorkbook'}) + book_views = SubElement(root, 'bookViews') + SubElement(book_views, 'workbookView', {'activeTab': '%d' % workbook.get_index(workbook.get_active_sheet()), + 'autoFilterDateGrouping': '1', 'firstSheet': '0', 'minimized': '0', + 'showHorizontalScroll': '1', 'showSheetTabs': '1', + 'showVerticalScroll': '1', 'tabRatio': '600', + 'visibility': 'visible'}) + # worksheets + sheets = SubElement(root, 'sheets') + for i, sheet in enumerate(workbook.worksheets): + sheet_node = SubElement(sheets, 'sheet', {'name': sheet.title, + 'sheetId': '%d' % (i + 1), 'r:id': 'rId%d' % (i + 1)}) + if not sheet.sheet_state == sheet.SHEETSTATE_VISIBLE: + sheet_node.set('state', sheet.sheet_state) + # named ranges + defined_names = SubElement(root, 'definedNames') + for named_range in workbook.get_named_ranges(): + name = SubElement(defined_names, 'definedName', + {'name': named_range.name}) + + # as there can be many cells in one range, generate the list of ranges + dest_cells = [] + cell_ids = [] + for worksheet, range_name in named_range.destinations: + cell_ids.append(workbook.get_index(worksheet)) + dest_cells.append("'%s'!%s" % (worksheet.title.replace("'", "''"), + absolute_coordinate(range_name))) + + # for local ranges, we must check all the cells belong to the same sheet + base_id = cell_ids[0] + if named_range.local_only and all([x == base_id for x in cell_ids]): + name.set('localSheetId', '%s' % base_id) + + # finally write the cells list + name.text = ','.join(dest_cells) + + SubElement(root, 'calcPr', {'calcId': '124519', 'calcMode': 'auto', + 'fullCalcOnLoad': '1'}) + return get_document_content(root) + + +def write_workbook_rels(workbook): + """Write the workbook relationships xml.""" + root = Element('Relationships', {'xmlns': + 'http://schemas.openxmlformats.org/package/2006/relationships'}) + for i in range(len(workbook.worksheets)): + SubElement(root, 'Relationship', {'Id': 'rId%d' % (i + 1), + 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet', + 'Target': 'worksheets/sheet%s.xml' % (i + 1)}) + rid = len(workbook.worksheets) + 1 + SubElement(root, 'Relationship', + {'Id': 'rId%d' % rid, 'Target': 'sharedStrings.xml', + 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings'}) + SubElement(root, 'Relationship', + {'Id': 'rId%d' % (rid + 1), 'Target': 'styles.xml', + 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles'}) + SubElement(root, 'Relationship', + {'Id': 'rId%d' % (rid + 2), 'Target': 'theme/theme1.xml', + 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme'}) + return get_document_content(root) diff --git a/tablib/packages/openpyxl/writer/worksheet.py b/tablib/packages/openpyxl/writer/worksheet.py new file mode 100644 index 0000000..ed85fcb --- /dev/null +++ b/tablib/packages/openpyxl/writer/worksheet.py @@ -0,0 +1,209 @@ +# file openpyxl/writer/worksheet.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Write worksheets to xml representations.""" + +# Python stdlib imports +from StringIO import StringIO # cStringIO doesn't handle unicode + +# package imports +from openpyxl.cell import coordinate_from_string, column_index_from_string +from openpyxl.shared.xmltools import Element, SubElement, XMLGenerator, \ + get_document_content, start_tag, end_tag, tag + + +def row_sort(cell): + """Translate column names for sorting.""" + return column_index_from_string(cell.column) + + +def write_worksheet(worksheet, string_table, style_table): + """Write a worksheet to an xml file.""" + xml_file = StringIO() + doc = XMLGenerator(xml_file, 'utf-8') + start_tag(doc, 'worksheet', + {'xml:space': 'preserve', + 'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', + 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}) + start_tag(doc, 'sheetPr') + tag(doc, 'outlinePr', + {'summaryBelow': '%d' % (worksheet.show_summary_below), + 'summaryRight': '%d' % (worksheet.show_summary_right)}) + end_tag(doc, 'sheetPr') + tag(doc, 'dimension', {'ref': '%s' % worksheet.calculate_dimension()}) + write_worksheet_sheetviews(doc, worksheet) + tag(doc, 'sheetFormatPr', {'defaultRowHeight': '15'}) + write_worksheet_cols(doc, worksheet) + write_worksheet_data(doc, worksheet, string_table, style_table) + if worksheet.auto_filter: + tag(doc, 'autoFilter', {'ref': worksheet.auto_filter}) + write_worksheet_hyperlinks(doc, worksheet) + if worksheet._charts: + tag(doc, 'drawing', {'r:id':'rId1'}) + end_tag(doc, 'worksheet') + doc.endDocument() + xml_string = xml_file.getvalue() + xml_file.close() + return xml_string + +def write_worksheet_sheetviews(doc, worksheet): + start_tag(doc, 'sheetViews') + start_tag(doc, 'sheetView', {'workbookViewId': '0'}) + selectionAttrs = {} + topLeftCell = worksheet.freeze_panes + if topLeftCell: + colName, row = coordinate_from_string(topLeftCell) + column = column_index_from_string(colName) + pane = 'topRight' + paneAttrs = {} + if column > 1: + paneAttrs['xSplit'] = str(column - 1) + if row > 1: + paneAttrs['ySplit'] = str(row - 1) + pane = 'bottomLeft' + if column > 1: + pane = 'bottomRight' + paneAttrs.update(dict(topLeftCell=topLeftCell, + activePane=pane, + state='frozen')) + tag(doc, 'pane', paneAttrs) + selectionAttrs['pane'] = pane + if row > 1 and column > 1: + tag(doc, 'selection', {'pane': 'topRight'}) + tag(doc, 'selection', {'pane': 'bottomLeft'}) + + selectionAttrs.update({'activeCell': worksheet.active_cell, + 'sqref': worksheet.selected_cell}) + + tag(doc, 'selection', selectionAttrs) + end_tag(doc, 'sheetView') + end_tag(doc, 'sheetViews') + + +def write_worksheet_cols(doc, worksheet): + """Write worksheet columns to xml.""" + if worksheet.column_dimensions: + start_tag(doc, 'cols') + for column_string, columndimension in \ + worksheet.column_dimensions.iteritems(): + col_index = column_index_from_string(column_string) + col_def = {} + col_def['collapsed'] = str(columndimension.style_index) + col_def['min'] = str(col_index) + col_def['max'] = str(col_index) + if columndimension.width != \ + worksheet.default_column_dimension.width: + col_def['customWidth'] = 'true' + if not columndimension.visible: + col_def['hidden'] = 'true' + if columndimension.outline_level > 0: + col_def['outlineLevel'] = str(columndimension.outline_level) + if columndimension.collapsed: + col_def['collapsed'] = 'true' + if columndimension.auto_size: + col_def['bestFit'] = 'true' + if columndimension.width > 0: + col_def['width'] = str(columndimension.width) + else: + col_def['width'] = '9.10' + tag(doc, 'col', col_def) + end_tag(doc, 'cols') + + +def write_worksheet_data(doc, worksheet, string_table, style_table): + """Write worksheet data to xml.""" + start_tag(doc, 'sheetData') + max_column = worksheet.get_highest_column() + style_id_by_hash = style_table + cells_by_row = {} + for cell in worksheet.get_cell_collection(): + cells_by_row.setdefault(cell.row, []).append(cell) + for row_idx in sorted(cells_by_row): + row_dimension = worksheet.row_dimensions[row_idx] + attrs = {'r': '%d' % row_idx, + 'spans': '1:%d' % max_column} + if row_dimension.height > 0: + attrs['ht'] = str(row_dimension.height) + attrs['customHeight'] = '1' + start_tag(doc, 'row', attrs) + row_cells = cells_by_row[row_idx] + sorted_cells = sorted(row_cells, key = row_sort) + for cell in sorted_cells: + value = cell._value + coordinate = cell.get_coordinate() + attributes = {'r': coordinate} + attributes['t'] = cell.data_type + if coordinate in worksheet._styles: + attributes['s'] = '%d' % style_id_by_hash[ + hash(worksheet._styles[coordinate])] + start_tag(doc, 'c', attributes) + if value is None: + tag(doc, 'v', body='') + elif cell.data_type == cell.TYPE_STRING: + tag(doc, 'v', body = '%s' % string_table[value]) + elif cell.data_type == cell.TYPE_FORMULA: + tag(doc, 'f', body = '%s' % value[1:]) + tag(doc, 'v') + elif cell.data_type == cell.TYPE_NUMERIC: + tag(doc, 'v', body = '%s' % value) + else: + tag(doc, 'v', body = '%s' % value) + end_tag(doc, 'c') + end_tag(doc, 'row') + end_tag(doc, 'sheetData') + + +def write_worksheet_hyperlinks(doc, worksheet): + """Write worksheet hyperlinks to xml.""" + write_hyperlinks = False + for cell in worksheet.get_cell_collection(): + if cell.hyperlink_rel_id is not None: + write_hyperlinks = True + break + if write_hyperlinks: + start_tag(doc, 'hyperlinks') + for cell in worksheet.get_cell_collection(): + if cell.hyperlink_rel_id is not None: + attrs = {'display': cell.hyperlink, + 'ref': cell.get_coordinate(), + 'r:id': cell.hyperlink_rel_id} + tag(doc, 'hyperlink', attrs) + end_tag(doc, 'hyperlinks') + + +def write_worksheet_rels(worksheet, idx): + """Write relationships for the worksheet to xml.""" + root = Element('Relationships', {'xmlns': 'http://schemas.openxmlformats.org/package/2006/relationships'}) + for rel in worksheet.relationships: + attrs = {'Id': rel.id, 'Type': rel.type, 'Target': rel.target} + if rel.target_mode: + attrs['TargetMode'] = rel.target_mode + SubElement(root, 'Relationship', attrs) + if worksheet._charts: + attrs = {'Id' : 'rId1', + 'Type' : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing', + 'Target' : '../drawings/drawing%s.xml' % idx } + SubElement(root, 'Relationship', attrs) + return get_document_content(root) From 85673b365cee0cea06b2525ded371af55191e308 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 12 May 2011 16:24:19 -0400 Subject: [PATCH 05/22] no more core25 --- tablib/compat.py | 4 +- tablib/core25.py | 818 ----------------------------------------------- 2 files changed, 2 insertions(+), 820 deletions(-) delete mode 100644 tablib/core25.py diff --git a/tablib/compat.py b/tablib/compat.py index f3dad91..85ae9b4 100644 --- a/tablib/compat.py +++ b/tablib/compat.py @@ -11,8 +11,8 @@ Tablib compatiblity module. import sys -from tablib.core25 import ( +from tablib.core import ( Databook, Dataset, detect, import_set, InvalidDatasetType, InvalidDimensions, UnsupportedFormat - ) +) diff --git a/tablib/core25.py b/tablib/core25.py deleted file mode 100644 index c8352a6..0000000 --- a/tablib/core25.py +++ /dev/null @@ -1,818 +0,0 @@ -# -*- coding: utf-8 -*- -u""" - tablib.core - ~~~~~~~~~~~ - - This module implements the central Tablib objects. - - :copyright: (c) 2011 by Kenneth Reitz. - :license: MIT, see LICENSE for more details. -""" - -from copy import copy -from operator import itemgetter - -from tablib import formats -import collections -from itertools import izip -from itertools import imap - -try: - from collections import OrderedDict -except ImportError: - from tablib.packages.ordereddict import OrderedDict - - -__title__ = u'tablib' -__version__ = u'0.9.4' -__build__ = 0x000904 -__author__ = u'Kenneth Reitz' -__license__ = u'MIT' -__copyright__ = u'Copyright 2011 Kenneth Reitz' -__docformat__ = u'restructuredtext' - - -class Row(object): - u"""Internal Row object. Mainly used for filtering.""" - - __slots__ = [u'tuple', u'_row', u'tags'] - - def __init__(self, row=list(), tags=list()): - self._row = list(row) - self.tags = list(tags) - - def __iter__(self): - return (col for col in self._row) - - def __len__(self): - return len(self._row) - - def __repr__(self): - return repr(self._row) - - def __getslice__(self, i, j): - return self._row[i,j] - - def __getitem__(self, i): - return self._row[i] - - def __setitem__(self, i, value): - self._row[i] = value - - def __delitem__(self, i): - del self._row[i] - - def __getstate__(self): - return {slot: [getattr(self, slot) for slot in self.__slots__]} - - def __setstate__(self, state): - for (k, v) in list(state.items()): setattr(self, k, v) - - def append(self, value): - self._row.append(value) - - def insert(self, index, value): - self._row.insert(index, value) - - def __contains__(self, item): - return (item in self._row) - - @property - def tuple(self): - u'''Tuple representation of :class:`Row`.''' - return tuple(self._row) - - @property - def list(self): - u'''List representation of :class:`Row`.''' - return list(self._row) - - def has_tag(self, tag): - u"""Returns true if current row contains tag.""" - - if tag == None: - return False - elif isinstance(tag, basestring): - return (tag in self.tags) - else: - return bool(len(set(tag) & set(self.tags))) - - - - -class Dataset(object): - u"""The :class:`Dataset` object is the heart of Tablib. It provides all core - functionality. - - Usually you create a :class:`Dataset` instance in your main module, and append - rows and columns as you collect data. :: - - data = tablib.Dataset() - data.headers = ('name', 'age') - - for (name, age) in some_collector(): - data.append((name, age)) - - You can also set rows and headers upon instantiation. This is useful if dealing - with dozens or hundres of :class:`Dataset` objects. :: - - headers = ('first_name', 'last_name') - data = [('John', 'Adams'), ('George', 'Washington')] - - data = tablib.Dataset(*data, headers=headers) - - - :param \*args: (optional) list of rows to populate Dataset - :param headers: (optional) list strings for Dataset header row - - - .. admonition:: Format Attributes Definition - - If you look at the code, the various output/import formats are not - defined within the :class:`Dataset` object. To add support for a new format, see - :ref:`Adding New Formats `. - - """ - - def __init__(self, *args, **kwargs): - self._data = list(Row(arg) for arg in args) - self.__headers = None - - # ('title', index) tuples - self._separators = [] - - # (column, callback) tuples - self._formatters = [] - - try: - self.headers = kwargs[u'headers'] - except KeyError: - self.headers = None - - try: - self.title = kwargs[u'title'] - except KeyError: - self.title = None - - self._register_formats() - - - def __len__(self): - return self.height - - - def __getitem__(self, key): - if isinstance(key, basestring): - if key in self.headers: - pos = self.headers.index(key) # get 'key' index from each data - return [row[pos] for row in self._data] - else: - raise KeyError - else: - _results = self._data[key] - if isinstance(_results, Row): - return _results.tuple - else: - return [result.tuple for result in _results] - - - def __setitem__(self, key, value): - self._validate(value) - self._data[key] = Row(value) - - - def __delitem__(self, key): - if isinstance(key, basestring): - - if key in self.headers: - - pos = self.headers.index(key) - del self.headers[pos] - - for i, row in enumerate(self._data): - - del row[pos] - self._data[i] = row - else: - raise KeyError - else: - del self._data[key] - - - def __repr__(self): - try: - return u'<%s dataset>' % (self.title.lower()) - except AttributeError: - return u'' - - - @classmethod - def _register_formats(cls): - u"""Adds format properties.""" - for fmt in formats.available: - try: - try: - setattr(cls, fmt.title, property(fmt.export_set, fmt.import_set)) - except AttributeError: - setattr(cls, fmt.title, property(fmt.export_set)) - - except AttributeError: - pass - - - def _validate(self, row=None, col=None, safety=False): - u"""Assures size of every row in dataset is of proper proportions.""" - if row: - is_valid = (len(row) == self.width) if self.width else True - elif col: - if len(col) < 1: - is_valid = True - else: - is_valid = (len(col) == self.height) if self.height else True - else: - is_valid = all((len(x) == self.width for x in self._data)) - - if is_valid: - return True - else: - if not safety: - raise InvalidDimensions - return False - - - def _package(self, dicts=True): - u"""Packages Dataset into lists of dictionaries for transmission.""" - - _data = list(self._data) - - # Execute formatters - if self._formatters: - for row_i, row in enumerate(_data): - for col, callback in self._formatters: - try: - if col is None: - for j, c in enumerate(row): - _data[row_i][j] = callback(c) - else: - _data[row_i][col] = callback(row[col]) - except IndexError: - raise InvalidDatasetIndex - - - if self.headers: - if dicts: - data = [OrderedDict(list(izip(self.headers, data_row))) for data_row in _data] - else: - data = [list(self.headers)] + list(_data) - else: - data = [list(row) for row in _data] - - return data - - - def _clean_col(self, col): - u"""Prepares the given column for insert/append.""" - - col = list(col) - - if self.headers: - header = [col.pop(0)] - else: - header = [] - - if len(col) == 1 and hasattr(col[0], '__call__'): - col = list(imap(col[0], self._data)) - col = tuple(header + col) - - return col - - - @property - def height(self): - u"""The number of rows currently in the :class:`Dataset`. - Cannot be directly modified. - """ - return len(self._data) - - - @property - def width(self): - u"""The number of columns currently in the :class:`Dataset`. - Cannot be directly modified. - """ - - try: - return len(self._data[0]) - except IndexError: - try: - return len(self.headers) - except TypeError: - return 0 - - - def _get_headers(self): - u"""An *optional* list of strings to be used for header rows and attribute names. - - This must be set manually. The given list length must equal :class:`Dataset.width`. - - """ - return self.__headers - - - def _set_headers(self, collection): - u"""Validating headers setter.""" - self._validate(collection) - if collection: - try: - self.__headers = list(collection) - except TypeError: - raise TypeError - else: - self.__headers = None - - headers = property(_get_headers, _set_headers) - - def _get_dict(self): - u"""A native Python representation of the :class:`Dataset` object. If headers have - been set, a list of Python dictionaries will be returned. If no headers have been set, - a list of tuples (rows) will be returned instead. - - A dataset object can also be imported by setting the `Dataset.dict` attribute: :: - - data = tablib.Dataset() - data.json = '[{"last_name": "Adams","age": 90,"first_name": "John"}]' - - """ - return self._package() - - - def _set_dict(self, pickle): - u"""A native Python representation of the Dataset object. If headers have been - set, a list of Python dictionaries will be returned. If no headers have been - set, a list of tuples (rows) will be returned instead. - - A dataset object can also be imported by setting the :class:`Dataset.dict` attribute. :: - - data = tablib.Dataset() - data.dict = [{'age': 90, 'first_name': 'Kenneth', 'last_name': 'Reitz'}] - - """ - - if not len(pickle): - return - - # if list of rows - if isinstance(pickle[0], list): - self.wipe() - for row in pickle: - self.append(Row(row)) - - # if list of objects - elif isinstance(pickle[0], dict): - self.wipe() - self.headers = list(pickle[0].keys()) - for row in pickle: - self.append(Row(list(row.values()))) - else: - raise UnsupportedFormat - - dict = property(_get_dict, _set_dict) - - - @property - def xls(): - u"""An Excel Spreadsheet representation of the :class:`Dataset` object, with :ref:`seperators`. Cannot be set. - - .. admonition:: Binary Warning - - :class:`Dataset.xls` contains binary data, so make sure to write in binary mode:: - - with open('output.xls', 'wb') as f: - f.write(data.xls)' - """ - pass - - - @property - def csv(): - u"""A CSV representation of the :class:`Dataset` object. The top row will contain - headers, if they have been set. Otherwise, the top row will contain - the first row of the dataset. - - A dataset object can also be imported by setting the :class:`Dataset.csv` attribute. :: - - data = tablib.Dataset() - data.csv = 'age, first_name, last_name\\n90, John, Adams' - - Import assumes (for now) that headers exist. - """ - pass - - - @property - def tsv(): - u"""A TSV representation of the :class:`Dataset` object. The top row will contain - headers, if they have been set. Otherwise, the top row will contain - the first row of the dataset. - - A dataset object can also be imported by setting the :class:`Dataset.tsv` attribute. :: - - data = tablib.Dataset() - data.tsv = 'age\tfirst_name\tlast_name\\n90\tJohn\tAdams' - - Import assumes (for now) that headers exist. - """ - - @property - def yaml(): - u"""A YAML representation of the :class:`Dataset` object. If headers have been - set, a YAML list of objects will be returned. If no headers have - been set, a YAML list of lists (rows) will be returned instead. - - A dataset object can also be imported by setting the :class:`Dataset.json` attribute: :: - - data = tablib.Dataset() - data.yaml = '- {age: 90, first_name: John, last_name: Adams}' - - Import assumes (for now) that headers exist. - """ - pass - - - @property - def json(): - u"""A JSON representation of the :class:`Dataset` object. If headers have been - set, a JSON list of objects will be returned. If no headers have - been set, a JSON list of lists (rows) will be returned instead. - - A dataset object can also be imported by setting the :class:`Dataset.json` attribute: :: - - data = tablib.Dataset() - data.json = '[{age: 90, first_name: "John", liast_name: "Adams"}]' - - Import assumes (for now) that headers exist. - """ - - @property - def html(): - u"""A HTML table representation of the :class:`Dataset` object. If - headers have been set, they will be used as table headers. - - ..notice:: This method can be used for export only. - """ - pass - - - def append(self, row=None, col=None, header=None, tags=list()): - u"""Adds a row or column to the :class:`Dataset`. - Usage is :class:`Dataset.insert` for documentation. - """ - - if row is not None: - self.insert(self.height, row=row, tags=tags) - elif col is not None: - self.insert(self.width, col=col, header=header) - - - def insert_separator(self, index, text=u'-'): - u"""Adds a separator to :class:`Dataset` at given index.""" - - sep = (index, text) - self._separators.append(sep) - - - def append_separator(self, text=u'-'): - u"""Adds a :ref:`seperator ` to the :class:`Dataset`.""" - - # change offsets if headers are or aren't defined - if not self.headers: - index = self.height if self.height else 0 - else: - index = (self.height + 1) if self.height else 1 - - self.insert_separator(index, text) - - - def add_formatter(self, col, handler): - u"""Adds a :ref:`formatter` to the :class:`Dataset`. - - .. versionadded:: 0.9.5 - :param col: column to. Accepts index int or header str. - :param handler: reference to callback function to execute - against each cell value. - """ - - if isinstance(col, basestring): - if col in self.headers: - col = self.headers.index(col) # get 'key' index from each data - else: - raise KeyError - - if not col > self.width: - self._formatters.append((col, handler)) - else: - raise InvalidDatasetIndex - - return True - - - def insert(self, index, row=None, col=None, header=None, tags=list()): - u"""Inserts a row or column to the :class:`Dataset` at the given index. - - Rows and columns inserted must be the correct size (height or width). - - The default behaviour is to insert the given row to the :class:`Dataset` - object at the given index. If the ``col`` parameter is given, however, - a new column will be insert to the :class:`Dataset` object instead. - - You can also insert a column of a single callable object, which will - add a new column with the return values of the callable each as an - item in the column. :: - - data.append(col=random.randint) - - See :ref:`dyncols` for an in-depth example. - - .. versionchanged:: 0.9.0 - If inserting a column, and :class:`Dataset.headers` is set, the - header attribute must be set, and will be considered the header for - that row. - - .. versionadded:: 0.9.0 - If inserting a row, you can add :ref:`tags ` to the row you are inserting. - This gives you the ability to :class:`filter ` your - :class:`Dataset` later. - - """ - if row: - self._validate(row) - self._data.insert(index, Row(row, tags=tags)) - elif col: - col = list(col) - - # Callable Columns... - if len(col) == 1 and hasattr(col[0], '__call__'): - col = list(imap(col[0], self._data)) - - col = self._clean_col(col) - self._validate(col=col) - - if self.headers: - # pop the first item off, add to headers - if not header: - raise HeadersNeeded() - self.headers.insert(index, header) - - if self.height and self.width: - - for i, row in enumerate(self._data): - - row.insert(index, col[i]) - self._data[i] = row - else: - self._data = [Row([row]) for row in col] - - - def filter(self, tag): - u"""Returns a new instance of the :class:`Dataset`, excluding any rows - that do not contain the given :ref:`tags `. - """ - _dset = copy(self) - _dset._data = [row for row in _dset._data if row.has_tag(tag)] - - return _dset - - - def sort(self, col, reverse=False): - u"""Sort a :class:`Dataset` by a specific column, given string (for - header) or integer (for column index). The order can be reversed by - setting ``reverse`` to ``True``. - Returns a new :class:`Dataset` instance where columns have been - sorted.""" - - if isinstance(col, basestring): - - if not self.headers: - raise HeadersNeeded - - _sorted = sorted(self.dict, key=itemgetter(col), reverse=reverse) - _dset = Dataset(headers=self.headers) - - for item in _sorted: - row = [item[key] for key in self.headers] - _dset.append(row=row) - - else: - if self.headers: - col = self.headers[col] - - _sorted = sorted(self.dict, key=itemgetter(col), reverse=reverse) - _dset = Dataset(headers=self.headers) - - for item in _sorted: - if self.headers: - row = [item[key] for key in self.headers] - else: - row = item - _dset.append(row=row) - - - return _dset - - - def transpose(self): - u"""Transpose a :class:`Dataset`, turning rows into columns and vice - versa, returning a new ``Dataset`` instance. The first row of the - original instance becomes the new header row.""" - - # Don't transpose if there is no data - if not self: - return - - _dset = Dataset() - # The first element of the headers stays in the headers, - # it is our "hinge" on which we rotate the data - new_headers = [self.headers[0]] + self[self.headers[0]] - - _dset.headers = new_headers - for column in self.headers: - - if column == self.headers[0]: - # It's in the headers, so skip it - continue - - # Adding the column name as now they're a regular column - row_data = [column] + self[column] - row_data = Row(row_data) - _dset.append(row=row_data) - - return _dset - - - def stack_rows(self, other): - u"""Stack two :class:`Dataset` instances together by - joining at the row level, and return new combined - ``Dataset`` instance.""" - - if not isinstance(other, Dataset): - return - - if self.width != other.width: - raise InvalidDimensions - - # Copy the source data - _dset = copy(self) - - rows_to_stack = [row for row in _dset._data] - other_rows = [row for row in other._data] - - rows_to_stack.extend(other_rows) - _dset._data = rows_to_stack - - return _dset - - - def stack_columns(self, other): - u"""Stack two :class:`Dataset` instances together by - joining at the column level, and return a new - combined ``Dataset`` instance. If either ``Dataset`` - has headers set, than the other must as well.""" - - if not isinstance(other, Dataset): - return - - if self.headers or other.headers: - if not self.headers or not other.headers: - raise HeadersNeeded - - if self.height != other.height: - raise InvalidDimensions - - try: - new_headers = self.headers + other.headers - except TypeError: - new_headers = None - - _dset = Dataset() - - for column in self.headers: - _dset.append(col=self[column]) - - for column in other.headers: - _dset.append(col=other[column]) - - _dset.headers = new_headers - - return _dset - - - def wipe(self): - u"""Removes all content and headers from the :class:`Dataset` object.""" - self._data = list() - self.__headers = None - - - -class Databook(object): - u"""A book of :class:`Dataset` objects. - """ - - def __init__(self, sets=None): - - if sets is None: - self._datasets = list() - else: - self._datasets = sets - - self._register_formats() - - def __repr__(self): - try: - return u'<%s databook>' % (self.title.lower()) - except AttributeError: - return u'' - - - def wipe(self): - u"""Removes all :class:`Dataset` objects from the :class:`Databook`.""" - self._datasets = [] - - - @classmethod - def _register_formats(cls): - u"""Adds format properties.""" - for fmt in formats.available: - try: - try: - setattr(cls, fmt.title, property(fmt.export_book, fmt.import_book)) - except AttributeError: - setattr(cls, fmt.title, property(fmt.export_book)) - - except AttributeError: - pass - - - def add_sheet(self, dataset): - u"""Adds given :class:`Dataset` to the :class:`Databook`.""" - if type(dataset) is Dataset: - self._datasets.append(dataset) - else: - raise InvalidDatasetType - - - def _package(self): - u"""Packages :class:`Databook` for delivery.""" - collector = [] - for dset in self._datasets: - collector.append(OrderedDict( - title = dset.title, - data = dset.dict - )) - return collector - - - @property - def size(self): - u"""The number of the :class:`Dataset` objects within :class:`Databook`.""" - return len(self._datasets) - - -def detect(stream): - u"""Return (format, stream) of given stream.""" - for fmt in formats.available: - try: - if fmt.detect(stream): - return (fmt, stream) - except AttributeError: - pass - return (None, stream) - - -def import_set(stream): - u"""Return dataset of given stream.""" - (format, stream) = detect(stream) - - try: - data = Dataset() - format.import_set(data, stream) - return data - - except AttributeError, e: - return None - - -class InvalidDatasetType(Exception): - u"Only Datasets can be added to a DataBook" - - -class InvalidDimensions(Exception): - u"Invalid size" - -class InvalidDatasetIndex(Exception): - u"Outside of Dataset size" - -class HeadersNeeded(Exception): - u"Header parameter must be given when appending a column in this Dataset." - -class UnsupportedFormat(NotImplementedError): - u"Format is not supported" From 5350355fbe0aefe053d40fda03c0688a7b7eae3d Mon Sep 17 00:00:00 2001 From: Mark Rogers Date: Thu, 12 May 2011 15:59:57 -0500 Subject: [PATCH 06/22] a bunch of cleanup from my previous commit --- AUTHORS | 3 ++- setup.py | 1 + tablib/core.py | 9 ++++++++ tablib/core25.py | 13 +++++++++++ tablib/formats/_xlsx.py | 51 ++++++++++++++++++----------------------- test_tablib.py | 2 ++ 6 files changed, 49 insertions(+), 30 deletions(-) diff --git a/AUTHORS b/AUTHORS index 1ca2018..facbd2e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -14,4 +14,5 @@ Patches and Suggestions - Josh Ourisman - Luca Beltrame - Benjamin Wohlwend -- Erik Youngren \ No newline at end of file +- Erik Youngren +- Mark Rogers \ No newline at end of file diff --git a/setup.py b/setup.py index bb7ef54..850f915 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,7 @@ setup( 'tablib', 'tablib.formats', 'tablib.packages', 'tablib.packages.xlwt', + 'tablib.packages.openpyxl', 'tablib.packages.yaml', 'tablib.packages.unicodecsv' ], diff --git a/tablib/core.py b/tablib/core.py index 71f4f71..348b88f 100644 --- a/tablib/core.py +++ b/tablib/core.py @@ -392,6 +392,15 @@ class Dataset(object): @property def xlsx(): + """An Excel Spreadsheet representation of the :class:`Dataset` object, with :ref:`separators`. Cannot be set. + + .. admonition:: Binary Warning + + :class:`Dataset.xlsx` contains binary data, so make sure to write in binary mode:: + + with open('output.xlsx', 'wb') as f: + f.write(data.xlsx)' + """ pass diff --git a/tablib/core25.py b/tablib/core25.py index c8352a6..a48fbb9 100644 --- a/tablib/core25.py +++ b/tablib/core25.py @@ -392,6 +392,19 @@ class Dataset(object): """ pass + @property + def xlsx(): + """An Excel Spreadsheet representation of the :class:`Dataset` object, with :ref:`separators`. Cannot be set. + + .. admonition:: Binary Warning + + :class:`Dataset.xlsx` contains binary data, so make sure to write in binary mode:: + + with open('output.xlsx', 'wb') as f: + f.write(data.xlsx)' + """ + pass + @property def csv(): diff --git a/tablib/formats/_xlsx.py b/tablib/formats/_xlsx.py index 92e33f9..1a0713b 100644 --- a/tablib/formats/_xlsx.py +++ b/tablib/formats/_xlsx.py @@ -7,30 +7,20 @@ import sys if sys.version_info[0] > 2: - from io import BytesIO - import tablib.packages.xlwt3 as xlwt - + from io import BytesIO else: from cStringIO import StringIO as BytesIO - import tablib.packages.xlwt as xlwt from tablib.packages.openpyxl.workbook import Workbook from tablib.packages.openpyxl.writer.excel import ExcelWriter from tablib.packages.openpyxl.cell import get_column_letter - - title = 'xlsx' extentions = ('xlsx',) -# special styles -#wrap = xlwt.easyxf("alignment: wrap on") -#bold = xlwt.easyxf("font: bold on") - - def export_set(dataset): - """Returns XLS representation of Dataset.""" + """Returns XLSX representation of Dataset.""" wb = Workbook() ws = wb.worksheets[0] @@ -44,19 +34,19 @@ def export_set(dataset): def export_book(databook): - """Returns XLS representation of DataBook.""" + """Returns XLSX representation of DataBook.""" wb = Workbook() ew = ExcelWriter(workbook = wb) for i, dset in enumerate(databook._datasets): - ws = wb.add_sheet() + ws = wb.create_sheet() ws.title = dset.title if dset.title else 'Sheet%s' % (i) dset_sheet(dset, ws) stream = BytesIO() - ew.save(filename='test.xlsx') + ew.save(stream) return stream.getvalue() @@ -75,30 +65,33 @@ def dset_sheet(dataset, ws): # bold headers if (row_number == 1) and dataset.headers: - ws.cell('%s%s'%(col_idx, row_number)).value = '%s' % col - #ws.write(i, j, col, bold) - - # frozen header row - #ws.panes_frozen = True - #ws.horz_split_pos = 1 + ws.cell('%s%s'%(col_idx, row_number)).value = unicode( + '%s' % col, errors='ignore') + style = ws.get_style('%s%s' % (col_idx, row_number)) + style.font.bold = True + ws.freeze_panes = '%s%s' % (col_idx, row_number) # bold separators elif len(row) < dataset.width: - ws.cell('%s%s'%(col_idx, row_number)).value = '%s' % col - #ws.write(i, j, col, bold) + ws.cell('%s%s'%(col_idx, row_number)).value = unicode( + '%s' % col, errors='ignore') + style = ws.get_style('%s%s' % (col_idx, row_number)) + style.font.bold = True # wrap the rest else: try: if '\n' in col: - ws.cell('%s%s'%(col_idx, row_number)).value = '%s' % col - #ws.write(i, j, col, wrap) + ws.cell('%s%s'%(col_idx, row_number)).value = unicode( + '%s' % col, errors='ignore') + style = ws.get_style('%s%s' % (col_idx, row_number)) + style.alignment.wrap_text else: - ws.cell('%s%s'%(col_idx, row_number)).value = '%s' % col - #ws.write(i, j, col) + ws.cell('%s%s'%(col_idx, row_number)).value = unicode( + '%s' % col, errors='ignore') except TypeError: - ws.cell('%s%s'%(col_idx, row_number)).value = '%s' % col - #ws.write(i, j, col) + ws.cell('%s%s'%(col_idx, row_number)).value = unicode( + '%s' % col, errors='ignore') diff --git a/test_tablib.py b/test_tablib.py index 7791935..4346859 100755 --- a/test_tablib.py +++ b/test_tablib.py @@ -222,6 +222,7 @@ class TablibTestCase(unittest.TestCase): data.csv data.tsv data.xls + data.xlsx def test_book_export_no_exceptions(self): @@ -233,6 +234,7 @@ class TablibTestCase(unittest.TestCase): book.json book.yaml book.xls + book.xlsx def test_json_import_set(self): From 79fb82d69d9c51e531c02a0af3f1bc49756fdaf4 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 12 May 2011 16:44:39 -0400 Subject: [PATCH 07/22] compat module --- tablib/__init__.py | 2 +- tablib/compat.py | 23 +++++++++++++++++++---- tablib/core.py | 6 ++---- tablib/formats/_xls.py | 10 +--------- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/tablib/__init__.py b/tablib/__init__.py index 24d5fdb..c7ae7c0 100644 --- a/tablib/__init__.py +++ b/tablib/__init__.py @@ -1,7 +1,7 @@ """ Tablib. """ -from tablib.compat import ( +from tablib.core import ( Databook, Dataset, detect, import_set, InvalidDatasetType, InvalidDimensions, UnsupportedFormat ) diff --git a/tablib/compat.py b/tablib/compat.py index 85ae9b4..09a56b6 100644 --- a/tablib/compat.py +++ b/tablib/compat.py @@ -10,9 +10,24 @@ Tablib compatiblity module. import sys +is_py3 = (sys.version_info[0] > 2) + + + +try: + from collections import OrderedDict +except ImportError: + from tablib.packages.ordereddict import OrderedDict + + +if is_py3: + from io import BytesIO + import tablib.packages.xlwt3 as xlwt + from tablib.packages import markup3 as markup + +else: + from cStringIO import StringIO as BytesIO + import tablib.packages.xlwt as xlwt + from tablib.packages import markup -from tablib.core import ( - Databook, Dataset, detect, import_set, - InvalidDatasetType, InvalidDimensions, UnsupportedFormat -) diff --git a/tablib/core.py b/tablib/core.py index 896dfcc..9fd9ec4 100644 --- a/tablib/core.py +++ b/tablib/core.py @@ -15,10 +15,8 @@ from operator import itemgetter from tablib import formats import collections -try: - from collections import OrderedDict -except ImportError: - from tablib.packages.ordereddict import OrderedDict + +from tablib.compat import OrderedDict __title__ = 'tablib' diff --git a/tablib/formats/_xls.py b/tablib/formats/_xls.py index d820250..48dcc0b 100644 --- a/tablib/formats/_xls.py +++ b/tablib/formats/_xls.py @@ -5,15 +5,7 @@ import sys - -if sys.version_info[0] > 2: - from io import BytesIO - import tablib.packages.xlwt3 as xlwt - -else: - from cStringIO import StringIO as BytesIO - import tablib.packages.xlwt as xlwt - +from tablib.compat import BytesIO, xlwt title = 'xls' From 2b5818598a87875aa77712bb8e733eea9d4e1f93 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 12 May 2011 16:44:39 -0400 Subject: [PATCH 08/22] compat module --- tablib/__init__.py | 2 +- tablib/compat.py | 23 +++++++++++++++++++---- tablib/core.py | 8 +++----- tablib/formats/_xls.py | 10 +--------- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/tablib/__init__.py b/tablib/__init__.py index 24d5fdb..c7ae7c0 100644 --- a/tablib/__init__.py +++ b/tablib/__init__.py @@ -1,7 +1,7 @@ """ Tablib. """ -from tablib.compat import ( +from tablib.core import ( Databook, Dataset, detect, import_set, InvalidDatasetType, InvalidDimensions, UnsupportedFormat ) diff --git a/tablib/compat.py b/tablib/compat.py index 85ae9b4..09a56b6 100644 --- a/tablib/compat.py +++ b/tablib/compat.py @@ -10,9 +10,24 @@ Tablib compatiblity module. import sys +is_py3 = (sys.version_info[0] > 2) + + + +try: + from collections import OrderedDict +except ImportError: + from tablib.packages.ordereddict import OrderedDict + + +if is_py3: + from io import BytesIO + import tablib.packages.xlwt3 as xlwt + from tablib.packages import markup3 as markup + +else: + from cStringIO import StringIO as BytesIO + import tablib.packages.xlwt as xlwt + from tablib.packages import markup -from tablib.core import ( - Databook, Dataset, detect, import_set, - InvalidDatasetType, InvalidDimensions, UnsupportedFormat -) diff --git a/tablib/core.py b/tablib/core.py index 896dfcc..55d057b 100644 --- a/tablib/core.py +++ b/tablib/core.py @@ -15,10 +15,8 @@ from operator import itemgetter from tablib import formats import collections -try: - from collections import OrderedDict -except ImportError: - from tablib.packages.ordereddict import OrderedDict + +from tablib.compat import OrderedDict __title__ = 'tablib' @@ -795,7 +793,7 @@ def import_set(stream): format.import_set(data, stream) return data - except AttributeError as e: + except AttributeError: return None diff --git a/tablib/formats/_xls.py b/tablib/formats/_xls.py index d820250..48dcc0b 100644 --- a/tablib/formats/_xls.py +++ b/tablib/formats/_xls.py @@ -5,15 +5,7 @@ import sys - -if sys.version_info[0] > 2: - from io import BytesIO - import tablib.packages.xlwt3 as xlwt - -else: - from cStringIO import StringIO as BytesIO - import tablib.packages.xlwt as xlwt - +from tablib.compat import BytesIO, xlwt title = 'xls' From 11bca4f7a28bffb90c67a89ae6069995ad0ee180 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 12 May 2011 17:20:22 -0400 Subject: [PATCH 09/22] callable check fix --- tablib/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tablib/core.py b/tablib/core.py index 55d057b..4cef83c 100644 --- a/tablib/core.py +++ b/tablib/core.py @@ -13,7 +13,6 @@ from copy import copy from operator import itemgetter from tablib import formats -import collections from tablib.compat import OrderedDict @@ -276,7 +275,8 @@ class Dataset(object): else: header = [] - if len(col) == 1 and isinstance(col[0], collections.Callable): + if len(col) == 1 and hasattr(col[0], '__call__'): + col = list(map(col[0], self._data)) col = tuple(header + col) @@ -547,7 +547,7 @@ class Dataset(object): col = list(col) # Callable Columns... - if len(col) == 1 and isinstance(col[0], collections.Callable): + if len(col) == 1 and hasattr(col[0], '__call__'): col = list(map(col[0], self._data)) col = self._clean_col(col) From 8dd7d73abccbedecc8a789208aeff5a64ae28317 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 12 May 2011 17:27:17 -0400 Subject: [PATCH 10/22] compat module notes --- HISTORY.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/HISTORY.rst b/HISTORY.rst index 22437ca..f35c538 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,6 +5,7 @@ History +++++ * Pickling Bugfix +* Compat Module 0.9.6 (2011-05-12) From 396872990377251966ad733924aa0fbfe177d058 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 12 May 2011 17:38:53 -0400 Subject: [PATCH 11/22] html out --- test_tablib.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test_tablib.py b/test_tablib.py index 7791935..5a7c2aa 100755 --- a/test_tablib.py +++ b/test_tablib.py @@ -222,6 +222,7 @@ class TablibTestCase(unittest.TestCase): data.csv data.tsv data.xls + data.html def test_book_export_no_exceptions(self): @@ -490,26 +491,26 @@ class TablibTestCase(unittest.TestCase): def test_formatters(self): """Confirm formatters are being triggered.""" - + def _formatter(cell_value): return str(cell_value).upper() - + self.founders.add_formatter('last_name', _formatter) - + for name in [r['last_name'] for r in self.founders.dict]: self.assertTrue(name.isupper()) def test_unicode_csv(self): """Check if unicode in csv export doesn't raise.""" - + data = tablib.Dataset() - + if sys.version_info[0] > 2: data.append(['\xfc', '\xfd']) else: exec("data.append([u'\xfc', u'\xfd'])") - - + + data.csv if __name__ == '__main__': From 6b6ef70c61e1f35ea1b26535a2bec33a74be6bdf Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 12 May 2011 17:54:00 -0400 Subject: [PATCH 12/22] Convert openpyxl to relative imports --- tablib/packages/openpyxl/__init__.py | 16 +-- tablib/packages/openpyxl/cell.py | 16 +-- tablib/packages/openpyxl/chart.py | 132 +++++++++--------- tablib/packages/openpyxl/drawing.py | 101 +++++++------- tablib/packages/openpyxl/namedrange.py | 2 +- tablib/packages/openpyxl/reader/__init__.py | 10 +- tablib/packages/openpyxl/reader/excel.py | 24 ++-- .../openpyxl/reader/iter_worksheet.py | 46 +++--- tablib/packages/openpyxl/reader/strings.py | 4 +- tablib/packages/openpyxl/reader/style.py | 6 +- tablib/packages/openpyxl/reader/workbook.py | 10 +- tablib/packages/openpyxl/reader/worksheet.py | 14 +- tablib/packages/openpyxl/shared/__init__.py | 10 +- tablib/packages/openpyxl/shared/xmltools.py | 2 +- tablib/packages/openpyxl/workbook.py | 14 +- tablib/packages/openpyxl/worksheet.py | 30 ++-- tablib/packages/openpyxl/writer/__init__.py | 12 +- tablib/packages/openpyxl/writer/charts.py | 88 ++++++------ tablib/packages/openpyxl/writer/drawings.py | 2 +- .../openpyxl/writer/dump_worksheet.py | 36 ++--- tablib/packages/openpyxl/writer/excel.py | 18 +-- tablib/packages/openpyxl/writer/strings.py | 2 +- tablib/packages/openpyxl/writer/styles.py | 62 ++++---- tablib/packages/openpyxl/writer/theme.py | 2 +- tablib/packages/openpyxl/writer/workbook.py | 10 +- tablib/packages/openpyxl/writer/worksheet.py | 6 +- 26 files changed, 337 insertions(+), 338 deletions(-) diff --git a/tablib/packages/openpyxl/__init__.py b/tablib/packages/openpyxl/__init__.py index c3dcc5e..81381d7 100644 --- a/tablib/packages/openpyxl/__init__.py +++ b/tablib/packages/openpyxl/__init__.py @@ -26,14 +26,14 @@ """Imports for the openpyxl package.""" # package imports -from openpyxl import cell -from openpyxl import namedrange -from openpyxl import style -from openpyxl import workbook -from openpyxl import worksheet -from openpyxl import reader -from openpyxl import shared -from openpyxl import writer +from . import cell +from . import namedrange +from . import style +from . import workbook +from . import worksheet +from . import reader +from . import shared +from . import writer # constants diff --git a/tablib/packages/openpyxl/cell.py b/tablib/packages/openpyxl/cell.py index 324fc96..d3a4d0b 100644 --- a/tablib/packages/openpyxl/cell.py +++ b/tablib/packages/openpyxl/cell.py @@ -38,10 +38,10 @@ import datetime import re # package imports -from openpyxl.shared.date_time import SharedDate -from openpyxl.shared.exc import CellCoordinatesException, \ +from .shared.date_time import SharedDate +from .shared.exc import CellCoordinatesException, \ ColumnStringIndexException, DataTypeException -from openpyxl.style import NumberFormat +from .style import NumberFormat # constants COORD_RE = re.compile('^[$]?([A-Z]+)[$]?(\d+)$') @@ -70,12 +70,12 @@ def absolute_coordinate(coord_string): def column_index_from_string(column, fast = False): """Convert a column letter into a column number (e.g. B -> 2) - + Excel only supports 1-3 letter column names from A -> ZZZ, so we restrict our column names to 1-3 characters, each in the range A-Z. - + .. note:: - + Fast mode is faster but does not check that all letters are capitals between A and Z """ @@ -349,7 +349,7 @@ class Cell(object): :rtype: string """ return '%s%s' % (self.column, self.row) - + @property def address(self): """Return the coordinate string for this cell (e.g. 'B12') @@ -376,7 +376,7 @@ class Cell(object): def is_date(self): """Returns whether the value is *probably* a date or not - + :rtype: bool """ return (self.has_style diff --git a/tablib/packages/openpyxl/chart.py b/tablib/packages/openpyxl/chart.py index c23e78f..56cbb4d 100644 --- a/tablib/packages/openpyxl/chart.py +++ b/tablib/packages/openpyxl/chart.py @@ -25,20 +25,20 @@ THE SOFTWARE. import math -from openpyxl.style import NumberFormat -from openpyxl.drawing import Drawing, Shape -from openpyxl.shared.units import pixels_to_EMU, short_color -from openpyxl.cell import get_column_letter +from .style import NumberFormat +from .drawing import Drawing, Shape +from .shared.units import pixels_to_EMU, short_color +from .cell import get_column_letter class Axis(object): - + POSITION_BOTTOM = 'b' POSITION_LEFT = 'l' - + ORIENTATION_MIN_MAX = "minMax" - + def __init__(self): - + self.orientation = self.ORIENTATION_MIN_MAX self.number_format = NumberFormat() for attr in ('position','tick_label_position','crosses', @@ -47,11 +47,11 @@ class Axis(object): self.min = 0 self.max = None self.unit = None - + @classmethod def default_category(cls): """ default values for category axes """ - + ax = Axis() ax.id = 60871424 ax.cross = 60873344 @@ -62,11 +62,11 @@ class Axis(object): ax.label_align = 'ctr' ax.label_offset = 100 return ax - + @classmethod def default_value(cls): """ default values for value axes """ - + ax = Axis() ax.id = 60873344 ax.cross = 60871424 @@ -82,7 +82,7 @@ class Reference(object): """ a simple wrapper around a serie of reference data """ def __init__(self, sheet, pos1, pos2=None): - + self.sheet = sheet self.pos1 = pos1 self.pos2 = pos2 @@ -93,10 +93,10 @@ class Reference(object): return 'str' else: return 'num' - + def _get_ref(self): """ format excel reference notation """ - + if self.pos2: return '%s!$%s$%s:$%s$%s' % (self.sheet.title, get_column_letter(self.pos1[1]+1), self.pos1[0]+1, @@ -104,11 +104,11 @@ class Reference(object): else: return '%s!$%s$%s' % (self.sheet.title, get_column_letter(self.pos1[1]+1), self.pos1[0]+1) - - + + def _get_cache(self): """ read data in sheet - to be used at writing time """ - + cache = [] if self.pos2: for row in range(self.pos1[0], self.pos2[0]+1): @@ -118,15 +118,15 @@ class Reference(object): cell = self.sheet.cell(row=self.pos1[0], column=self.pos1[1]) cache.append(cell.value) return cache - + class Serie(object): """ a serie of data and possibly associated labels """ - + MARKER_NONE = 'none' - + def __init__(self, values, labels=None, legend=None, color=None, xvalues=None): - + self.marker = Serie.MARKER_NONE self.values = values self.xvalues = xvalues @@ -134,13 +134,13 @@ class Serie(object): self.legend = legend self.error_bar = None self._color = color - + def _get_color(self): return self._color - + def _set_color(self, color): self._color = short_color(color) - + color = property(_get_color, _set_color) def get_min_max(self): @@ -152,43 +152,43 @@ class Serie(object): else: vals = self.values._get_cache() return min(vals), max(vals) - + def __len__(self): return len(self.values.cache) - + class Legend(object): - + def __init__(self): - + self.position = 'r' self.layout = None - + class ErrorBar(object): - + PLUS = 1 MINUS = 2 PLUS_MINUS = 3 - + def __init__(self, _type, values): - + self.type = _type self.values = values - + class Chart(object): """ raw chart class """ - + GROUPING_CLUSTERED = 'clustered' GROUPING_STANDARD = 'standard' - + BAR_CHART = 1 LINE_CHART = 2 SCATTER_CHART = 3 - + def __init__(self, _type, grouping): - + self._series = [] - + # public api self.type = _type self.grouping = grouping @@ -198,46 +198,46 @@ class Chart(object): self.lang = 'fr-FR' self.title = '' self.print_margins = dict(b=.75, l=.7, r=.7, t=.75, header=0.3, footer=.3) - + # the containing drawing self.drawing = Drawing() - + # the offset for the plot part in percentage of the drawing size self.width = .6 self.height = .6 self.margin_top = self._get_max_margin_top() self.margin_left = 0 - + # the user defined shapes self._shapes = [] - + def add_serie(self, serie): - + serie.id = len(self._series) self._series.append(serie) self._compute_min_max() if not None in [s.xvalues for s in self._series]: self._compute_xmin_xmax() - + def add_shape(self, shape): - + shape._chart = self self._shapes.append(shape) - + def get_x_units(self): """ calculate one unit for x axis in EMU """ return max([len(s.values._get_cache()) for s in self._series]) - + def get_y_units(self): """ calculate one unit for y axis in EMU """ dh = pixels_to_EMU(self.drawing.height) return (dh * self.height) / self.y_axis.max - + def get_y_chars(self): """ estimate nb of chars for y axis """ - + _max = max([max(s.values._get_cache()) for s in self._series]) return len(str(int(_max))) @@ -265,14 +265,14 @@ class Chart(object): if mul is not None: maxi = maxi/mul unit = unit/mul - + if maxi / unit > 9: # no more that 10 ticks unit *= 2 - + self.y_axis.max = maxi self.y_axis.unit = unit - + def _compute_xmin_xmax(self): """ compute x axis limits and units """ @@ -297,44 +297,44 @@ class Chart(object): if mul is not None: maxi = maxi/mul unit = unit/mul - + if maxi / unit > 9: # no more that 10 ticks unit *= 2 - + self.x_axis.max = maxi self.x_axis.unit = unit - + def _get_max_margin_top(self): - + mb = Shape.FONT_HEIGHT + Shape.MARGIN_BOTTOM plot_height = self.drawing.height * self.height return float(self.drawing.height - plot_height - mb)/self.drawing.height - + def _get_min_margin_left(self): - + ml = (self.get_y_chars() * Shape.FONT_WIDTH) + Shape.MARGIN_LEFT return float(ml)/self.drawing.width def _get_margin_top(self): """ get margin in percent """ - + return min(self.margin_top, self._get_max_margin_top()) - + def _get_margin_left(self): - + return max(self._get_min_margin_left(), self.margin_left) class BarChart(Chart): def __init__(self): super(BarChart, self).__init__(Chart.BAR_CHART, Chart.GROUPING_CLUSTERED) - + class LineChart(Chart): def __init__(self): super(LineChart, self).__init__(Chart.LINE_CHART, Chart.GROUPING_STANDARD) - + class ScatterChart(Chart): def __init__(self): super(ScatterChart, self).__init__(Chart.SCATTER_CHART, Chart.GROUPING_STANDARD) - - + + diff --git a/tablib/packages/openpyxl/drawing.py b/tablib/packages/openpyxl/drawing.py index c9cf084..d3dc00b 100644 --- a/tablib/packages/openpyxl/drawing.py +++ b/tablib/packages/openpyxl/drawing.py @@ -24,11 +24,11 @@ THE SOFTWARE. ''' import math -from openpyxl.style import Color -from openpyxl.shared.units import pixels_to_EMU, EMU_to_pixels, short_color +from .style import Color +from .shared.units import pixels_to_EMU, EMU_to_pixels, short_color class Shadow(object): - + SHADOW_BOTTOM = 'b' SHADOW_BOTTOM_LEFT = 'bl' SHADOW_BOTTOM_RIGHT = 'br' @@ -46,17 +46,17 @@ class Shadow(object): self.alignment = self.SHADOW_BOTTOM_RIGHT self.color = Color(Color.BLACK) self.alpha = 50 - + class Drawing(object): - """ a drawing object - eg container for shapes or charts - we assume user specifies dimensions in pixels; units are + """ a drawing object - eg container for shapes or charts + we assume user specifies dimensions in pixels; units are converted to EMU in the drawing part """ count = 0 def __init__(self): - + self.name = '' self.description = '' self.coordinates = ((1,2), (16,8)) @@ -67,38 +67,38 @@ class Drawing(object): self.resize_proportional = False self.rotation = 0 # self.shadow = Shadow() - + def _set_width(self, w): - + if self.resize_proportional and w: ratio = self._height / self._width self._height = round(ratio * w) self._width = w - + def _get_width(self): - + return self._width - + width = property(_get_width, _set_width) - + def _set_height(self, h): - + if self.resize_proportional and h: ratio = self._width / self._height self._width = round(ratio * h) self._height = h - + def _get_height(self): - + return self._height - + height = property(_get_height, _set_height) - + def set_dimension(self, w=0, h=0): xratio = w / self._width yratio = h / self._height - + if self.resize_proportional and w and h: if (xratio * self._height) < h: self._height = math.ceil(xratio * self._height) @@ -106,28 +106,28 @@ class Drawing(object): else: self._width = math.ceil(yratio * self._width) self._height = height - + def get_emu_dimensions(self): """ return (x, y, w, h) in EMU """ - + return (pixels_to_EMU(self.left), pixels_to_EMU(self.top), pixels_to_EMU(self._width), pixels_to_EMU(self._height)) - + class Shape(object): """ a drawing inside a chart coordiantes are specified by the user in the axis units """ - + MARGIN_LEFT = 6 + 13 + 1 MARGIN_BOTTOM = 17 + 11 - + FONT_WIDTH = 7 FONT_HEIGHT = 8 - + ROUND_RECT = 'roundRect' RECT = 'rect' - + # other shapes to define : ''' "line" @@ -317,9 +317,9 @@ class Shape(object): "chartStar" "chartPlus" ''' - + def __init__(self, coordinates=((0,0), (1,1)), text=None, scheme="accent1"): - + self.coordinates = coordinates # in axis unit self.text = text self.scheme = scheme @@ -328,47 +328,47 @@ class Shape(object): self._border_color = Color.BLACK[2:] #"F3B3C5" self._color = Color.WHITE[2:] self._text_color = Color.BLACK[2:] - + def _get_border_color(self): return self._border_color - + def _set_border_color(self, color): self._border_color = short_color(color) - + border_color = property(_get_border_color, _set_border_color) - + def _get_color(self): return self._color - + def _set_color(self, color): self._color = short_color(color) - + color = property(_get_color, _set_color) - + def _get_text_color(self): return self._text_color - + def _set_text_color(self, color): self._text_color = short_color(color) - + text_color = property(_get_text_color, _set_text_color) - + def _get_border_width(self): - + return EMU_to_pixels(self._border_width) - + def _set_border_width(self, w): - + self._border_width = pixels_to_EMU(w) print self._border_width - + border_width = property(_get_border_width, _set_border_width) - + def get_coordinates(self): """ return shape coordinates in percentages (left, top, right, bottom) """ (x1, y1), (x2, y2) = self.coordinates - + drawing_width = pixels_to_EMU(self._chart.drawing.width) drawing_height = pixels_to_EMU(self._chart.drawing.height) plot_width = drawing_width * self._chart.width @@ -376,27 +376,26 @@ class Shape(object): margin_left = self._chart._get_margin_left() * drawing_width xunit = plot_width / self._chart.get_x_units() - + margin_top = self._chart._get_margin_top() * drawing_height yunit = self._chart.get_y_units() - + x_start = (margin_left + (float(x1) * xunit)) / drawing_width y_start = (margin_top + plot_height - (float(y1) * yunit)) / drawing_height x_end = (margin_left + (float(x2) * xunit)) / drawing_width y_end = (margin_top + plot_height - (float(y2) * yunit)) / drawing_height - + def _norm_pct(pct): """ force shapes to appear by truncating too large sizes """ if pct>1: pct = 1 elif pct<0: pct = 0 return pct - + # allow user to specify y's in whatever order # excel expect y_end to be lower if y_end < y_start: y_end, y_start = y_start, y_end - - return (_norm_pct(x_start), _norm_pct(y_start), + + return (_norm_pct(x_start), _norm_pct(y_start), _norm_pct(x_end), _norm_pct(y_end)) - \ No newline at end of file diff --git a/tablib/packages/openpyxl/namedrange.py b/tablib/packages/openpyxl/namedrange.py index 617847a..bffa978 100644 --- a/tablib/packages/openpyxl/namedrange.py +++ b/tablib/packages/openpyxl/namedrange.py @@ -29,7 +29,7 @@ import re # package imports -from openpyxl.shared.exc import NamedRangeException +from .shared.exc import NamedRangeException # constants NAMED_RANGE_RE = re.compile("'?([^']*)'?!((\$([A-Za-z]+))?\$([0-9]+)(:(\$([A-Za-z]+))?(\$([0-9]+)))?)$") diff --git a/tablib/packages/openpyxl/reader/__init__.py b/tablib/packages/openpyxl/reader/__init__.py index a45d67e..9b0ee2f 100644 --- a/tablib/packages/openpyxl/reader/__init__.py +++ b/tablib/packages/openpyxl/reader/__init__.py @@ -26,8 +26,8 @@ """Imports for the openpyxl.reader namespace.""" # package imports -from openpyxl.reader import excel -from openpyxl.reader import strings -from openpyxl.reader import style -from openpyxl.reader import workbook -from openpyxl.reader import worksheet +from ..reader import excel +from ..reader import strings +from ..reader import style +from ..reader import workbook +from ..reader import worksheet diff --git a/tablib/packages/openpyxl/reader/excel.py b/tablib/packages/openpyxl/reader/excel.py index ba633e6..3dc6547 100644 --- a/tablib/packages/openpyxl/reader/excel.py +++ b/tablib/packages/openpyxl/reader/excel.py @@ -29,32 +29,32 @@ from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile # package imports -from openpyxl.shared.exc import OpenModeError, InvalidFileException -from openpyxl.shared.ooxml import ARC_SHARED_STRINGS, ARC_CORE, ARC_APP, \ +from ..shared.exc import OpenModeError, InvalidFileException +from ..shared.ooxml import ARC_SHARED_STRINGS, ARC_CORE, ARC_APP, \ ARC_WORKBOOK, PACKAGE_WORKSHEETS, ARC_STYLE -from openpyxl.workbook import Workbook -from openpyxl.reader.strings import read_string_table -from openpyxl.reader.style import read_style_table -from openpyxl.reader.workbook import read_sheets_titles, read_named_ranges, \ +from ..workbook import Workbook +from ..reader.strings import read_string_table +from ..reader.style import read_style_table +from ..reader.workbook import read_sheets_titles, read_named_ranges, \ read_properties_core, get_sheet_ids -from openpyxl.reader.worksheet import read_worksheet -from openpyxl.reader.iter_worksheet import unpack_worksheet +from ..reader.worksheet import read_worksheet +from ..reader.iter_worksheet import unpack_worksheet def load_workbook(filename, use_iterators = False): """Open the given filename and return the workbook :param filename: the path to open :type filename: string - + :param use_iterators: use lazy load for cells :type use_iterators: bool :rtype: :class:`openpyxl.workbook.Workbook` - + .. note:: - + When using lazy load, all worksheets will be :class:`openpyxl.reader.iter_worksheet.IterableWorksheet` - and the returned workbook will be read-only. + and the returned workbook will be read-only. """ diff --git a/tablib/packages/openpyxl/reader/iter_worksheet.py b/tablib/packages/openpyxl/reader/iter_worksheet.py index f8e9399..926116d 100644 --- a/tablib/packages/openpyxl/reader/iter_worksheet.py +++ b/tablib/packages/openpyxl/reader/iter_worksheet.py @@ -23,7 +23,7 @@ # @license: http://www.opensource.org/licenses/mit-license.php # @author: Eric Gazoni -""" Iterators-based worksheet reader +""" Iterators-based worksheet reader *Still very raw* """ @@ -32,18 +32,18 @@ import warnings import operator from functools import partial from itertools import ifilter, groupby -from openpyxl.worksheet import Worksheet -from openpyxl.cell import coordinate_from_string, get_column_letter, Cell -from openpyxl.reader.excel import get_sheet_ids -from openpyxl.reader.strings import read_string_table -from openpyxl.reader.style import read_style_table, NumberFormat -from openpyxl.shared.date_time import SharedDate -from openpyxl.reader.worksheet import read_dimension -from openpyxl.shared.ooxml import (MIN_COLUMN, MAX_COLUMN, PACKAGE_WORKSHEETS, +from ..worksheet import Worksheet +from ..cell import coordinate_from_string, get_column_letter, Cell +from ..reader.excel import get_sheet_ids +from ..reader.strings import read_string_table +from ..reader.style import read_style_table, NumberFormat +from ..shared.date_time import SharedDate +from ..reader.worksheet import read_dimension +from ..shared.ooxml import (MIN_COLUMN, MAX_COLUMN, PACKAGE_WORKSHEETS, MAX_ROW, MIN_ROW, ARC_SHARED_STRINGS, ARC_APP, ARC_STYLE) from xml.etree.cElementTree import iterparse from zipfile import ZipFile -import openpyxl.cell +from .. import cell import re import tempfile import zlib @@ -51,7 +51,7 @@ import zipfile import struct TYPE_NULL = Cell.TYPE_NULL -MISSING_VALUE = None +MISSING_VALUE = None RE_COORDINATE = re.compile('^([A-Z]+)([0-9]+)$') @@ -108,11 +108,11 @@ class RawCell(BaseRawCell): def is_date(self): res = (self.data_type == Cell.TYPE_NUMERIC and self.number_format is not None - and ('d' in self.number_format + and ('d' in self.number_format or 'm' in self.number_format or 'y' in self.number_format or 'h' in self.number_format - or 's' in self.number_format + or 's' in self.number_format )) return res @@ -121,7 +121,7 @@ def iter_rows(workbook_name, sheet_name, xml_source, range_string = '', row_offs archive = get_archive_file(workbook_name) - source = xml_source + source = xml_source if range_string: min_col, min_row, max_col, max_row = get_range_boundaries(range_string, row_offset, column_offset) @@ -187,7 +187,7 @@ def get_range_boundaries(range_string, row = 0, column = 0): min_col, min_row = coordinate_from_string(range_string) min_col = column_index_from_string(min_col) max_col = min_col + 1 - max_row = min_row + max_row = min_row return (min_col, min_row, max_col, max_row) @@ -215,7 +215,7 @@ def get_squared_range(p, min_col, min_row, max_col, max_row, string_table, style for gap_row in xrange(current_row, row): dummy_cells = get_missing_cells(gap_row, expected_columns) - + yield tuple([dummy_cells[column] for column in expected_columns]) current_row = row @@ -254,11 +254,11 @@ def get_squared_range(p, min_col, min_row, max_col, max_row, string_table, style yield tuple(full_row) -#------------------------------------------------------------------------------ +#------------------------------------------------------------------------------ class IterableWorksheet(Worksheet): - def __init__(self, parent_workbook, title, workbook_name, + def __init__(self, parent_workbook, title, workbook_name, sheet_codename, xml_source): Worksheet.__init__(self, parent_workbook, title) @@ -267,20 +267,20 @@ class IterableWorksheet(Worksheet): self._xml_source = xml_source def iter_rows(self, range_string = '', row_offset = 0, column_offset = 0): - """ Returns a squared range based on the `range_string` parameter, + """ Returns a squared range based on the `range_string` parameter, using generators. - + :param range_string: range of cells (e.g. 'A1:C4') :type range_string: string - + :param row: row index of the cell (e.g. 4) :type row: int :param column: column index of the cell (e.g. 3) :type column: int - + :rtype: generator - + """ return iter_rows(workbook_name = self._workbook_name, diff --git a/tablib/packages/openpyxl/reader/strings.py b/tablib/packages/openpyxl/reader/strings.py index 525d502..e19e291 100644 --- a/tablib/packages/openpyxl/reader/strings.py +++ b/tablib/packages/openpyxl/reader/strings.py @@ -26,8 +26,8 @@ """Read the shared strings table.""" # package imports -from openpyxl.shared.xmltools import fromstring, QName -from openpyxl.shared.ooxml import NAMESPACES +from ..shared.xmltools import fromstring, QName +from ..shared.ooxml import NAMESPACES def read_string_table(xml_source): diff --git a/tablib/packages/openpyxl/reader/style.py b/tablib/packages/openpyxl/reader/style.py index 1286f9a..f773070 100644 --- a/tablib/packages/openpyxl/reader/style.py +++ b/tablib/packages/openpyxl/reader/style.py @@ -26,9 +26,9 @@ """Read shared style definitions""" # package imports -from openpyxl.shared.xmltools import fromstring, QName -from openpyxl.shared.exc import MissingNumberFormat -from openpyxl.style import Style, NumberFormat +from ..shared.xmltools import fromstring, QName +from ..shared.exc import MissingNumberFormat +from ..style import Style, NumberFormat def read_style_table(xml_source): diff --git a/tablib/packages/openpyxl/reader/workbook.py b/tablib/packages/openpyxl/reader/workbook.py index 3419a31..d9bc161 100644 --- a/tablib/packages/openpyxl/reader/workbook.py +++ b/tablib/packages/openpyxl/reader/workbook.py @@ -26,11 +26,11 @@ """Read in global settings to be maintained by the workbook object.""" # package imports -from openpyxl.shared.xmltools import fromstring, QName -from openpyxl.shared.ooxml import NAMESPACES -from openpyxl.workbook import DocumentProperties -from openpyxl.shared.date_time import W3CDTF_to_datetime -from openpyxl.namedrange import NamedRange, split_named_range +from ..shared.xmltools import fromstring, QName +from ..shared.ooxml import NAMESPACES +from ..workbook import DocumentProperties +from ..shared.date_time import W3CDTF_to_datetime +from ..namedrange import NamedRange, split_named_range import datetime diff --git a/tablib/packages/openpyxl/reader/worksheet.py b/tablib/packages/openpyxl/reader/worksheet.py index f7f3a73..722b6fd 100644 --- a/tablib/packages/openpyxl/reader/worksheet.py +++ b/tablib/packages/openpyxl/reader/worksheet.py @@ -34,12 +34,12 @@ from itertools import ifilter from StringIO import StringIO # package imports -from openpyxl.cell import Cell, coordinate_from_string -from openpyxl.worksheet import Worksheet +from ..cell import Cell, coordinate_from_string +from ..worksheet import Worksheet def _get_xml_iter(xml_source): - if not hasattr(xml_source, 'name'): + if not hasattr(xml_source, 'name'): return StringIO(xml_source) else: xml_source.seek(0) @@ -47,7 +47,7 @@ def _get_xml_iter(xml_source): def read_dimension(xml_source): - source = _get_xml_iter(xml_source) + source = _get_xml_iter(xml_source) it = iterparse(source) @@ -73,7 +73,7 @@ def filter_cells((event, element)): def fast_parse(ws, xml_source, string_table, style_table): - source = _get_xml_iter(xml_source) + source = _get_xml_iter(xml_source) it = iterparse(source) @@ -98,13 +98,13 @@ def fast_parse(ws, xml_source, string_table, style_table): # to avoid memory exhaustion, clear the item after use element.clear() -from openpyxl.reader.iter_worksheet import IterableWorksheet +from ..reader.iter_worksheet import IterableWorksheet def read_worksheet(xml_source, parent, preset_title, string_table, style_table, workbook_name = None, sheet_codename = None): """Read an xml worksheet""" if workbook_name and sheet_codename: - ws = IterableWorksheet(parent, preset_title, workbook_name, + ws = IterableWorksheet(parent, preset_title, workbook_name, sheet_codename, xml_source) else: ws = Worksheet(parent, preset_title) diff --git a/tablib/packages/openpyxl/shared/__init__.py b/tablib/packages/openpyxl/shared/__init__.py index ef0f171..8b560df 100644 --- a/tablib/packages/openpyxl/shared/__init__.py +++ b/tablib/packages/openpyxl/shared/__init__.py @@ -26,8 +26,8 @@ """Imports for the openpyxl.shared namespace.""" # package imports -from openpyxl.shared import date_time -from openpyxl.shared import exc -from openpyxl.shared import ooxml -from openpyxl.shared import password_hasher -from openpyxl.shared import xmltools +from . import date_time +from . import exc +from . import ooxml +from . import password_hasher +from . import xmltools diff --git a/tablib/packages/openpyxl/shared/xmltools.py b/tablib/packages/openpyxl/shared/xmltools.py index 9f0f943..275cfca 100644 --- a/tablib/packages/openpyxl/shared/xmltools.py +++ b/tablib/packages/openpyxl/shared/xmltools.py @@ -41,7 +41,7 @@ except ImportError: QName, fromstring, tostring # package imports -from openpyxl import __name__ as prefix +from .. import __name__ as prefix def get_document_content(xml_node): diff --git a/tablib/packages/openpyxl/workbook.py b/tablib/packages/openpyxl/workbook.py index 10a4564..bbb14b6 100644 --- a/tablib/packages/openpyxl/workbook.py +++ b/tablib/packages/openpyxl/workbook.py @@ -32,13 +32,13 @@ import datetime import os # package imports -from openpyxl.worksheet import Worksheet -from openpyxl.writer.dump_worksheet import DumpWorksheet, save_dump -from openpyxl.writer.strings import StringTableBuilder -from openpyxl.namedrange import NamedRange -from openpyxl.style import Style -from openpyxl.writer.excel import save_workbook -from openpyxl.shared.exc import ReadOnlyWorkbookException +from .worksheet import Worksheet +from .writer.dump_worksheet import DumpWorksheet, save_dump +from .writer.strings import StringTableBuilder +from .namedrange import NamedRange +from .style import Style +from .writer.excel import save_workbook +from .shared.exc import ReadOnlyWorkbookException class DocumentProperties(object): diff --git a/tablib/packages/openpyxl/worksheet.py b/tablib/packages/openpyxl/worksheet.py index bfc873c..ea5e81b 100644 --- a/tablib/packages/openpyxl/worksheet.py +++ b/tablib/packages/openpyxl/worksheet.py @@ -29,15 +29,15 @@ import re # package imports -import openpyxl.cell -from openpyxl.cell import coordinate_from_string, \ +from . import cell +from .cell import coordinate_from_string, \ column_index_from_string, get_column_letter -from openpyxl.shared.exc import SheetTitleException, \ +from .shared.exc import SheetTitleException, \ InsufficientCoordinatesException, CellCoordinatesException, \ NamedRangeException -from openpyxl.shared.password_hasher import hash_password -from openpyxl.style import Style, DEFAULTS as DEFAULTS_STYLE -from openpyxl.drawing import Drawing +from .shared.password_hasher import hash_password +from .style import Style, DEFAULTS as DEFAULTS_STYLE +from .drawing import Drawing _DEFAULTS_STYLE_HASH = hash(DEFAULTS_STYLE) @@ -344,7 +344,7 @@ class Worksheet(object): if not coordinate in self._cells: column, row = coordinate_from_string(coordinate) - new_cell = openpyxl.cell.Cell(self, column, row) + new_cell = cell.Cell(self, column, row) self._cells[coordinate] = new_cell if column not in self.column_dimensions: self.column_dimensions[column] = ColumnDimension(column) @@ -354,7 +354,7 @@ class Worksheet(object): def get_highest_row(self): """Returns the maximum row index containing data - + :rtype: int """ if self.row_dimensions: @@ -364,7 +364,7 @@ class Worksheet(object): def get_highest_column(self): """Get the largest value for column currently stored. - + :rtype: int """ if self.column_dimensions: @@ -475,21 +475,21 @@ class Worksheet(object): def append(self, list_or_dict): """Appends a group of values at the bottom of the current sheet. - + * If it's a list: all values are added in order, starting from the first column * If it's a dict: values are assigned to the columns indicated by the keys (numbers or letters) - + :param list_or_dict: list or dict containing values to append :type list_or_dict: list/tuple or dict - + Usage: - + * append(['This is A1', 'This is B1', 'This is C1']) * **or** append({'A' : 'This is A1', 'C' : 'This is C1'}) * **or** append({0 : 'This is A1', 2 : 'This is C1'}) - + :raise: TypeError when list_or_dict is neither a list/tuple nor a dict - + """ row_idx = len(self.row_dimensions) diff --git a/tablib/packages/openpyxl/writer/__init__.py b/tablib/packages/openpyxl/writer/__init__.py index fff1f92..9eb0a21 100644 --- a/tablib/packages/openpyxl/writer/__init__.py +++ b/tablib/packages/openpyxl/writer/__init__.py @@ -26,9 +26,9 @@ """Imports for the openpyxl.writer namespace.""" # package imports -from openpyxl.writer import excel -from openpyxl.writer import strings -from openpyxl.writer import styles -from openpyxl.writer import theme -from openpyxl.writer import workbook -from openpyxl.writer import worksheet +from . import excel +from . import strings +from . import styles +from . import theme +from . import workbook +from . import worksheet diff --git a/tablib/packages/openpyxl/writer/charts.py b/tablib/packages/openpyxl/writer/charts.py index 1d3dbd9..b5f4570 100644 --- a/tablib/packages/openpyxl/writer/charts.py +++ b/tablib/packages/openpyxl/writer/charts.py @@ -24,33 +24,33 @@ THE SOFTWARE. @author: Eric Gazoni ''' -from openpyxl.shared.xmltools import Element, SubElement, get_document_content -from openpyxl.chart import Chart, ErrorBar +from ..shared.xmltools import Element, SubElement, get_document_content +from ..chart import Chart, ErrorBar class ChartWriter(object): - + def __init__(self, chart): self.chart = chart - + def write(self): """ write a chart """ - - root = Element('c:chartSpace', + + root = Element('c:chartSpace', {'xmlns:c':"http://schemas.openxmlformats.org/drawingml/2006/chart", 'xmlns:a':"http://schemas.openxmlformats.org/drawingml/2006/main", 'xmlns:r':"http://schemas.openxmlformats.org/officeDocument/2006/relationships"}) - + SubElement(root, 'c:lang', {'val':self.chart.lang}) self._write_chart(root) self._write_print_settings(root) self._write_shapes(root) return get_document_content(root) - + def _write_chart(self, root): - + chart = self.chart - + ch = SubElement(root, 'c:chart') self._write_title(ch) plot_area = SubElement(ch, 'c:plotArea') @@ -63,7 +63,7 @@ class ChartWriter(object): SubElement(mlayout, 'c:y', {'val':str(chart._get_margin_top())}) SubElement(mlayout, 'c:w', {'val':str(chart.width)}) SubElement(mlayout, 'c:h', {'val':str(chart.height)}) - + if chart.type == Chart.SCATTER_CHART: subchart = SubElement(plot_area, 'c:scatterChart') SubElement(subchart, 'c:scatterStyle', {'val':str('lineMarker')}) @@ -73,23 +73,23 @@ class ChartWriter(object): SubElement(subchart, 'c:barDir', {'val':'col'}) else: subchart = SubElement(plot_area, 'c:lineChart') - + SubElement(subchart, 'c:grouping', {'val':chart.grouping}) - + self._write_series(subchart) - + SubElement(subchart, 'c:marker', {'val':'1'}) SubElement(subchart, 'c:axId', {'val':str(chart.x_axis.id)}) SubElement(subchart, 'c:axId', {'val':str(chart.y_axis.id)}) - + if chart.type == Chart.SCATTER_CHART: self._write_axis(plot_area, chart.x_axis, 'c:valAx') else: self._write_axis(plot_area, chart.x_axis, 'c:catAx') self._write_axis(plot_area, chart.y_axis, 'c:valAx') - + self._write_legend(ch) - + SubElement(ch, 'c:plotVisOnly', {'val':'1'}) def _write_title(self, chart): @@ -108,16 +108,16 @@ class ChartWriter(object): SubElement(title, 'c:layout') def _write_axis(self, plot_area, axis, label): - + ax = SubElement(plot_area, label) SubElement(ax, 'c:axId', {'val':str(axis.id)}) - + scaling = SubElement(ax, 'c:scaling') SubElement(scaling, 'c:orientation', {'val':axis.orientation}) if label == 'c:valAx': SubElement(scaling, 'c:max', {'val':str(axis.max)}) SubElement(scaling, 'c:min', {'val':str(axis.min)}) - + SubElement(ax, 'c:axPos', {'val':axis.position}) if label == 'c:valAx': SubElement(ax, 'c:majorGridlines') @@ -137,18 +137,18 @@ class ChartWriter(object): else: SubElement(ax, 'c:crossBetween', {'val':'between'}) SubElement(ax, 'c:majorUnit', {'val':str(axis.unit)}) - + def _write_series(self, subchart): - + for i, serie in enumerate(self.chart._series): ser = SubElement(subchart, 'c:ser') SubElement(ser, 'c:idx', {'val':str(i)}) SubElement(ser, 'c:order', {'val':str(i)}) - + if serie.legend: tx = SubElement(ser, 'c:tx') self._write_serial(tx, serie.legend) - + if serie.color: sppr = SubElement(ser, 'c:spPr') if self.chart.type == Chart.BAR_CHART: @@ -159,17 +159,17 @@ class ChartWriter(object): ln = SubElement(sppr, 'a:ln') fill = SubElement(ln, 'a:solidFill') SubElement(fill, 'a:srgbClr', {'val':serie.color}) - + if serie.error_bar: self._write_error_bar(ser, serie) - + marker = SubElement(ser, 'c:marker') SubElement(marker, 'c:symbol', {'val':serie.marker}) if serie.labels: cat = SubElement(ser, 'c:cat') self._write_serial(cat, serie.labels) - + if self.chart.type == Chart.SCATTER_CHART: if serie.xvalues: xval = SubElement(ser, 'c:xVal') @@ -180,7 +180,7 @@ class ChartWriter(object): else: val = SubElement(ser, 'c:val') self._write_serial(val, serie.values) - + def _write_serial(self, node, serie, literal=False): cache = serie._get_cache() @@ -188,7 +188,7 @@ class ChartWriter(object): typ = 'str' else: typ = 'num' - + if not literal: if typ == 'num': ref = SubElement(node, 'c:numRef') @@ -208,51 +208,51 @@ class ChartWriter(object): values = (1,) else: values = cache - + SubElement(data, 'c:ptCount', {'val':str(len(values))}) for j, val in enumerate(values): point = SubElement(data, 'c:pt', {'idx':str(j)}) SubElement(point, 'c:v').text = str(val) def _write_error_bar(self, node, serie): - - flag = {ErrorBar.PLUS_MINUS:'both', - ErrorBar.PLUS:'plus', + + flag = {ErrorBar.PLUS_MINUS:'both', + ErrorBar.PLUS:'plus', ErrorBar.MINUS:'minus'} - + eb = SubElement(node, 'c:errBars') SubElement(eb, 'c:errBarType', {'val':flag[serie.error_bar.type]}) SubElement(eb, 'c:errValType', {'val':'cust'}) - + plus = SubElement(eb, 'c:plus') self._write_serial(plus, serie.error_bar.values, literal=(serie.error_bar.type==ErrorBar.MINUS)) - + minus = SubElement(eb, 'c:minus') self._write_serial(minus, serie.error_bar.values, literal=(serie.error_bar.type==ErrorBar.PLUS)) - + def _write_legend(self, chart): - + legend = SubElement(chart, 'c:legend') SubElement(legend, 'c:legendPos', {'val':self.chart.legend.position}) SubElement(legend, 'c:layout') - + def _write_print_settings(self, root): - + settings = SubElement(root, 'c:printSettings') SubElement(settings, 'c:headerFooter') margins = dict([(k, str(v)) for (k,v) in self.chart.print_margins.iteritems()]) SubElement(settings, 'c:pageMargins', margins) SubElement(settings, 'c:pageSetup') - + def _write_shapes(self, root): - + if self.chart._shapes: SubElement(root, 'c:userShapes', {'r:id':'rId1'}) - + def write_rels(self, drawing_id): - + root = Element('Relationships', {'xmlns' : 'http://schemas.openxmlformats.org/package/2006/relationships'}) attrs = {'Id' : 'rId1', 'Type' : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUserShapes', diff --git a/tablib/packages/openpyxl/writer/drawings.py b/tablib/packages/openpyxl/writer/drawings.py index e6e5b30..8a6cce2 100644 --- a/tablib/packages/openpyxl/writer/drawings.py +++ b/tablib/packages/openpyxl/writer/drawings.py @@ -24,7 +24,7 @@ THE SOFTWARE. @author: Eric Gazoni ''' -from openpyxl.shared.xmltools import Element, SubElement, get_document_content +from ..shared.xmltools import Element, SubElement, get_document_content class DrawingWriter(object): diff --git a/tablib/packages/openpyxl/writer/dump_worksheet.py b/tablib/packages/openpyxl/writer/dump_worksheet.py index da4ef56..7f098f5 100644 --- a/tablib/packages/openpyxl/writer/dump_worksheet.py +++ b/tablib/packages/openpyxl/writer/dump_worksheet.py @@ -28,19 +28,19 @@ import datetime import os -from openpyxl.cell import column_index_from_string, get_column_letter, Cell -from openpyxl.worksheet import Worksheet -from openpyxl.shared.xmltools import XMLGenerator, get_document_content, \ +from ..cell import column_index_from_string, get_column_letter, Cell +from ..worksheet import Worksheet +from ..shared.xmltools import XMLGenerator, get_document_content, \ start_tag, end_tag, tag -from openpyxl.shared.date_time import SharedDate -from openpyxl.shared.ooxml import MAX_COLUMN, MAX_ROW +from ..shared.date_time import SharedDate +from ..shared.ooxml import MAX_COLUMN, MAX_ROW from tempfile import NamedTemporaryFile -from openpyxl.writer.excel import ExcelWriter -from openpyxl.writer.strings import write_string_table -from openpyxl.writer.styles import StyleWriter -from openpyxl.style import Style, NumberFormat +from ..writer.excel import ExcelWriter +from ..writer.strings import write_string_table +from ..writer.styles import StyleWriter +from ..style import Style, NumberFormat -from openpyxl.shared.ooxml import ARC_SHARED_STRINGS, ARC_CONTENT_TYPES, \ +from ..shared.ooxml import ARC_SHARED_STRINGS, ARC_CONTENT_TYPES, \ ARC_ROOT_RELS, ARC_WORKBOOK_RELS, ARC_APP, ARC_CORE, ARC_THEME, \ ARC_STYLE, ARC_WORKBOOK, \ PACKAGE_WORKSHEETS, PACKAGE_DRAWINGS, PACKAGE_CHARTS @@ -58,7 +58,7 @@ STYLES = {'datetime' : {'type':Cell.TYPE_NUMERIC, } DATETIME_STYLE = Style() -DATETIME_STYLE.number_format.format_code = NumberFormat.FORMAT_DATE_YYYYMMDD2 +DATETIME_STYLE.number_format.format_code = NumberFormat.FORMAT_DATE_YYYYMMDD2 BOUNDING_BOX_PLACEHOLDER = 'A1:%s%d' % (get_column_letter(MAX_COLUMN), MAX_ROW) class DumpWorksheet(Worksheet): @@ -66,7 +66,7 @@ class DumpWorksheet(Worksheet): """ .. warning:: - You shouldn't initialize this yourself, use :class:`openpyxl.workbook.Workbook` constructor instead, + You shouldn't initialize this yourself, use :class:`openpyxl.workbook.Workbook` constructor instead, with `optimized_write = True`. """ @@ -101,7 +101,7 @@ class DumpWorksheet(Worksheet): 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}) start_tag(doc, 'sheetPr') tag(doc, 'outlinePr', - {'summaryBelow': '1', + {'summaryBelow': '1', 'summaryRight': '1'}) end_tag(doc, 'sheetPr') tag(doc, 'dimension', {'ref': 'A1:%s' % (self.get_dimensions())}) @@ -141,7 +141,7 @@ class DumpWorksheet(Worksheet): self._fileobj.flush() def _close_header(self): - + doc = self.header #doc.endDocument() @@ -159,7 +159,7 @@ class DumpWorksheet(Worksheet): return 'A1' else: return '%s%d' % (get_column_letter(self._max_col), (self._max_row)) - + def append(self, row): """ @@ -185,7 +185,7 @@ class DumpWorksheet(Worksheet): if cell is None: continue - coordinate = '%s%d' % (get_column_letter(col_idx+1), row_idx) + coordinate = '%s%d' % (get_column_letter(col_idx+1), row_idx) attributes = {'r': coordinate} if isinstance(cell, bool): @@ -211,7 +211,7 @@ class DumpWorksheet(Worksheet): tag(doc, 'v') else: tag(doc, 'v', body = '%s' % cell) - + end_tag(doc, 'c') @@ -253,4 +253,4 @@ class StyleDumpWriter(StyleWriter): def _get_style_list(self, workbook): return [] - + diff --git a/tablib/packages/openpyxl/writer/excel.py b/tablib/packages/openpyxl/writer/excel.py index 192716e..1e915d9 100644 --- a/tablib/packages/openpyxl/writer/excel.py +++ b/tablib/packages/openpyxl/writer/excel.py @@ -30,19 +30,19 @@ from zipfile import ZipFile, ZIP_DEFLATED from StringIO import StringIO # package imports -from openpyxl.shared.ooxml import ARC_SHARED_STRINGS, ARC_CONTENT_TYPES, \ +from ..shared.ooxml import ARC_SHARED_STRINGS, ARC_CONTENT_TYPES, \ ARC_ROOT_RELS, ARC_WORKBOOK_RELS, ARC_APP, ARC_CORE, ARC_THEME, \ ARC_STYLE, ARC_WORKBOOK, \ PACKAGE_WORKSHEETS, PACKAGE_DRAWINGS, PACKAGE_CHARTS -from openpyxl.writer.strings import create_string_table, write_string_table -from openpyxl.writer.workbook import write_content_types, write_root_rels, \ +from ..writer.strings import create_string_table, write_string_table +from ..writer.workbook import write_content_types, write_root_rels, \ write_workbook_rels, write_properties_app, write_properties_core, \ write_workbook -from openpyxl.writer.theme import write_theme -from openpyxl.writer.styles import StyleWriter -from openpyxl.writer.drawings import DrawingWriter, ShapeWriter -from openpyxl.writer.charts import ChartWriter -from openpyxl.writer.worksheet import write_worksheet, write_worksheet_rels +from ..writer.theme import write_theme +from ..writer.styles import StyleWriter +from ..writer.drawings import DrawingWriter, ShapeWriter +from ..writer.charts import ChartWriter +from ..writer.worksheet import write_worksheet, write_worksheet_rels class ExcelWriter(object): @@ -56,7 +56,7 @@ class ExcelWriter(object): """Write the various xml files into the zip archive.""" # cleanup all worksheets shared_string_table = self._write_string_table(archive) - + archive.writestr(ARC_CONTENT_TYPES, write_content_types(self.workbook)) archive.writestr(ARC_ROOT_RELS, write_root_rels(self.workbook)) archive.writestr(ARC_WORKBOOK_RELS, write_workbook_rels(self.workbook)) diff --git a/tablib/packages/openpyxl/writer/strings.py b/tablib/packages/openpyxl/writer/strings.py index 245c48b..1359e93 100644 --- a/tablib/packages/openpyxl/writer/strings.py +++ b/tablib/packages/openpyxl/writer/strings.py @@ -29,7 +29,7 @@ from StringIO import StringIO # package imports -from openpyxl.shared.xmltools import start_tag, end_tag, tag, XMLGenerator +from ..shared.xmltools import start_tag, end_tag, tag, XMLGenerator def create_string_table(workbook): diff --git a/tablib/packages/openpyxl/writer/styles.py b/tablib/packages/openpyxl/writer/styles.py index 7bf91dd..fb04343 100644 --- a/tablib/packages/openpyxl/writer/styles.py +++ b/tablib/packages/openpyxl/writer/styles.py @@ -26,17 +26,17 @@ """Write the shared style table.""" # package imports -from openpyxl.shared.xmltools import Element, SubElement -from openpyxl.shared.xmltools import get_document_content -from openpyxl import style +from ..shared.xmltools import Element, SubElement +from ..shared.xmltools import get_document_content +from .. import style class StyleWriter(object): - + def __init__(self, workbook): self._style_list = self._get_style_list(workbook) - self._root = Element('styleSheet', + self._root = Element('styleSheet', {'xmlns':'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}) - + def _get_style_list(self, workbook): crc = {} for worksheet in workbook.worksheets: @@ -51,7 +51,7 @@ class StyleWriter(object): def get_style_by_hash(self): return dict([(hash(style), id) \ for style, id in self.style_table.iteritems()]) - + def write_table(self): number_format_table = self._write_number_formats() fonts_table = self._write_fonts() @@ -71,7 +71,7 @@ class StyleWriter(object): """ fonts = SubElement(self._root, 'fonts') - + # default font_node = SubElement(fonts, 'font') SubElement(font_node, 'sz', {'val':'11'}) @@ -79,7 +79,7 @@ class StyleWriter(object): SubElement(font_node, 'name', {'val':'Calibri'}) SubElement(font_node, 'family', {'val':'2'}) SubElement(font_node, 'scheme', {'val':'minor'}) - + # others table = {} index = 1 @@ -97,7 +97,7 @@ class StyleWriter(object): if st.font.italic: SubElement(font_node, 'i') index += 1 - + fonts.attrib["count"] = str(index) return table @@ -122,7 +122,7 @@ class StyleWriter(object): if hash(st.fill.end_color) != hash(style.DEFAULTS.fill.end_color): SubElement(node, 'bgColor', {'rgb':str(st.fill.start_color.index)}) index += 1 - + fills.attrib["count"] = str(index) return table @@ -136,7 +136,7 @@ class StyleWriter(object): SubElement(border, 'top') SubElement(border, 'bottom') SubElement(border, 'diagonal') - + # others table = {} index = 1 @@ -150,40 +150,40 @@ class StyleWriter(object): node = SubElement(border, side, {'style':obj.border_style}) SubElement(node, 'color', {'rgb':str(obj.color.index)}) index += 1 - + borders.attrib["count"] = str(index) return table def _write_cell_style_xfs(self): cell_style_xfs = SubElement(self._root, 'cellStyleXfs', {'count':'1'}) - xf = SubElement(cell_style_xfs, 'xf', + xf = SubElement(cell_style_xfs, 'xf', {'numFmtId':"0", 'fontId':"0", 'fillId':"0", 'borderId':"0"}) - + def _write_cell_xfs(self, number_format_table, fonts_table, fills_table, borders_table): """ write styles combinations based on ids found in tables """ - + # writing the cellXfs - cell_xfs = SubElement(self._root, 'cellXfs', + cell_xfs = SubElement(self._root, 'cellXfs', {'count':'%d' % (len(self._style_list) + 1)}) - + # default def _get_default_vals(): - return dict(numFmtId='0', fontId='0', fillId='0', + return dict(numFmtId='0', fontId='0', fillId='0', xfId='0', borderId='0') - + SubElement(cell_xfs, 'xf', _get_default_vals()) - + for st in self._style_list: vals = _get_default_vals() - + if hash(st.font) != hash(style.DEFAULTS.font): vals['fontId'] = fonts_table[hash(st.font)] vals['applyFont'] = '1' - + if hash(st.borders) != hash(style.DEFAULTS.borders): vals['borderId'] = borders_table[hash(st.borders)] vals['applyBorder'] = '1' - + if hash(st.fill) != hash(style.DEFAULTS.fill): vals['fillId'] = fills_table[hash(st.fill)] vals['applyFillId'] = '1' @@ -191,7 +191,7 @@ class StyleWriter(object): if st.number_format != style.DEFAULTS.number_format: vals['numFmtId'] = '%d' % number_format_table[st.number_format] vals['applyNumberFormat'] = '1' - + if hash(st.alignment) != hash(style.DEFAULTS.alignment): vals['applyAlignment'] = '1' @@ -209,7 +209,7 @@ class StyleWriter(object): def _write_cell_style(self): cell_styles = SubElement(self._root, 'cellStyles', {'count':'1'}) - cell_style = SubElement(cell_styles, 'cellStyle', + cell_style = SubElement(cell_styles, 'cellStyle', {'name':"Normal", 'xfId':"0", 'builtinId':"0"}) def _write_dxfs(self): @@ -217,7 +217,7 @@ class StyleWriter(object): def _write_table_styles(self): - table_styles = SubElement(self._root, 'tableStyles', + table_styles = SubElement(self._root, 'tableStyles', {'count':'0', 'defaultTableStyle':'TableStyleMedium9', 'defaultPivotStyle':'PivotStyleLight16'}) @@ -245,12 +245,12 @@ class StyleWriter(object): num_fmt_offset += 1 exceptions_list.append(number_format) - num_fmts = SubElement(self._root, 'numFmts', + num_fmts = SubElement(self._root, 'numFmts', {'count':'%d' % len(exceptions_list)}) for number_format in exceptions_list : - SubElement(num_fmts, 'numFmt', + SubElement(num_fmts, 'numFmt', {'numFmtId':'%d' % number_format_table[number_format], - 'formatCode':'%s' % number_format.format_code}) - + 'formatCode':'%s' % number_format.format_code}) + return number_format_table diff --git a/tablib/packages/openpyxl/writer/theme.py b/tablib/packages/openpyxl/writer/theme.py index 4b1723b..80700f2 100644 --- a/tablib/packages/openpyxl/writer/theme.py +++ b/tablib/packages/openpyxl/writer/theme.py @@ -27,7 +27,7 @@ """Write the theme xml based on a fixed string.""" # package imports -from openpyxl.shared.xmltools import fromstring, get_document_content +from ..shared.xmltools import fromstring, get_document_content def write_theme(): diff --git a/tablib/packages/openpyxl/writer/workbook.py b/tablib/packages/openpyxl/writer/workbook.py index 5d05b31..e7b390c 100644 --- a/tablib/packages/openpyxl/writer/workbook.py +++ b/tablib/packages/openpyxl/writer/workbook.py @@ -26,12 +26,12 @@ """Write the workbook global settings to the archive.""" # package imports -from openpyxl.shared.xmltools import Element, SubElement -from openpyxl.cell import absolute_coordinate -from openpyxl.shared.xmltools import get_document_content -from openpyxl.shared.ooxml import NAMESPACES, ARC_CORE, ARC_WORKBOOK, \ +from ..shared.xmltools import Element, SubElement +from ..cell import absolute_coordinate +from ..shared.xmltools import get_document_content +from ..shared.ooxml import NAMESPACES, ARC_CORE, ARC_WORKBOOK, \ ARC_APP, ARC_THEME, ARC_STYLE, ARC_SHARED_STRINGS -from openpyxl.shared.date_time import datetime_to_W3CDTF +from ..shared.date_time import datetime_to_W3CDTF def write_properties_core(properties): diff --git a/tablib/packages/openpyxl/writer/worksheet.py b/tablib/packages/openpyxl/writer/worksheet.py index ed85fcb..d9c493a 100644 --- a/tablib/packages/openpyxl/writer/worksheet.py +++ b/tablib/packages/openpyxl/writer/worksheet.py @@ -29,8 +29,8 @@ from StringIO import StringIO # cStringIO doesn't handle unicode # package imports -from openpyxl.cell import coordinate_from_string, column_index_from_string -from openpyxl.shared.xmltools import Element, SubElement, XMLGenerator, \ +from ..cell import coordinate_from_string, column_index_from_string +from ..shared.xmltools import Element, SubElement, XMLGenerator, \ get_document_content, start_tag, end_tag, tag @@ -100,7 +100,7 @@ def write_worksheet_sheetviews(doc, worksheet): tag(doc, 'selection', selectionAttrs) end_tag(doc, 'sheetView') end_tag(doc, 'sheetViews') - + def write_worksheet_cols(doc, worksheet): """Write worksheet columns to xml.""" From 690de63b7c0c1431151c308c67f8d344f15d6a65 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 12 May 2011 19:16:24 -0400 Subject: [PATCH 13/22] ugh --- tablib/packages/openpyxl/cell.py | 3 +- tablib/packages/openpyxl/drawing.py | 2 +- tablib/packages/openpyxl/namedrange.py | 2 +- tablib/packages/openpyxl/reader/excel.py | 10 +- .../openpyxl/reader/iter_worksheet.py | 7 +- tablib/packages/openpyxl/reader/worksheet.py | 8 +- tablib/packages/openpyxl/shared/xmltools.py | 20 +- tablib/packages/openpyxl/style.py | 2 +- tablib/packages/openpyxl/tests/test_dump.py | 6 +- tablib/packages/openpyxl/worksheet.py | 6 +- tablib/packages/openpyxl/writer/charts.py | 2 +- tablib/packages/openpyxl/writer/excel.py | 7 +- tablib/packages/openpyxl/writer/strings.py | 4 +- tablib/packages/openpyxl/writer/styles.py | 4 +- tablib/packages/openpyxl/writer/worksheet.py | 4 +- tablib/packages/openpyxl3/__init__.py | 53 ++ tablib/packages/openpyxl3/cell.py | 384 +++++++++++++ tablib/packages/openpyxl3/chart.py | 340 +++++++++++ tablib/packages/openpyxl3/drawing.py | 402 +++++++++++++ tablib/packages/openpyxl3/namedrange.py | 68 +++ tablib/packages/openpyxl3/reader/__init__.py | 33 ++ tablib/packages/openpyxl3/reader/excel.py | 117 ++++ .../openpyxl3/reader/iter_worksheet.py | 343 +++++++++++ tablib/packages/openpyxl3/reader/strings.py | 64 +++ tablib/packages/openpyxl3/reader/style.py | 69 +++ tablib/packages/openpyxl3/reader/workbook.py | 156 +++++ tablib/packages/openpyxl3/reader/worksheet.py | 112 ++++ tablib/packages/openpyxl3/shared/__init__.py | 33 ++ tablib/packages/openpyxl3/shared/date_time.py | 154 +++++ tablib/packages/openpyxl3/shared/exc.py | 59 ++ tablib/packages/openpyxl3/shared/ooxml.py | 60 ++ .../openpyxl3/shared/password_hasher.py | 47 ++ tablib/packages/openpyxl3/shared/units.py | 67 +++ tablib/packages/openpyxl3/shared/xmltools.py | 96 ++++ tablib/packages/openpyxl3/style.py | 392 +++++++++++++ tablib/packages/openpyxl3/tests/__init__.py | 24 + tablib/packages/openpyxl3/tests/helper.py | 94 +++ tablib/packages/openpyxl3/tests/test_cell.py | 200 +++++++ tablib/packages/openpyxl3/tests/test_chart.py | 131 +++++ .../test_data/genuine/empty-no-string.xlsx | Bin 0 -> 7842 bytes .../test_data/genuine/empty-with-styles.xlsx | Bin 0 -> 7511 bytes .../tests/test_data/genuine/empty.xlsx | Bin 0 -> 11005 bytes .../tests/test_data/genuine/merge_range.xlsx | Bin 0 -> 8742 bytes .../test_data/reader/app-multi-titles.xml | 43 ++ .../reader/empty-workbook-styles.xml | 53 ++ .../tests/test_data/reader/merged-ranges.xml | 2 + .../tests/test_data/reader/null_archive.xlsx | Bin 0 -> 126 bytes .../tests/test_data/reader/null_file.xlsx | 0 .../test_data/reader/shared-strings-rich.xml | 33 ++ .../reader/sharedStrings-emptystring.xml | 3 + .../tests/test_data/reader/sharedStrings.xml | 2 + .../tests/test_data/reader/sheet2.xml | 2 + .../tests/test_data/reader/simple-styles.xml | 46 ++ .../tests/test_data/reader/workbook.xml | 2 + .../tests/test_data/writer/expected/.rels | 2 + .../writer/expected/[Content_Types].xml | 31 + .../tests/test_data/writer/expected/app.xml | 2 + .../tests/test_data/writer/expected/core.xml | 2 + .../tests/test_data/writer/expected/font.xml | 10 + .../writer/expected/sharedStrings.xml | 2 + .../test_data/writer/expected/sheet1.xml | 23 + .../writer/expected/sheet1_auto_filter.xml | 1 + .../writer/expected/sheet1_formula.xml | 33 ++ .../expected/sheet1_freeze_panes_both.xml | 26 + .../expected/sheet1_freeze_panes_horiz.xml | 24 + .../expected/sheet1_freeze_panes_vert.xml | 24 + .../writer/expected/sheet1_height.xml | 23 + .../writer/expected/sheet1_hyperlink.xml | 27 + .../writer/expected/sheet1_hyperlink.xml.rels | 6 + .../writer/expected/sheet1_style.xml | 1 + .../writer/expected/simple-styles.xml | 46 ++ .../test_data/writer/expected/styles.xml | 2 + .../test_data/writer/expected/theme1.xml | 2 + .../test_data/writer/expected/workbook.xml | 2 + .../writer/expected/workbook.xml.rels | 2 + tablib/packages/openpyxl3/tests/test_dump.py | 109 ++++ tablib/packages/openpyxl3/tests/test_iter.py | 112 ++++ tablib/packages/openpyxl3/tests/test_meta.py | 50 ++ .../openpyxl3/tests/test_named_range.py | 102 ++++ .../openpyxl3/tests/test_number_format.py | 142 +++++ .../openpyxl3/tests/test_password_hash.py | 41 ++ tablib/packages/openpyxl3/tests/test_props.py | Bin 0 -> 9060 bytes tablib/packages/openpyxl3/tests/test_read.py | 134 +++++ .../packages/openpyxl3/tests/test_strings.py | 68 +++ tablib/packages/openpyxl3/tests/test_style.py | 181 ++++++ tablib/packages/openpyxl3/tests/test_theme.py | 37 ++ .../packages/openpyxl3/tests/test_workbook.py | 148 +++++ .../openpyxl3/tests/test_worksheet.py | 258 +++++++++ tablib/packages/openpyxl3/tests/test_write.py | 203 +++++++ tablib/packages/openpyxl3/workbook.py | 186 ++++++ tablib/packages/openpyxl3/worksheet.py | 534 ++++++++++++++++++ tablib/packages/openpyxl3/writer/__init__.py | 34 ++ tablib/packages/openpyxl3/writer/charts.py | 261 +++++++++ tablib/packages/openpyxl3/writer/drawings.py | 192 +++++++ .../openpyxl3/writer/dump_worksheet.py | 256 +++++++++ tablib/packages/openpyxl3/writer/excel.py | 156 +++++ tablib/packages/openpyxl3/writer/strings.py | 86 +++ tablib/packages/openpyxl3/writer/styles.py | 256 +++++++++ tablib/packages/openpyxl3/writer/theme.py | 202 +++++++ tablib/packages/openpyxl3/writer/workbook.py | 204 +++++++ tablib/packages/openpyxl3/writer/worksheet.py | 209 +++++++ 101 files changed, 8192 insertions(+), 31 deletions(-) create mode 100644 tablib/packages/openpyxl3/__init__.py create mode 100644 tablib/packages/openpyxl3/cell.py create mode 100644 tablib/packages/openpyxl3/chart.py create mode 100644 tablib/packages/openpyxl3/drawing.py create mode 100644 tablib/packages/openpyxl3/namedrange.py create mode 100644 tablib/packages/openpyxl3/reader/__init__.py create mode 100644 tablib/packages/openpyxl3/reader/excel.py create mode 100644 tablib/packages/openpyxl3/reader/iter_worksheet.py create mode 100644 tablib/packages/openpyxl3/reader/strings.py create mode 100644 tablib/packages/openpyxl3/reader/style.py create mode 100644 tablib/packages/openpyxl3/reader/workbook.py create mode 100644 tablib/packages/openpyxl3/reader/worksheet.py create mode 100644 tablib/packages/openpyxl3/shared/__init__.py create mode 100644 tablib/packages/openpyxl3/shared/date_time.py create mode 100644 tablib/packages/openpyxl3/shared/exc.py create mode 100644 tablib/packages/openpyxl3/shared/ooxml.py create mode 100644 tablib/packages/openpyxl3/shared/password_hasher.py create mode 100644 tablib/packages/openpyxl3/shared/units.py create mode 100644 tablib/packages/openpyxl3/shared/xmltools.py create mode 100644 tablib/packages/openpyxl3/style.py create mode 100644 tablib/packages/openpyxl3/tests/__init__.py create mode 100644 tablib/packages/openpyxl3/tests/helper.py create mode 100644 tablib/packages/openpyxl3/tests/test_cell.py create mode 100644 tablib/packages/openpyxl3/tests/test_chart.py create mode 100644 tablib/packages/openpyxl3/tests/test_data/genuine/empty-no-string.xlsx create mode 100644 tablib/packages/openpyxl3/tests/test_data/genuine/empty-with-styles.xlsx create mode 100644 tablib/packages/openpyxl3/tests/test_data/genuine/empty.xlsx create mode 100644 tablib/packages/openpyxl3/tests/test_data/genuine/merge_range.xlsx create mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/app-multi-titles.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/empty-workbook-styles.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/merged-ranges.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/null_archive.xlsx create mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/null_file.xlsx create mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/shared-strings-rich.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings-emptystring.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/sheet2.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/simple-styles.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/workbook.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/.rels create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/[Content_Types].xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/app.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/core.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/font.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sharedStrings.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_auto_filter.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_formula.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_both.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_horiz.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_vert.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_height.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml.rels create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_style.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/simple-styles.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/styles.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/theme1.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml create mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml.rels create mode 100644 tablib/packages/openpyxl3/tests/test_dump.py create mode 100644 tablib/packages/openpyxl3/tests/test_iter.py create mode 100644 tablib/packages/openpyxl3/tests/test_meta.py create mode 100644 tablib/packages/openpyxl3/tests/test_named_range.py create mode 100644 tablib/packages/openpyxl3/tests/test_number_format.py create mode 100644 tablib/packages/openpyxl3/tests/test_password_hash.py create mode 100644 tablib/packages/openpyxl3/tests/test_props.py create mode 100644 tablib/packages/openpyxl3/tests/test_read.py create mode 100644 tablib/packages/openpyxl3/tests/test_strings.py create mode 100644 tablib/packages/openpyxl3/tests/test_style.py create mode 100644 tablib/packages/openpyxl3/tests/test_theme.py create mode 100644 tablib/packages/openpyxl3/tests/test_workbook.py create mode 100644 tablib/packages/openpyxl3/tests/test_worksheet.py create mode 100644 tablib/packages/openpyxl3/tests/test_write.py create mode 100644 tablib/packages/openpyxl3/workbook.py create mode 100644 tablib/packages/openpyxl3/worksheet.py create mode 100644 tablib/packages/openpyxl3/writer/__init__.py create mode 100644 tablib/packages/openpyxl3/writer/charts.py create mode 100644 tablib/packages/openpyxl3/writer/drawings.py create mode 100644 tablib/packages/openpyxl3/writer/dump_worksheet.py create mode 100644 tablib/packages/openpyxl3/writer/excel.py create mode 100644 tablib/packages/openpyxl3/writer/strings.py create mode 100644 tablib/packages/openpyxl3/writer/styles.py create mode 100644 tablib/packages/openpyxl3/writer/theme.py create mode 100644 tablib/packages/openpyxl3/writer/workbook.py create mode 100644 tablib/packages/openpyxl3/writer/worksheet.py diff --git a/tablib/packages/openpyxl/cell.py b/tablib/packages/openpyxl/cell.py index d3a4d0b..ec5e74e 100644 --- a/tablib/packages/openpyxl/cell.py +++ b/tablib/packages/openpyxl/cell.py @@ -42,6 +42,7 @@ from .shared.date_time import SharedDate from .shared.exc import CellCoordinatesException, \ ColumnStringIndexException, DataTypeException from .style import NumberFormat +from ...compat import basestring, unicode # constants COORD_RE = re.compile('^[$]?([A-Z]+)[$]?(\d+)$') @@ -177,7 +178,7 @@ class Cell(object): self.xf_index = 0 def __repr__(self): - return u"" % (self.parent.title, self.get_coordinate()) + return "" % (self.parent.title, self.get_coordinate()) def check_string(self, value): """Check string coding, length, and line break character""" diff --git a/tablib/packages/openpyxl/drawing.py b/tablib/packages/openpyxl/drawing.py index d3dc00b..610324e 100644 --- a/tablib/packages/openpyxl/drawing.py +++ b/tablib/packages/openpyxl/drawing.py @@ -360,7 +360,7 @@ class Shape(object): def _set_border_width(self, w): self._border_width = pixels_to_EMU(w) - print self._border_width + # print self._border_width border_width = property(_get_border_width, _set_border_width) diff --git a/tablib/packages/openpyxl/namedrange.py b/tablib/packages/openpyxl/namedrange.py index bffa978..85b08a8 100644 --- a/tablib/packages/openpyxl/namedrange.py +++ b/tablib/packages/openpyxl/namedrange.py @@ -44,7 +44,7 @@ class NamedRange(object): self.local_only = False def __str__(self): - return ','.join([u'%s!%s' % (sheet, name) for sheet, name in self.destinations]) + return ','.join(['%s!%s' % (sheet, name) for sheet, name in self.destinations]) def __repr__(self): diff --git a/tablib/packages/openpyxl/reader/excel.py b/tablib/packages/openpyxl/reader/excel.py index 3dc6547..16c3f91 100644 --- a/tablib/packages/openpyxl/reader/excel.py +++ b/tablib/packages/openpyxl/reader/excel.py @@ -66,8 +66,8 @@ def load_workbook(filename, use_iterators = False): try: archive = ZipFile(filename, 'r', ZIP_DEFLATED) - except (BadZipfile, RuntimeError, IOError, ValueError), e: - raise InvalidFileException(unicode(e)) + except (BadZipfile, RuntimeError, IOError, ValueError): + raise InvalidFileException() wb = Workbook() if use_iterators: @@ -75,10 +75,8 @@ def load_workbook(filename, use_iterators = False): try: _load_workbook(wb, archive, filename, use_iterators) - except KeyError, e: - raise InvalidFileException(unicode(e)) - except Exception, e: - raise e + except KeyError: + raise InvalidFileException() finally: archive.close() return wb diff --git a/tablib/packages/openpyxl/reader/iter_worksheet.py b/tablib/packages/openpyxl/reader/iter_worksheet.py index 926116d..781c0ca 100644 --- a/tablib/packages/openpyxl/reader/iter_worksheet.py +++ b/tablib/packages/openpyxl/reader/iter_worksheet.py @@ -27,11 +27,12 @@ *Still very raw* """ -from StringIO import StringIO +from ....compat import BytesIO as StringIO import warnings import operator from functools import partial -from itertools import ifilter, groupby +from ....compat import ifilter, xrange +from itertools import groupby from ..worksheet import Worksheet from ..cell import coordinate_from_string, get_column_letter, Cell from ..reader.excel import get_sheet_ids @@ -70,7 +71,7 @@ try: BaseRawCell = namedtuple('RawCell', RAW_ATTRIBUTES) except ImportError: - warnings.warn("""Unable to import 'namedtuple' module, this may cause memory issues when using optimized reader. Please upgrade your Python installation to 2.6+""") + # warnings.warn("""Unable to import 'namedtuple' module, this may cause memory issues when using optimized reader. Please upgrade your Python installation to 2.6+""") class BaseRawCell(object): diff --git a/tablib/packages/openpyxl/reader/worksheet.py b/tablib/packages/openpyxl/reader/worksheet.py index 722b6fd..a14c4a8 100644 --- a/tablib/packages/openpyxl/reader/worksheet.py +++ b/tablib/packages/openpyxl/reader/worksheet.py @@ -30,8 +30,9 @@ try: from xml.etree.cElementTree import iterparse except ImportError: from xml.etree.ElementTree import iterparse -from itertools import ifilter -from StringIO import StringIO + +from ....compat import ifilter +from ....compat import BytesIO as StringIO # package imports from ..cell import Cell, coordinate_from_string @@ -67,7 +68,8 @@ def read_dimension(xml_source): return None -def filter_cells((event, element)): +def filter_cells(x): + (event, element) = x return element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c' diff --git a/tablib/packages/openpyxl/shared/xmltools.py b/tablib/packages/openpyxl/shared/xmltools.py index 275cfca..74729e9 100644 --- a/tablib/packages/openpyxl/shared/xmltools.py +++ b/tablib/packages/openpyxl/shared/xmltools.py @@ -71,12 +71,30 @@ def start_tag(doc, name, attr=None, body=None, namespace=None): """Wrapper to start an xml tag.""" if attr is None: attr = {} + + + # name = bytes(name, 'utf-8') + + # if namespace is not None: + # namespace = bytes(namespace, 'utf-8') + + attr_vals = {} attr_keys = {} - for key, val in attr.iteritems(): + for key, val in attr.items(): + + + # if key is not None: + # key = bytes(key, 'utf-8') + + # if val is not None: + # val = bytes(val, 'utf-8') + key_tuple = (namespace, key) + attr_vals[key_tuple] = val attr_keys[key_tuple] = key + attr2 = AttributesNSImpl(attr_vals, attr_keys) doc.startElementNS((namespace, name), name, attr2) if body: diff --git a/tablib/packages/openpyxl/style.py b/tablib/packages/openpyxl/style.py index 0828105..38628db 100644 --- a/tablib/packages/openpyxl/style.py +++ b/tablib/packages/openpyxl/style.py @@ -307,7 +307,7 @@ class NumberFormat(HashableObject): 48: '##0.0E+0', 49: '@', } _BUILTIN_FORMATS_REVERSE = dict( - [(value, key) for key, value in _BUILTIN_FORMATS.iteritems()]) + [(value, key) for key, value in _BUILTIN_FORMATS.items()]) __fields__ = ('_format_code', '_format_index') diff --git a/tablib/packages/openpyxl/tests/test_dump.py b/tablib/packages/openpyxl/tests/test_dump.py index 58794b7..1e18ace 100644 --- a/tablib/packages/openpyxl/tests/test_dump.py +++ b/tablib/packages/openpyxl/tests/test_dump.py @@ -2,7 +2,7 @@ # file openpyxl/tests/test_dump.py # Copyright (c) 2010 openpyxl -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights @@ -43,7 +43,7 @@ import shutil def test_dump_sheet(): - test_file = NamedTemporaryFile(prefix='openpyxl.', suffix='.xlsx', delete=False) + test_file = NamedTemporaryFile(prefix='openpyxl.', suffix='.xlsx', delete=False) test_file.close() test_filename = test_file.name @@ -105,5 +105,5 @@ def test_table_builder(): table = dict(sb.get_table()) - for key,idx in result.iteritems(): + for key,idx in result.items(): eq_(idx, table[key]) diff --git a/tablib/packages/openpyxl/worksheet.py b/tablib/packages/openpyxl/worksheet.py index ea5e81b..4f3955c 100644 --- a/tablib/packages/openpyxl/worksheet.py +++ b/tablib/packages/openpyxl/worksheet.py @@ -229,12 +229,12 @@ class Worksheet(object): self._freeze_panes = None def __repr__(self): - return u'' % self.title + return '' % self.title def garbage_collect(self): """Delete cells that are not storing a value.""" delete_list = [coordinate for coordinate, cell in \ - self._cells.iteritems() if (cell.value in ('', None) and \ + self._cells.items() if (cell.value in ('', None) and \ hash(cell.style) == _DEFAULTS_STYLE_HASH)] for coordinate in delete_list: del self._cells[coordinate] @@ -502,7 +502,7 @@ class Worksheet(object): elif isinstance(list_or_dict, dict): - for col_idx, content in list_or_dict.iteritems(): + for col_idx, content in list_or_dict.items(): if isinstance(col_idx, basestring): col_idx = column_index_from_string(col_idx) - 1 diff --git a/tablib/packages/openpyxl/writer/charts.py b/tablib/packages/openpyxl/writer/charts.py index b5f4570..2c8df39 100644 --- a/tablib/packages/openpyxl/writer/charts.py +++ b/tablib/packages/openpyxl/writer/charts.py @@ -242,7 +242,7 @@ class ChartWriter(object): settings = SubElement(root, 'c:printSettings') SubElement(settings, 'c:headerFooter') - margins = dict([(k, str(v)) for (k,v) in self.chart.print_margins.iteritems()]) + margins = dict([(k, str(v)) for (k,v) in self.chart.print_margins.items()]) SubElement(settings, 'c:pageMargins', margins) SubElement(settings, 'c:pageSetup') diff --git a/tablib/packages/openpyxl/writer/excel.py b/tablib/packages/openpyxl/writer/excel.py index 1e915d9..b95245e 100644 --- a/tablib/packages/openpyxl/writer/excel.py +++ b/tablib/packages/openpyxl/writer/excel.py @@ -27,7 +27,7 @@ # Python stdlib imports from zipfile import ZipFile, ZIP_DEFLATED -from StringIO import StringIO +from ....compat import BytesIO as StringIO # package imports from ..shared.ooxml import ARC_SHARED_STRINGS, ARC_CONTENT_TYPES, \ @@ -74,9 +74,14 @@ class ExcelWriter(object): for ws in self.workbook.worksheets: ws.garbage_collect() shared_string_table = create_string_table(self.workbook) + + archive.writestr(ARC_SHARED_STRINGS, write_string_table(shared_string_table)) + for k, v in shared_string_table.items(): + shared_string_table[k] = bytes(v) + return shared_string_table def _write_worksheets(self, archive, shared_string_table, style_writer): diff --git a/tablib/packages/openpyxl/writer/strings.py b/tablib/packages/openpyxl/writer/strings.py index 1359e93..f73daed 100644 --- a/tablib/packages/openpyxl/writer/strings.py +++ b/tablib/packages/openpyxl/writer/strings.py @@ -26,7 +26,7 @@ """Write the shared string table.""" # Python stdlib imports -from StringIO import StringIO +from ....compat import BytesIO as StringIO # package imports from ..shared.xmltools import start_tag, end_tag, tag, XMLGenerator @@ -49,7 +49,7 @@ def write_string_table(string_table): start_tag(doc, 'sst', {'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', 'uniqueCount': '%d' % len(string_table)}) - strings_to_write = sorted(string_table.iteritems(), + strings_to_write = sorted(string_table.items(), key=lambda pair: pair[1]) for key in [pair[0] for pair in strings_to_write]: start_tag(doc, 'si') diff --git a/tablib/packages/openpyxl/writer/styles.py b/tablib/packages/openpyxl/writer/styles.py index fb04343..70dd719 100644 --- a/tablib/packages/openpyxl/writer/styles.py +++ b/tablib/packages/openpyxl/writer/styles.py @@ -44,13 +44,13 @@ class StyleWriter(object): crc[hash(style)] = style self.style_table = dict([(style, i+1) \ for i, style in enumerate(crc.values())]) - sorted_styles = sorted(self.style_table.iteritems(), \ + sorted_styles = sorted(self.style_table.items(), \ key = lambda pair:pair[1]) return [s[0] for s in sorted_styles] def get_style_by_hash(self): return dict([(hash(style), id) \ - for style, id in self.style_table.iteritems()]) + for style, id in self.style_table.items()]) def write_table(self): number_format_table = self._write_number_formats() diff --git a/tablib/packages/openpyxl/writer/worksheet.py b/tablib/packages/openpyxl/writer/worksheet.py index d9c493a..91effe2 100644 --- a/tablib/packages/openpyxl/writer/worksheet.py +++ b/tablib/packages/openpyxl/writer/worksheet.py @@ -26,7 +26,7 @@ """Write worksheets to xml representations.""" # Python stdlib imports -from StringIO import StringIO # cStringIO doesn't handle unicode +from ....compat import BytesIO as StringIO # cStringIO doesn't handle unicode # package imports from ..cell import coordinate_from_string, column_index_from_string @@ -107,7 +107,7 @@ def write_worksheet_cols(doc, worksheet): if worksheet.column_dimensions: start_tag(doc, 'cols') for column_string, columndimension in \ - worksheet.column_dimensions.iteritems(): + worksheet.column_dimensions.items(): col_index = column_index_from_string(column_string) col_def = {} col_def['collapsed'] = str(columndimension.style_index) diff --git a/tablib/packages/openpyxl3/__init__.py b/tablib/packages/openpyxl3/__init__.py new file mode 100644 index 0000000..81381d7 --- /dev/null +++ b/tablib/packages/openpyxl3/__init__.py @@ -0,0 +1,53 @@ +# file openpyxl/__init__.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Imports for the openpyxl package.""" + +# package imports +from . import cell +from . import namedrange +from . import style +from . import workbook +from . import worksheet +from . import reader +from . import shared +from . import writer + +# constants + +__major__ = 1 # for major interface/format changes +__minor__ = 5 # for minor interface/format changes +__release__ = 2 # for tweaks, bug-fixes, or development + +__version__ = '%d.%d.%d' % (__major__, __minor__, __release__) + +__author__ = 'Eric Gazoni' +__license__ = 'MIT/Expat' +__author_email__ = 'eric.gazoni@gmail.com' +__maintainer_email__ = 'openpyxl-users@googlegroups.com' +__url__ = 'http://bitbucket.org/ericgazoni/openpyxl/wiki/Home' +__downloadUrl__ = "http://bitbucket.org/ericgazoni/openpyxl/downloads" + +__all__ = ('reader', 'shared', 'writer',) diff --git a/tablib/packages/openpyxl3/cell.py b/tablib/packages/openpyxl3/cell.py new file mode 100644 index 0000000..300f0ed --- /dev/null +++ b/tablib/packages/openpyxl3/cell.py @@ -0,0 +1,384 @@ +# file openpyxl/cell.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Manage individual cells in a spreadsheet. + +The Cell class is required to know its value and type, display options, +and any other features of an Excel cell. Utilities for referencing +cells using Excel's 'A1' column/row nomenclature are also provided. + +""" + +__docformat__ = "restructuredtext en" + +# Python stdlib imports +import datetime +import re + +# package imports +from .shared.date_time import SharedDate +from .shared.exc import CellCoordinatesException, \ + ColumnStringIndexException, DataTypeException +from .style import NumberFormat + +# constants +COORD_RE = re.compile('^[$]?([A-Z]+)[$]?(\d+)$') + +ABSOLUTE_RE = re.compile('^[$]?([A-Z]+)[$]?(\d+)(:[$]?([A-Z]+)[$]?(\d+))?$') + +def coordinate_from_string(coord_string): + """Convert a coordinate string like 'B12' to a tuple ('B', 12)""" + match = COORD_RE.match(coord_string.upper()) + if not match: + msg = 'Invalid cell coordinates (%s)' % coord_string + raise CellCoordinatesException(msg) + column, row = match.groups() + return (column, int(row)) + + +def absolute_coordinate(coord_string): + """Convert a coordinate to an absolute coordinate string (B12 -> $B$12)""" + parts = ABSOLUTE_RE.match(coord_string).groups() + + if all(parts[-2:]): + return '$%s$%s:$%s$%s' % (parts[0], parts[1], parts[3], parts[4]) + else: + return '$%s$%s' % (parts[0], parts[1]) + + +def column_index_from_string(column, fast = False): + """Convert a column letter into a column number (e.g. B -> 2) + + Excel only supports 1-3 letter column names from A -> ZZZ, so we + restrict our column names to 1-3 characters, each in the range A-Z. + + .. note:: + + Fast mode is faster but does not check that all letters are capitals between A and Z + + """ + column = column.upper() + + clen = len(column) + + if not fast and not all('A' <= char <= 'Z' for char in column): + msg = 'Column string must contain only characters A-Z: got %s' % column + raise ColumnStringIndexException(msg) + + if clen == 1: + return ord(column[0]) - 64 + elif clen == 2: + return ((1 + (ord(column[0]) - 65)) * 26) + (ord(column[1]) - 64) + elif clen == 3: + return ((1 + (ord(column[0]) - 65)) * 676) + ((1 + (ord(column[1]) - 65)) * 26) + (ord(column[2]) - 64) + elif clen > 3: + raise ColumnStringIndexException('Column string index can not be longer than 3 characters') + else: + raise ColumnStringIndexException('Column string index can not be empty') + + +def get_column_letter(col_idx): + """Convert a column number into a column letter (3 -> 'C') + + Right shift the column col_idx by 26 to find column letters in reverse + order. These numbers are 1-based, and can be converted to ASCII + ordinals by adding 64. + + """ + # these indicies corrospond to A -> ZZZ and include all allowed + # columns + if not 1 <= col_idx <= 18278: + msg = 'Column index out of bounds: %s' % col_idx + raise ColumnStringIndexException(msg) + ordinals = [] + temp = col_idx + while temp: + quotient, remainder = divmod(temp, 26) + # check for exact division and borrow if needed + if remainder == 0: + quotient -= 1 + remainder = 26 + ordinals.append(remainder + 64) + temp = quotient + ordinals.reverse() + return ''.join([chr(ordinal) for ordinal in ordinals]) + + +class Cell(object): + """Describes cell associated properties. + + Properties of interest include style, type, value, and address. + + """ + __slots__ = ('column', + 'row', + '_value', + '_data_type', + 'parent', + 'xf_index', + '_hyperlink_rel') + + ERROR_CODES = {'#NULL!': 0, + '#DIV/0!': 1, + '#VALUE!': 2, + '#REF!': 3, + '#NAME?': 4, + '#NUM!': 5, + '#N/A': 6} + + TYPE_STRING = 's' + TYPE_FORMULA = 'f' + TYPE_NUMERIC = 'n' + TYPE_BOOL = 'b' + TYPE_NULL = 's' + TYPE_INLINE = 'inlineStr' + TYPE_ERROR = 'e' + + VALID_TYPES = [TYPE_STRING, TYPE_FORMULA, TYPE_NUMERIC, TYPE_BOOL, + TYPE_NULL, TYPE_INLINE, TYPE_ERROR] + + RE_PATTERNS = { + 'percentage': re.compile('^\-?[0-9]*\.?[0-9]*\s?\%$'), + 'time': re.compile('^(\d|[0-1]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$'), + 'numeric': re.compile('^\-?([0-9]+\\.?[0-9]*|[0-9]*\\.?[0-9]+)((E|e)\-?[0-9]+)?$'), } + + def __init__(self, worksheet, column, row, value = None): + self.column = column.upper() + self.row = row + # _value is the stored value, while value is the displayed value + self._value = None + self._hyperlink_rel = None + self._data_type = self.TYPE_NULL + if value: + self.value = value + self.parent = worksheet + self.xf_index = 0 + + def __repr__(self): + return "" % (self.parent.title, self.get_coordinate()) + + def check_string(self, value): + """Check string coding, length, and line break character""" + # convert to unicode string + value = str(value) + # string must never be longer than 32,767 characters + # truncate if necessary + value = value[:32767] + # we require that newline is represented as "\n" in core, + # not as "\r\n" or "\r" + value = value.replace('\r\n', '\n') + return value + + def check_numeric(self, value): + """Cast value to int or float if necessary""" + if not isinstance(value, (int, float)): + try: + value = int(value) + except ValueError: + value = float(value) + return value + + def set_value_explicit(self, value = None, data_type = TYPE_STRING): + """Coerce values according to their explicit type""" + type_coercion_map = { + self.TYPE_INLINE: self.check_string, + self.TYPE_STRING: self.check_string, + self.TYPE_FORMULA: str, + self.TYPE_NUMERIC: self.check_numeric, + self.TYPE_BOOL: bool, } + try: + self._value = type_coercion_map[data_type](value) + except KeyError: + if data_type not in self.VALID_TYPES: + msg = 'Invalid data type: %s' % data_type + raise DataTypeException(msg) + self._data_type = data_type + + def data_type_for_value(self, value): + """Given a value, infer the correct data type""" + if value is None: + data_type = self.TYPE_NULL + elif value is True or value is False: + data_type = self.TYPE_BOOL + elif isinstance(value, (int, float)): + data_type = self.TYPE_NUMERIC + elif not value: + data_type = self.TYPE_STRING + elif isinstance(value, (datetime.datetime, datetime.date)): + data_type = self.TYPE_NUMERIC + elif isinstance(value, str) and value[0] == '=': + data_type = self.TYPE_FORMULA + elif self.RE_PATTERNS['numeric'].match(value): + data_type = self.TYPE_NUMERIC + elif value.strip() in self.ERROR_CODES: + data_type = self.TYPE_ERROR + else: + data_type = self.TYPE_STRING + return data_type + + def bind_value(self, value): + """Given a value, infer type and display options.""" + self._data_type = self.data_type_for_value(value) + if value is None: + self.set_value_explicit('', self.TYPE_NULL) + return True + elif self._data_type == self.TYPE_STRING: + # percentage detection + percentage_search = self.RE_PATTERNS['percentage'].match(value) + if percentage_search and value.strip() != '%': + value = float(value.replace('%', '')) / 100.0 + self.set_value_explicit(value, self.TYPE_NUMERIC) + self._set_number_format(NumberFormat.FORMAT_PERCENTAGE) + return True + # time detection + time_search = self.RE_PATTERNS['time'].match(value) + if time_search: + sep_count = value.count(':') #pylint: disable-msg=E1103 + if sep_count == 1: + hours, minutes = [int(bit) for bit in value.split(':')] #pylint: disable-msg=E1103 + seconds = 0 + elif sep_count == 2: + hours, minutes, seconds = \ + [int(bit) for bit in value.split(':')] #pylint: disable-msg=E1103 + days = (hours / 24.0) + (minutes / 1440.0) + \ + (seconds / 86400.0) + self.set_value_explicit(days, self.TYPE_NUMERIC) + self._set_number_format(NumberFormat.FORMAT_DATE_TIME3) + return True + if self._data_type == self.TYPE_NUMERIC: + # date detection + # if the value is a date, but not a date time, make it a + # datetime, and set the time part to 0 + if isinstance(value, datetime.date) and not \ + isinstance(value, datetime.datetime): + value = datetime.datetime.combine(value, datetime.time()) + if isinstance(value, datetime.datetime): + value = SharedDate().datetime_to_julian(date = value) + self.set_value_explicit(value, self.TYPE_NUMERIC) + self._set_number_format(NumberFormat.FORMAT_DATE_YYYYMMDD2) + return True + self.set_value_explicit(value, self._data_type) + + def _get_value(self): + """Return the value, formatted as a date if needed""" + value = self._value + if self.is_date(): + value = SharedDate().from_julian(value) + return value + + def _set_value(self, value): + """Set the value and infer type and display options.""" + self.bind_value(value) + + value = property(_get_value, _set_value, + doc = 'Get or set the value held in the cell.\n\n' + ':rtype: depends on the value (string, float, int or ' + ':class:`datetime.datetime`)') + + def _set_hyperlink(self, val): + """Set value and display for hyperlinks in a cell""" + if self._hyperlink_rel is None: + self._hyperlink_rel = self.parent.create_relationship("hyperlink") + self._hyperlink_rel.target = val + self._hyperlink_rel.target_mode = "External" + if self._value is None: + self.value = val + + def _get_hyperlink(self): + """Return the hyperlink target or an empty string""" + return self._hyperlink_rel is not None and \ + self._hyperlink_rel.target or '' + + hyperlink = property(_get_hyperlink, _set_hyperlink, + doc = 'Get or set the hyperlink held in the cell. ' + 'Automatically sets the `value` of the cell with link text, ' + 'but you can modify it afterwards by setting the ' + '`value` property, and the hyperlink will remain.\n\n' + ':rtype: string') + + @property + def hyperlink_rel_id(self): + """Return the id pointed to by the hyperlink, or None""" + return self._hyperlink_rel is not None and \ + self._hyperlink_rel.id or None + + def _set_number_format(self, format_code): + """Set a new formatting code for numeric values""" + self.style.number_format.format_code = format_code + + @property + def has_style(self): + """Check if the parent worksheet has a style for this cell""" + return self.get_coordinate() in self.parent._styles #pylint: disable-msg=W0212 + + @property + def style(self): + """Returns the :class:`openpyxl.style.Style` object for this cell""" + return self.parent.get_style(self.get_coordinate()) + + @property + def data_type(self): + """Return the data type represented by this cell""" + return self._data_type + + def get_coordinate(self): + """Return the coordinate string for this cell (e.g. 'B12') + + :rtype: string + """ + return '%s%s' % (self.column, self.row) + + @property + def address(self): + """Return the coordinate string for this cell (e.g. 'B12') + + :rtype: string + """ + return self.get_coordinate() + + def offset(self, row = 0, column = 0): + """Returns a cell location relative to this cell. + + :param row: number of rows to offset + :type row: int + + :param column: number of columns to offset + :type column: int + + :rtype: :class:`openpyxl.cell.Cell` + """ + offset_column = get_column_letter(column_index_from_string( + column = self.column) + column) + offset_row = self.row + row + return self.parent.cell('%s%s' % (offset_column, offset_row)) + + def is_date(self): + """Returns whether the value is *probably* a date or not + + :rtype: bool + """ + return (self.has_style + and self.style.number_format.is_date_format() + and isinstance(self._value, (int, float))) diff --git a/tablib/packages/openpyxl3/chart.py b/tablib/packages/openpyxl3/chart.py new file mode 100644 index 0000000..19ba3b9 --- /dev/null +++ b/tablib/packages/openpyxl3/chart.py @@ -0,0 +1,340 @@ +''' +Copyright (c) 2010 openpyxl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license: http://www.opensource.org/licenses/mit-license.php +@author: Eric Gazoni +''' + +import math + +from .style import NumberFormat +from .drawing import Drawing, Shape +from .shared.units import pixels_to_EMU, short_color +from .cell import get_column_letter + +class Axis(object): + + POSITION_BOTTOM = 'b' + POSITION_LEFT = 'l' + + ORIENTATION_MIN_MAX = "minMax" + + def __init__(self): + + self.orientation = self.ORIENTATION_MIN_MAX + self.number_format = NumberFormat() + for attr in ('position','tick_label_position','crosses', + 'auto','label_align','label_offset','cross_between'): + setattr(self, attr, None) + self.min = 0 + self.max = None + self.unit = None + + @classmethod + def default_category(cls): + """ default values for category axes """ + + ax = Axis() + ax.id = 60871424 + ax.cross = 60873344 + ax.position = Axis.POSITION_BOTTOM + ax.tick_label_position = 'nextTo' + ax.crosses = "autoZero" + ax.auto = True + ax.label_align = 'ctr' + ax.label_offset = 100 + return ax + + @classmethod + def default_value(cls): + """ default values for value axes """ + + ax = Axis() + ax.id = 60873344 + ax.cross = 60871424 + ax.position = Axis.POSITION_LEFT + ax.major_gridlines = None + ax.tick_label_position = 'nextTo' + ax.crosses = 'autoZero' + ax.auto = False + ax.cross_between = 'between' + return ax + +class Reference(object): + """ a simple wrapper around a serie of reference data """ + + def __init__(self, sheet, pos1, pos2=None): + + self.sheet = sheet + self.pos1 = pos1 + self.pos2 = pos2 + + def get_type(self): + + if isinstance(self.cache[0], str): + return 'str' + else: + return 'num' + + def _get_ref(self): + """ format excel reference notation """ + + if self.pos2: + return '%s!$%s$%s:$%s$%s' % (self.sheet.title, + get_column_letter(self.pos1[1]+1), self.pos1[0]+1, + get_column_letter(self.pos2[1]+1), self.pos2[0]+1) + else: + return '%s!$%s$%s' % (self.sheet.title, + get_column_letter(self.pos1[1]+1), self.pos1[0]+1) + + + def _get_cache(self): + """ read data in sheet - to be used at writing time """ + + cache = [] + if self.pos2: + for row in range(self.pos1[0], self.pos2[0]+1): + for col in range(self.pos1[1], self.pos2[1]+1): + cache.append(self.sheet.cell(row=row, column=col).value) + else: + cell = self.sheet.cell(row=self.pos1[0], column=self.pos1[1]) + cache.append(cell.value) + return cache + + +class Serie(object): + """ a serie of data and possibly associated labels """ + + MARKER_NONE = 'none' + + def __init__(self, values, labels=None, legend=None, color=None, xvalues=None): + + self.marker = Serie.MARKER_NONE + self.values = values + self.xvalues = xvalues + self.labels = labels + self.legend = legend + self.error_bar = None + self._color = color + + def _get_color(self): + return self._color + + def _set_color(self, color): + self._color = short_color(color) + + color = property(_get_color, _set_color) + + def get_min_max(self): + + if self.error_bar: + err_cache = self.error_bar.values._get_cache() + vals = [v + err_cache[i] \ + for i,v in enumerate(self.values._get_cache())] + else: + vals = self.values._get_cache() + return min(vals), max(vals) + + def __len__(self): + + return len(self.values.cache) + +class Legend(object): + + def __init__(self): + + self.position = 'r' + self.layout = None + +class ErrorBar(object): + + PLUS = 1 + MINUS = 2 + PLUS_MINUS = 3 + + def __init__(self, _type, values): + + self.type = _type + self.values = values + +class Chart(object): + """ raw chart class """ + + GROUPING_CLUSTERED = 'clustered' + GROUPING_STANDARD = 'standard' + + BAR_CHART = 1 + LINE_CHART = 2 + SCATTER_CHART = 3 + + def __init__(self, _type, grouping): + + self._series = [] + + # public api + self.type = _type + self.grouping = grouping + self.x_axis = Axis.default_category() + self.y_axis = Axis.default_value() + self.legend = Legend() + self.lang = 'fr-FR' + self.title = '' + self.print_margins = dict(b=.75, l=.7, r=.7, t=.75, header=0.3, footer=.3) + + # the containing drawing + self.drawing = Drawing() + + # the offset for the plot part in percentage of the drawing size + self.width = .6 + self.height = .6 + self.margin_top = self._get_max_margin_top() + self.margin_left = 0 + + # the user defined shapes + self._shapes = [] + + def add_serie(self, serie): + + serie.id = len(self._series) + self._series.append(serie) + self._compute_min_max() + if not None in [s.xvalues for s in self._series]: + self._compute_xmin_xmax() + + def add_shape(self, shape): + + shape._chart = self + self._shapes.append(shape) + + def get_x_units(self): + """ calculate one unit for x axis in EMU """ + + return max([len(s.values._get_cache()) for s in self._series]) + + def get_y_units(self): + """ calculate one unit for y axis in EMU """ + + dh = pixels_to_EMU(self.drawing.height) + return (dh * self.height) / self.y_axis.max + + def get_y_chars(self): + """ estimate nb of chars for y axis """ + + _max = max([max([x for x in s.values._get_cache() if x is not None]) for s in self._series]) + return len(str(int(_max))) + + def _compute_min_max(self): + """ compute y axis limits and units """ + + maxi = max([max([x for x in s.values._get_cache() if x is not None]) for s in self._series]) + + mul = None + if maxi < 1: + s = str(maxi).split('.')[1] + mul = 10 + for x in s: + if x == '0': + mul *= 10 + else: + break + maxi = maxi * mul + + maxi = math.ceil(maxi * 1.1) + sz = len(str(int(maxi))) - 1 + unit = math.ceil(math.ceil(maxi / pow(10, sz)) * pow(10, sz-1)) + maxi = math.ceil(maxi/unit) * unit + + if mul is not None: + maxi = maxi/mul + unit = unit/mul + + if maxi / unit > 9: + # no more that 10 ticks + unit *= 2 + + self.y_axis.max = maxi + self.y_axis.unit = unit + + def _compute_xmin_xmax(self): + """ compute x axis limits and units """ + + maxi = max([max([x for x in s.xvalues._get_cache() if x is not None]) for s in self._series]) + + mul = None + if maxi < 1: + s = str(maxi).split('.')[1] + mul = 10 + for x in s: + if x == '0': + mul *= 10 + else: + break + maxi = maxi * mul + + maxi = math.ceil(maxi * 1.1) + sz = len(str(int(maxi))) - 1 + unit = math.ceil(math.ceil(maxi / pow(10, sz)) * pow(10, sz-1)) + maxi = math.ceil(maxi/unit) * unit + + if mul is not None: + maxi = maxi/mul + unit = unit/mul + + if maxi / unit > 9: + # no more that 10 ticks + unit *= 2 + + self.x_axis.max = maxi + self.x_axis.unit = unit + + def _get_max_margin_top(self): + + mb = Shape.FONT_HEIGHT + Shape.MARGIN_BOTTOM + plot_height = self.drawing.height * self.height + return float(self.drawing.height - plot_height - mb)/self.drawing.height + + def _get_min_margin_left(self): + + ml = (self.get_y_chars() * Shape.FONT_WIDTH) + Shape.MARGIN_LEFT + return float(ml)/self.drawing.width + + def _get_margin_top(self): + """ get margin in percent """ + + return min(self.margin_top, self._get_max_margin_top()) + + def _get_margin_left(self): + + return max(self._get_min_margin_left(), self.margin_left) + +class BarChart(Chart): + def __init__(self): + super(BarChart, self).__init__(Chart.BAR_CHART, Chart.GROUPING_CLUSTERED) + +class LineChart(Chart): + def __init__(self): + super(LineChart, self).__init__(Chart.LINE_CHART, Chart.GROUPING_STANDARD) + +class ScatterChart(Chart): + def __init__(self): + super(ScatterChart, self).__init__(Chart.SCATTER_CHART, Chart.GROUPING_STANDARD) + + diff --git a/tablib/packages/openpyxl3/drawing.py b/tablib/packages/openpyxl3/drawing.py new file mode 100644 index 0000000..6b3c76d --- /dev/null +++ b/tablib/packages/openpyxl3/drawing.py @@ -0,0 +1,402 @@ +''' +Copyright (c) 2010 openpyxl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license: http://www.opensource.org/licenses/mit-license.php +@author: Eric Gazoni +''' + +import math +from .style import Color +from .shared.units import pixels_to_EMU, EMU_to_pixels, short_color + +class Shadow(object): + + SHADOW_BOTTOM = 'b' + SHADOW_BOTTOM_LEFT = 'bl' + SHADOW_BOTTOM_RIGHT = 'br' + SHADOW_CENTER = 'ctr' + SHADOW_LEFT = 'l' + SHADOW_TOP = 't' + SHADOW_TOP_LEFT = 'tl' + SHADOW_TOP_RIGHT = 'tr' + + def __init__(self): + self.visible = False + self.blurRadius = 6 + self.distance = 2 + self.direction = 0 + self.alignment = self.SHADOW_BOTTOM_RIGHT + self.color = Color(Color.BLACK) + self.alpha = 50 + +class Drawing(object): + """ a drawing object - eg container for shapes or charts + we assume user specifies dimensions in pixels; units are + converted to EMU in the drawing part + """ + + count = 0 + + def __init__(self): + + self.name = '' + self.description = '' + self.coordinates = ((1,2), (16,8)) + self.left = 0 + self.top = 0 + self._width = EMU_to_pixels(200000) + self._height = EMU_to_pixels(1828800) + self.resize_proportional = False + self.rotation = 0 +# self.shadow = Shadow() + + def _set_width(self, w): + + if self.resize_proportional and w: + ratio = self._height / self._width + self._height = round(ratio * w) + self._width = w + + def _get_width(self): + + return self._width + + width = property(_get_width, _set_width) + + def _set_height(self, h): + + if self.resize_proportional and h: + ratio = self._width / self._height + self._width = round(ratio * h) + self._height = h + + def _get_height(self): + + return self._height + + height = property(_get_height, _set_height) + + def set_dimension(self, w=0, h=0): + + xratio = w / self._width + yratio = h / self._height + + if self.resize_proportional and w and h: + if (xratio * self._height) < h: + self._height = math.ceil(xratio * self._height) + self._width = width + else: + self._width = math.ceil(yratio * self._width) + self._height = height + + def get_emu_dimensions(self): + """ return (x, y, w, h) in EMU """ + + return (pixels_to_EMU(self.left), pixels_to_EMU(self.top), + pixels_to_EMU(self._width), pixels_to_EMU(self._height)) + + +class Shape(object): + """ a drawing inside a chart + coordiantes are specified by the user in the axis units + """ + + MARGIN_LEFT = 6 + 13 + 1 + MARGIN_BOTTOM = 17 + 11 + + FONT_WIDTH = 7 + FONT_HEIGHT = 8 + + ROUND_RECT = 'roundRect' + RECT = 'rect' + + # other shapes to define : + ''' + "line" + "lineInv" + "triangle" + "rtTriangle" + "diamond" + "parallelogram" + "trapezoid" + "nonIsoscelesTrapezoid" + "pentagon" + "hexagon" + "heptagon" + "octagon" + "decagon" + "dodecagon" + "star4" + "star5" + "star6" + "star7" + "star8" + "star10" + "star12" + "star16" + "star24" + "star32" + "roundRect" + "round1Rect" + "round2SameRect" + "round2DiagRect" + "snipRoundRect" + "snip1Rect" + "snip2SameRect" + "snip2DiagRect" + "plaque" + "ellipse" + "teardrop" + "homePlate" + "chevron" + "pieWedge" + "pie" + "blockArc" + "donut" + "noSmoking" + "rightArrow" + "leftArrow" + "upArrow" + "downArrow" + "stripedRightArrow" + "notchedRightArrow" + "bentUpArrow" + "leftRightArrow" + "upDownArrow" + "leftUpArrow" + "leftRightUpArrow" + "quadArrow" + "leftArrowCallout" + "rightArrowCallout" + "upArrowCallout" + "downArrowCallout" + "leftRightArrowCallout" + "upDownArrowCallout" + "quadArrowCallout" + "bentArrow" + "uturnArrow" + "circularArrow" + "leftCircularArrow" + "leftRightCircularArrow" + "curvedRightArrow" + "curvedLeftArrow" + "curvedUpArrow" + "curvedDownArrow" + "swooshArrow" + "cube" + "can" + "lightningBolt" + "heart" + "sun" + "moon" + "smileyFace" + "irregularSeal1" + "irregularSeal2" + "foldedCorner" + "bevel" + "frame" + "halfFrame" + "corner" + "diagStripe" + "chord" + "arc" + "leftBracket" + "rightBracket" + "leftBrace" + "rightBrace" + "bracketPair" + "bracePair" + "straightConnector1" + "bentConnector2" + "bentConnector3" + "bentConnector4" + "bentConnector5" + "curvedConnector2" + "curvedConnector3" + "curvedConnector4" + "curvedConnector5" + "callout1" + "callout2" + "callout3" + "accentCallout1" + "accentCallout2" + "accentCallout3" + "borderCallout1" + "borderCallout2" + "borderCallout3" + "accentBorderCallout1" + "accentBorderCallout2" + "accentBorderCallout3" + "wedgeRectCallout" + "wedgeRoundRectCallout" + "wedgeEllipseCallout" + "cloudCallout" + "cloud" + "ribbon" + "ribbon2" + "ellipseRibbon" + "ellipseRibbon2" + "leftRightRibbon" + "verticalScroll" + "horizontalScroll" + "wave" + "doubleWave" + "plus" + "flowChartProcess" + "flowChartDecision" + "flowChartInputOutput" + "flowChartPredefinedProcess" + "flowChartInternalStorage" + "flowChartDocument" + "flowChartMultidocument" + "flowChartTerminator" + "flowChartPreparation" + "flowChartManualInput" + "flowChartManualOperation" + "flowChartConnector" + "flowChartPunchedCard" + "flowChartPunchedTape" + "flowChartSummingJunction" + "flowChartOr" + "flowChartCollate" + "flowChartSort" + "flowChartExtract" + "flowChartMerge" + "flowChartOfflineStorage" + "flowChartOnlineStorage" + "flowChartMagneticTape" + "flowChartMagneticDisk" + "flowChartMagneticDrum" + "flowChartDisplay" + "flowChartDelay" + "flowChartAlternateProcess" + "flowChartOffpageConnector" + "actionButtonBlank" + "actionButtonHome" + "actionButtonHelp" + "actionButtonInformation" + "actionButtonForwardNext" + "actionButtonBackPrevious" + "actionButtonEnd" + "actionButtonBeginning" + "actionButtonReturn" + "actionButtonDocument" + "actionButtonSound" + "actionButtonMovie" + "gear6" + "gear9" + "funnel" + "mathPlus" + "mathMinus" + "mathMultiply" + "mathDivide" + "mathEqual" + "mathNotEqual" + "cornerTabs" + "squareTabs" + "plaqueTabs" + "chartX" + "chartStar" + "chartPlus" + ''' + + def __init__(self, coordinates=((0,0), (1,1)), text=None, scheme="accent1"): + + self.coordinates = coordinates # in axis unit + self.text = text + self.scheme = scheme + self.style = Shape.RECT + self._border_width = 3175 # in EMU + self._border_color = Color.BLACK[2:] #"F3B3C5" + self._color = Color.WHITE[2:] + self._text_color = Color.BLACK[2:] + + def _get_border_color(self): + return self._border_color + + def _set_border_color(self, color): + self._border_color = short_color(color) + + border_color = property(_get_border_color, _set_border_color) + + def _get_color(self): + return self._color + + def _set_color(self, color): + self._color = short_color(color) + + color = property(_get_color, _set_color) + + def _get_text_color(self): + return self._text_color + + def _set_text_color(self, color): + self._text_color = short_color(color) + + text_color = property(_get_text_color, _set_text_color) + + def _get_border_width(self): + + return EMU_to_pixels(self._border_width) + + def _set_border_width(self, w): + + self._border_width = pixels_to_EMU(w) + print(self._border_width) + + border_width = property(_get_border_width, _set_border_width) + + def get_coordinates(self): + """ return shape coordinates in percentages (left, top, right, bottom) """ + + (x1, y1), (x2, y2) = self.coordinates + + drawing_width = pixels_to_EMU(self._chart.drawing.width) + drawing_height = pixels_to_EMU(self._chart.drawing.height) + plot_width = drawing_width * self._chart.width + plot_height = drawing_height * self._chart.height + + margin_left = self._chart._get_margin_left() * drawing_width + xunit = plot_width / self._chart.get_x_units() + + margin_top = self._chart._get_margin_top() * drawing_height + yunit = self._chart.get_y_units() + + x_start = (margin_left + (float(x1) * xunit)) / drawing_width + y_start = (margin_top + plot_height - (float(y1) * yunit)) / drawing_height + + x_end = (margin_left + (float(x2) * xunit)) / drawing_width + y_end = (margin_top + plot_height - (float(y2) * yunit)) / drawing_height + + def _norm_pct(pct): + """ force shapes to appear by truncating too large sizes """ + if pct>1: pct = 1 + elif pct<0: pct = 0 + return pct + + # allow user to specify y's in whatever order + # excel expect y_end to be lower + if y_end < y_start: + y_end, y_start = y_start, y_end + + return (_norm_pct(x_start), _norm_pct(y_start), + _norm_pct(x_end), _norm_pct(y_end)) + diff --git a/tablib/packages/openpyxl3/namedrange.py b/tablib/packages/openpyxl3/namedrange.py new file mode 100644 index 0000000..85b08a8 --- /dev/null +++ b/tablib/packages/openpyxl3/namedrange.py @@ -0,0 +1,68 @@ +# file openpyxl/namedrange.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Track named groups of cells in a worksheet""" + +# Python stdlib imports +import re + +# package imports +from .shared.exc import NamedRangeException + +# constants +NAMED_RANGE_RE = re.compile("'?([^']*)'?!((\$([A-Za-z]+))?\$([0-9]+)(:(\$([A-Za-z]+))?(\$([0-9]+)))?)$") + +class NamedRange(object): + """A named group of cells""" + __slots__ = ('name', 'destinations', 'local_only') + + def __init__(self, name, destinations): + self.name = name + self.destinations = destinations + self.local_only = False + + def __str__(self): + return ','.join(['%s!%s' % (sheet, name) for sheet, name in self.destinations]) + + def __repr__(self): + + return '<%s "%s">' % (self.__class__.__name__, str(self)) + + +def split_named_range(range_string): + """Separate a named range into its component parts""" + + destinations = [] + + for range_string in range_string.split(','): + + match = NAMED_RANGE_RE.match(range_string) + if not match: + raise NamedRangeException('Invalid named range string: "%s"' % range_string) + else: + sheet_name, xlrange = match.groups()[:2] + destinations.append((sheet_name, xlrange)) + + return destinations diff --git a/tablib/packages/openpyxl3/reader/__init__.py b/tablib/packages/openpyxl3/reader/__init__.py new file mode 100644 index 0000000..9b0ee2f --- /dev/null +++ b/tablib/packages/openpyxl3/reader/__init__.py @@ -0,0 +1,33 @@ +# file openpyxl/reader/__init__.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Imports for the openpyxl.reader namespace.""" + +# package imports +from ..reader import excel +from ..reader import strings +from ..reader import style +from ..reader import workbook +from ..reader import worksheet diff --git a/tablib/packages/openpyxl3/reader/excel.py b/tablib/packages/openpyxl3/reader/excel.py new file mode 100644 index 0000000..698d292 --- /dev/null +++ b/tablib/packages/openpyxl3/reader/excel.py @@ -0,0 +1,117 @@ +# file openpyxl/reader/excel.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Read an xlsx file into Python""" + +# Python stdlib imports +from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile + +# package imports +from ..shared.exc import OpenModeError, InvalidFileException +from ..shared.ooxml import ARC_SHARED_STRINGS, ARC_CORE, ARC_APP, \ + ARC_WORKBOOK, PACKAGE_WORKSHEETS, ARC_STYLE +from ..workbook import Workbook +from ..reader.strings import read_string_table +from ..reader.style import read_style_table +from ..reader.workbook import read_sheets_titles, read_named_ranges, \ + read_properties_core, get_sheet_ids +from ..reader.worksheet import read_worksheet +from ..reader.iter_worksheet import unpack_worksheet + +def load_workbook(filename, use_iterators = False): + """Open the given filename and return the workbook + + :param filename: the path to open + :type filename: string + + :param use_iterators: use lazy load for cells + :type use_iterators: bool + + :rtype: :class:`openpyxl.workbook.Workbook` + + .. note:: + + When using lazy load, all worksheets will be :class:`openpyxl.reader.iter_worksheet.IterableWorksheet` + and the returned workbook will be read-only. + + """ + + if isinstance(filename, file): + # fileobject must have been opened with 'rb' flag + # it is required by zipfile + if 'b' not in filename.mode: + raise OpenModeError("File-object must be opened in binary mode") + + try: + archive = ZipFile(filename, 'r', ZIP_DEFLATED) + except (BadZipfile, RuntimeError, IOError, ValueError) as e: + raise InvalidFileException(str(e)) + wb = Workbook() + + if use_iterators: + wb._set_optimized_read() + + try: + _load_workbook(wb, archive, filename, use_iterators) + except KeyError as e: + raise InvalidFileException(str(e)) + except Exception as e: + raise e + finally: + archive.close() + return wb + +def _load_workbook(wb, archive, filename, use_iterators): + + valid_files = archive.namelist() + + # get workbook-level information + wb.properties = read_properties_core(archive.read(ARC_CORE)) + try: + string_table = read_string_table(archive.read(ARC_SHARED_STRINGS)) + except KeyError: + string_table = {} + style_table = read_style_table(archive.read(ARC_STYLE)) + + # get worksheets + wb.worksheets = [] # remove preset worksheet + sheet_names = read_sheets_titles(archive.read(ARC_APP)) + for i, sheet_name in enumerate(sheet_names): + + sheet_codename = 'sheet%d.xml' % (i + 1) + worksheet_path = '%s/%s' % (PACKAGE_WORKSHEETS, sheet_codename) + + if not worksheet_path in valid_files: + continue + + if not use_iterators: + new_ws = read_worksheet(archive.read(worksheet_path), wb, sheet_name, string_table, style_table) + else: + xml_source = unpack_worksheet(archive, worksheet_path) + new_ws = read_worksheet(xml_source, wb, sheet_name, string_table, style_table, filename, sheet_codename) + #new_ws = read_worksheet(archive.read(worksheet_path), wb, sheet_name, string_table, style_table, filename, sheet_codename) + wb.add_sheet(new_ws, index = i) + + wb._named_ranges = read_named_ranges(archive.read(ARC_WORKBOOK), wb) diff --git a/tablib/packages/openpyxl3/reader/iter_worksheet.py b/tablib/packages/openpyxl3/reader/iter_worksheet.py new file mode 100644 index 0000000..7bc9ed4 --- /dev/null +++ b/tablib/packages/openpyxl3/reader/iter_worksheet.py @@ -0,0 +1,343 @@ +# file openpyxl/reader/iter_worksheet.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +""" Iterators-based worksheet reader +*Still very raw* +""" + +from io import BytesIO as StringIO +import warnings +import operator +from functools import partial +from itertools import groupby +from ..worksheet import Worksheet +from ..cell import coordinate_from_string, get_column_letter, Cell +from ..reader.excel import get_sheet_ids +from ..reader.strings import read_string_table +from ..reader.style import read_style_table, NumberFormat +from ..shared.date_time import SharedDate +from ..reader.worksheet import read_dimension +from ..shared.ooxml import (MIN_COLUMN, MAX_COLUMN, PACKAGE_WORKSHEETS, + MAX_ROW, MIN_ROW, ARC_SHARED_STRINGS, ARC_APP, ARC_STYLE) +from xml.etree.cElementTree import iterparse +from zipfile import ZipFile +from .. import cell +import re +import tempfile +import zlib +import zipfile +import struct + +TYPE_NULL = Cell.TYPE_NULL +MISSING_VALUE = None + +RE_COORDINATE = re.compile('^([A-Z]+)([0-9]+)$') + +SHARED_DATE = SharedDate() + +_COL_CONVERSION_CACHE = dict((get_column_letter(i), i) for i in range(1, 18279)) +def column_index_from_string(str_col, _col_conversion_cache=_COL_CONVERSION_CACHE): + # we use a function argument to get indexed name lookup + return _col_conversion_cache[str_col] +del _COL_CONVERSION_CACHE + +RAW_ATTRIBUTES = ['row', 'column', 'coordinate', 'internal_value', 'data_type', 'style_id', 'number_format'] + +try: + from collections import namedtuple + BaseRawCell = namedtuple('RawCell', RAW_ATTRIBUTES) +except ImportError: + + warnings.warn("""Unable to import 'namedtuple' module, this may cause memory issues when using optimized reader. Please upgrade your Python installation to 2.6+""") + + class BaseRawCell(object): + + def __init__(self, *args): + assert len(args)==len(RAW_ATTRIBUTES) + + for attr, val in zip(RAW_ATTRIBUTES, args): + setattr(self, attr, val) + + def _replace(self, **kwargs): + + self.__dict__.update(kwargs) + + return self + + +class RawCell(BaseRawCell): + """Optimized version of the :class:`openpyxl.cell.Cell`, using named tuples. + + Useful attributes are: + + * row + * column + * coordinate + * internal_value + + You can also access if needed: + + * data_type + * number_format + + """ + + @property + def is_date(self): + res = (self.data_type == Cell.TYPE_NUMERIC + and self.number_format is not None + and ('d' in self.number_format + or 'm' in self.number_format + or 'y' in self.number_format + or 'h' in self.number_format + or 's' in self.number_format + )) + + return res + +def iter_rows(workbook_name, sheet_name, xml_source, range_string = '', row_offset = 0, column_offset = 0): + + archive = get_archive_file(workbook_name) + + source = xml_source + + if range_string: + min_col, min_row, max_col, max_row = get_range_boundaries(range_string, row_offset, column_offset) + else: + min_col, min_row, max_col, max_row = read_dimension(xml_source = source) + min_col = column_index_from_string(min_col) + max_col = column_index_from_string(max_col) + 1 + max_row += 6 + + try: + string_table = read_string_table(archive.read(ARC_SHARED_STRINGS)) + except KeyError: + string_table = {} + + style_table = read_style_table(archive.read(ARC_STYLE)) + + source.seek(0) + p = iterparse(source) + + return get_squared_range(p, min_col, min_row, max_col, max_row, string_table, style_table) + + +def get_rows(p, min_column = MIN_COLUMN, min_row = MIN_ROW, max_column = MAX_COLUMN, max_row = MAX_ROW): + + return groupby(get_cells(p, min_row, min_column, max_row, max_column), operator.attrgetter('row')) + +def get_cells(p, min_row, min_col, max_row, max_col, _re_coordinate=RE_COORDINATE): + + for _event, element in p: + + if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c': + coord = element.get('r') + column_str, row = _re_coordinate.match(coord).groups() + + row = int(row) + column = column_index_from_string(column_str) + + if min_col <= column <= max_col and min_row <= row <= max_row: + data_type = element.get('t', 'n') + style_id = element.get('s') + value = element.findtext('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v') + yield RawCell(row, column_str, coord, value, data_type, style_id, None) + + if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v': + continue + element.clear() + + + +def get_range_boundaries(range_string, row = 0, column = 0): + + if ':' in range_string: + min_range, max_range = range_string.split(':') + min_col, min_row = coordinate_from_string(min_range) + max_col, max_row = coordinate_from_string(max_range) + + min_col = column_index_from_string(min_col) + column + max_col = column_index_from_string(max_col) + column + min_row += row + max_row += row + + else: + min_col, min_row = coordinate_from_string(range_string) + min_col = column_index_from_string(min_col) + max_col = min_col + 1 + max_row = min_row + + return (min_col, min_row, max_col, max_row) + +def get_archive_file(archive_name): + + return ZipFile(archive_name, 'r') + +def get_xml_source(archive_file, sheet_name): + + return archive_file.read('%s/%s' % (PACKAGE_WORKSHEETS, sheet_name)) + +def get_missing_cells(row, columns): + + return dict([(column, RawCell(row, column, '%s%s' % (column, row), MISSING_VALUE, TYPE_NULL, None, None)) for column in columns]) + +def get_squared_range(p, min_col, min_row, max_col, max_row, string_table, style_table): + + expected_columns = [get_column_letter(ci) for ci in range(min_col, max_col)] + + current_row = min_row + for row, cells in get_rows(p, min_row = min_row, max_row = max_row, min_column = min_col, max_column = max_col): + full_row = [] + if current_row < row: + + for gap_row in range(current_row, row): + + dummy_cells = get_missing_cells(gap_row, expected_columns) + + yield tuple([dummy_cells[column] for column in expected_columns]) + + current_row = row + + temp_cells = list(cells) + + retrieved_columns = dict([(c.column, c) for c in temp_cells]) + + missing_columns = list(set(expected_columns) - set(retrieved_columns.keys())) + + replacement_columns = get_missing_cells(row, missing_columns) + + for column in expected_columns: + + if column in retrieved_columns: + cell = retrieved_columns[column] + + if cell.style_id is not None: + style = style_table[int(cell.style_id)] + cell = cell._replace(number_format = style.number_format.format_code) #pylint: disable-msg=W0212 + if cell.internal_value is not None: + if cell.data_type == Cell.TYPE_STRING: + cell = cell._replace(internal_value = string_table[int(cell.internal_value)]) #pylint: disable-msg=W0212 + elif cell.data_type == Cell.TYPE_BOOL: + cell = cell._replace(internal_value = cell.internal_value == 'True') + elif cell.is_date: + cell = cell._replace(internal_value = SHARED_DATE.from_julian(float(cell.internal_value))) + elif cell.data_type == Cell.TYPE_NUMERIC: + cell = cell._replace(internal_value = float(cell.internal_value)) + full_row.append(cell) + + else: + full_row.append(replacement_columns[column]) + + current_row = row + 1 + + yield tuple(full_row) + +#------------------------------------------------------------------------------ + +class IterableWorksheet(Worksheet): + + def __init__(self, parent_workbook, title, workbook_name, + sheet_codename, xml_source): + + Worksheet.__init__(self, parent_workbook, title) + self._workbook_name = workbook_name + self._sheet_codename = sheet_codename + self._xml_source = xml_source + + def iter_rows(self, range_string = '', row_offset = 0, column_offset = 0): + """ Returns a squared range based on the `range_string` parameter, + using generators. + + :param range_string: range of cells (e.g. 'A1:C4') + :type range_string: string + + :param row: row index of the cell (e.g. 4) + :type row: int + + :param column: column index of the cell (e.g. 3) + :type column: int + + :rtype: generator + + """ + + return iter_rows(workbook_name = self._workbook_name, + sheet_name = self._sheet_codename, + xml_source = self._xml_source, + range_string = range_string, + row_offset = row_offset, + column_offset = column_offset) + + def cell(self, *args, **kwargs): + + raise NotImplementedError("use 'iter_rows()' instead") + + def range(self, *args, **kwargs): + + raise NotImplementedError("use 'iter_rows()' instead") + +def unpack_worksheet(archive, filename): + + temp_file = tempfile.TemporaryFile(mode='r+', prefix='openpyxl.', suffix='.unpack.temp') + + zinfo = archive.getinfo(filename) + + if zinfo.compress_type == zipfile.ZIP_STORED: + decoder = None + elif zinfo.compress_type == zipfile.ZIP_DEFLATED: + decoder = zlib.decompressobj(-zlib.MAX_WBITS) + else: + raise zipfile.BadZipFile("Unrecognized compression method") + + archive.fp.seek(_get_file_offset(archive, zinfo)) + bytes_to_read = zinfo.compress_size + + while True: + buff = archive.fp.read(min(bytes_to_read, 102400)) + if not buff: + break + bytes_to_read -= len(buff) + if decoder: + buff = decoder.decompress(buff) + temp_file.write(buff) + + if decoder: + temp_file.write(decoder.decompress('Z')) + + return temp_file + +def _get_file_offset(archive, zinfo): + + try: + return zinfo.file_offset + except AttributeError: + # From http://stackoverflow.com/questions/3781261/how-to-simulate-zipfile-open-in-python-2-5 + + # Seek over the fixed size fields to the "file name length" field in + # the file header (26 bytes). Unpack this and the "extra field length" + # field ourselves as info.extra doesn't seem to be the correct length. + archive.fp.seek(zinfo.header_offset + 26) + file_name_len, extra_len = struct.unpack(" 10000: + msg = 'Year not supported by Excel: %s' % year + raise ValueError(msg) + if self.excel_base_date == self.CALENDAR_WINDOWS_1900: + # Fudge factor for the erroneous fact that the year 1900 is + # treated as a Leap Year in MS Excel. This affects every date + # following 28th February 1900 + if year == 1900 and month <= 2: + excel_1900_leap_year = False + else: + excel_1900_leap_year = True + excel_base_date = 2415020 + else: + raise NotImplementedError('Mac dates are not yet supported.') + #excel_base_date = 2416481 + #excel_1900_leap_year = False + + # Julian base date adjustment + if month > 2: + month = month - 3 + else: + month = month + 9 + year -= 1 + + # Calculate the Julian Date, then subtract the Excel base date + # JD 2415020 = 31 - Dec - 1899 -> Excel Date of 0 + century, decade = int(str(year)[:2]), int(str(year)[2:]) + excel_date = floor(146097 * century / 4) + \ + floor((1461 * decade) / 4) + floor((153 * month + 2) / 5) + \ + day + 1721119 - excel_base_date + if excel_1900_leap_year: + excel_date += 1 + + # check to ensure that we exclude 2/29/1900 as a possible value + if self.excel_base_date == self.CALENDAR_WINDOWS_1900 \ + and excel_date == 60: + msg = 'Error: Excel believes 1900 was a leap year' + raise ValueError(msg) + excel_time = ((hours * 3600) + (minutes * 60) + seconds) / 86400 + return excel_date + excel_time + + def from_julian(self, value=0): + """Convert from the Excel JD back to a date""" + if self.excel_base_date == self.CALENDAR_WINDOWS_1900: + excel_base_date = 25569 + if value < 60: + excel_base_date -= 1 + elif value == 60: + msg = 'Error: Excel believes 1900 was a leap year' + raise ValueError(msg) + else: + raise NotImplementedError('Mac dates are not yet supported.') + #excel_base_date = 24107 + + if value >= 1: + utc_days = value - excel_base_date + + return EPOCH + datetime.timedelta(days=utc_days) + + elif value >= 0: + hours = floor(value * 24) + mins = floor(value * 24 * 60) - floor(hours * 60) + secs = floor(value * 24 * 60 * 60) - floor(hours * 60 * 60) - \ + floor(mins * 60) + return datetime.time(int(hours), int(mins), int(secs)) + else: + msg = 'Negative dates (%s) are not supported' % value + raise ValueError(msg) diff --git a/tablib/packages/openpyxl3/shared/exc.py b/tablib/packages/openpyxl3/shared/exc.py new file mode 100644 index 0000000..94a3e2c --- /dev/null +++ b/tablib/packages/openpyxl3/shared/exc.py @@ -0,0 +1,59 @@ +# file openpyxl/shared/exc.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Definitions for openpyxl shared exception classes.""" + + +class CellCoordinatesException(Exception): + """Error for converting between numeric and A1-style cell references.""" + +class ColumnStringIndexException(Exception): + """Error for bad column names in A1-style cell references.""" + +class DataTypeException(Exception): + """Error for any data type inconsistencies.""" + +class NamedRangeException(Exception): + """Error for badly formatted named ranges.""" + +class SheetTitleException(Exception): + """Error for bad sheet names.""" + +class InsufficientCoordinatesException(Exception): + """Error for partially specified cell coordinates.""" + +class OpenModeError(Exception): + """Error for fileobj opened in non-binary mode.""" + +class InvalidFileException(Exception): + """Error for trying to open a non-ooxml file.""" + +class ReadOnlyWorkbookException(Exception): + """Error for trying to modify a read-only workbook""" + +class MissingNumberFormat(Exception): + """Error when a referenced number format is not in the stylesheet""" + + diff --git a/tablib/packages/openpyxl3/shared/ooxml.py b/tablib/packages/openpyxl3/shared/ooxml.py new file mode 100644 index 0000000..979b172 --- /dev/null +++ b/tablib/packages/openpyxl3/shared/ooxml.py @@ -0,0 +1,60 @@ +# file openpyxl/shared/ooxml.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Constants for fixed paths in a file and xml namespace urls.""" + +MIN_ROW = 0 +MIN_COLUMN = 0 +MAX_COLUMN = 16384 +MAX_ROW = 1048576 + +# constants +PACKAGE_PROPS = 'docProps' +PACKAGE_XL = 'xl' +PACKAGE_RELS = '_rels' +PACKAGE_THEME = PACKAGE_XL + '/' + 'theme' +PACKAGE_WORKSHEETS = PACKAGE_XL + '/' + 'worksheets' +PACKAGE_DRAWINGS = PACKAGE_XL + '/' + 'drawings' +PACKAGE_CHARTS = PACKAGE_XL + '/' + 'charts' + +ARC_CONTENT_TYPES = '[Content_Types].xml' +ARC_ROOT_RELS = PACKAGE_RELS + '/.rels' +ARC_WORKBOOK_RELS = PACKAGE_XL + '/' + PACKAGE_RELS + '/workbook.xml.rels' +ARC_CORE = PACKAGE_PROPS + '/core.xml' +ARC_APP = PACKAGE_PROPS + '/app.xml' +ARC_WORKBOOK = PACKAGE_XL + '/workbook.xml' +ARC_STYLE = PACKAGE_XL + '/styles.xml' +ARC_THEME = PACKAGE_THEME + '/theme1.xml' +ARC_SHARED_STRINGS = PACKAGE_XL + '/sharedStrings.xml' + +NAMESPACES = { + 'cp': 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties', + 'dc': 'http://purl.org/dc/elements/1.1/', + 'dcterms': 'http://purl.org/dc/terms/', + 'dcmitype': 'http://purl.org/dc/dcmitype/', + 'xsi': 'http://www.w3.org/2001/XMLSchema-instance', + 'vt': 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes', + 'xml': 'http://www.w3.org/XML/1998/namespace' +} diff --git a/tablib/packages/openpyxl3/shared/password_hasher.py b/tablib/packages/openpyxl3/shared/password_hasher.py new file mode 100644 index 0000000..b5d0dd0 --- /dev/null +++ b/tablib/packages/openpyxl3/shared/password_hasher.py @@ -0,0 +1,47 @@ +# file openpyxl/shared/password_hasher.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Basic password hashing.""" + + +def hash_password(plaintext_password=''): + """Create a password hash from a given string. + + This method is based on the algorithm provided by + Daniel Rentz of OpenOffice and the PEAR package + Spreadsheet_Excel_Writer by Xavier Noguer . + + """ + password = 0x0000 + i = 1 + for char in plaintext_password: + value = ord(char) << i + rotated_bits = value >> 15 + value &= 0x7fff + password ^= (value | rotated_bits) + i += 1 + password ^= len(plaintext_password) + password ^= 0xCE4B + return str(hex(password)).upper()[2:] diff --git a/tablib/packages/openpyxl3/shared/units.py b/tablib/packages/openpyxl3/shared/units.py new file mode 100644 index 0000000..fba82d7 --- /dev/null +++ b/tablib/packages/openpyxl3/shared/units.py @@ -0,0 +1,67 @@ +# file openpyxl/shared/units.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +import math + +def pixels_to_EMU(value): + return int(round(value * 9525)) + +def EMU_to_pixels(value): + if not value: + return 0 + else: + return round(value / 9525.) + +def EMU_to_cm(value): + if not value: + return 0 + else: + return (EMU_to_pixels(value) * 2.57 / 96) + +def pixels_to_points(value): + return value * 0.67777777 + +def points_to_pixels(value): + if not value: + return 0 + else: + return int(math.ceil(value * 1.333333333)) + +def degrees_to_angle(value): + return int(round(value * 60000)) + +def angle_to_degrees(value): + if not value: + return 0 + else: + return round(value / 60000.) + +def short_color(color): + """ format a color to its short size """ + + if len(color) > 6: + return color[2:] + else: + return color diff --git a/tablib/packages/openpyxl3/shared/xmltools.py b/tablib/packages/openpyxl3/shared/xmltools.py new file mode 100644 index 0000000..3c0be4c --- /dev/null +++ b/tablib/packages/openpyxl3/shared/xmltools.py @@ -0,0 +1,96 @@ +# file openpyxl/shared/xmltools.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Shared xml tools. + +Shortcut functions taken from: + http://lethain.com/entry/2009/jan/22/handling-very-large-csv-and-xml-files-in-python/ + +""" + +# Python stdlib imports +from xml.sax.xmlreader import AttributesNSImpl +from xml.sax.saxutils import XMLGenerator +try: + from xml.etree.ElementTree import ElementTree, Element, SubElement, \ + QName, fromstring, tostring +except ImportError: + from cElementTree import ElementTree, Element, SubElement, \ + QName, fromstring, tostring + +# package imports +from .. import __name__ as prefix + + +def get_document_content(xml_node): + """Print nicely formatted xml to a string.""" + pretty_indent(xml_node) + return tostring(xml_node, 'utf-8') + + +def pretty_indent(elem, level=0): + """Format xml with nice indents and line breaks.""" + i = "\n" + level * " " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + pretty_indent(elem, level + 1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + +def start_tag(doc, name, attr=None, body=None, namespace=None): + """Wrapper to start an xml tag.""" + if attr is None: + attr = {} + attr_vals = {} + attr_keys = {} + for key, val in attr.items(): + key_tuple = (namespace, key) + attr_vals[key_tuple] = val + attr_keys[key_tuple] = key + attr2 = AttributesNSImpl(attr_vals, attr_keys) + doc.startElementNS((namespace, name), name, attr2) + if body: + doc.characters(body) + + +def end_tag(doc, name, namespace=None): + """Wrapper to close an xml tag.""" + doc.endElementNS((namespace, name), name) + + +def tag(doc, name, attr=None, body=None, namespace=None): + """Wrapper to print xml tags and comments.""" + if attr is None: + attr = {} + start_tag(doc, name, attr, body, namespace) + end_tag(doc, name, namespace) diff --git a/tablib/packages/openpyxl3/style.py b/tablib/packages/openpyxl3/style.py new file mode 100644 index 0000000..38628db --- /dev/null +++ b/tablib/packages/openpyxl3/style.py @@ -0,0 +1,392 @@ +# file openpyxl/style.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Style and formatting option tracking.""" + +# Python stdlib imports +import re +try: + from hashlib import md5 +except ImportError: + from md5 import md5 + + +class HashableObject(object): + """Define how to hash property classes.""" + __fields__ = None + __leaf__ = False + + def __repr__(self): + + return ':'.join([repr(getattr(self, x)) for x in self.__fields__]) + + def __hash__(self): + +# return int(md5(repr(self)).hexdigest(), 16) + return hash(repr(self)) + +class Color(HashableObject): + """Named colors for use in styles.""" + BLACK = 'FF000000' + WHITE = 'FFFFFFFF' + RED = 'FFFF0000' + DARKRED = 'FF800000' + BLUE = 'FF0000FF' + DARKBLUE = 'FF000080' + GREEN = 'FF00FF00' + DARKGREEN = 'FF008000' + YELLOW = 'FFFFFF00' + DARKYELLOW = 'FF808000' + + __fields__ = ('index',) + __slots__ = __fields__ + __leaf__ = True + + def __init__(self, index): + super(Color, self).__init__() + self.index = index + + +class Font(HashableObject): + """Font options used in styles.""" + UNDERLINE_NONE = 'none' + UNDERLINE_DOUBLE = 'double' + UNDERLINE_DOUBLE_ACCOUNTING = 'doubleAccounting' + UNDERLINE_SINGLE = 'single' + UNDERLINE_SINGLE_ACCOUNTING = 'singleAccounting' + + __fields__ = ('name', + 'size', + 'bold', + 'italic', + 'superscript', + 'subscript', + 'underline', + 'strikethrough', + 'color') + __slots__ = __fields__ + + def __init__(self): + super(Font, self).__init__() + self.name = 'Calibri' + self.size = 11 + self.bold = False + self.italic = False + self.superscript = False + self.subscript = False + self.underline = self.UNDERLINE_NONE + self.strikethrough = False + self.color = Color(Color.BLACK) + + +class Fill(HashableObject): + """Area fill patterns for use in styles.""" + FILL_NONE = 'none' + FILL_SOLID = 'solid' + FILL_GRADIENT_LINEAR = 'linear' + FILL_GRADIENT_PATH = 'path' + FILL_PATTERN_DARKDOWN = 'darkDown' + FILL_PATTERN_DARKGRAY = 'darkGray' + FILL_PATTERN_DARKGRID = 'darkGrid' + FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal' + FILL_PATTERN_DARKTRELLIS = 'darkTrellis' + FILL_PATTERN_DARKUP = 'darkUp' + FILL_PATTERN_DARKVERTICAL = 'darkVertical' + FILL_PATTERN_GRAY0625 = 'gray0625' + FILL_PATTERN_GRAY125 = 'gray125' + FILL_PATTERN_LIGHTDOWN = 'lightDown' + FILL_PATTERN_LIGHTGRAY = 'lightGray' + FILL_PATTERN_LIGHTGRID = 'lightGrid' + FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal' + FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis' + FILL_PATTERN_LIGHTUP = 'lightUp' + FILL_PATTERN_LIGHTVERTICAL = 'lightVertical' + FILL_PATTERN_MEDIUMGRAY = 'mediumGray' + + __fields__ = ('fill_type', + 'rotation', + 'start_color', + 'end_color') + __slots__ = __fields__ + + def __init__(self): + super(Fill, self).__init__() + self.fill_type = self.FILL_NONE + self.rotation = 0 + self.start_color = Color(Color.WHITE) + self.end_color = Color(Color.BLACK) + + +class Border(HashableObject): + """Border options for use in styles.""" + BORDER_NONE = 'none' + BORDER_DASHDOT = 'dashDot' + BORDER_DASHDOTDOT = 'dashDotDot' + BORDER_DASHED = 'dashed' + BORDER_DOTTED = 'dotted' + BORDER_DOUBLE = 'double' + BORDER_HAIR = 'hair' + BORDER_MEDIUM = 'medium' + BORDER_MEDIUMDASHDOT = 'mediumDashDot' + BORDER_MEDIUMDASHDOTDOT = 'mediumDashDotDot' + BORDER_MEDIUMDASHED = 'mediumDashed' + BORDER_SLANTDASHDOT = 'slantDashDot' + BORDER_THICK = 'thick' + BORDER_THIN = 'thin' + + __fields__ = ('border_style', + 'color') + __slots__ = __fields__ + + def __init__(self): + super(Border, self).__init__() + self.border_style = self.BORDER_NONE + self.color = Color(Color.BLACK) + + +class Borders(HashableObject): + """Border positioning for use in styles.""" + DIAGONAL_NONE = 0 + DIAGONAL_UP = 1 + DIAGONAL_DOWN = 2 + DIAGONAL_BOTH = 3 + + __fields__ = ('left', + 'right', + 'top', + 'bottom', + 'diagonal', + 'diagonal_direction', + 'all_borders', + 'outline', + 'inside', + 'vertical', + 'horizontal') + __slots__ = __fields__ + + def __init__(self): + super(Borders, self).__init__() + self.left = Border() + self.right = Border() + self.top = Border() + self.bottom = Border() + self.diagonal = Border() + self.diagonal_direction = self.DIAGONAL_NONE + + self.all_borders = Border() + self.outline = Border() + self.inside = Border() + self.vertical = Border() + self.horizontal = Border() + + +class Alignment(HashableObject): + """Alignment options for use in styles.""" + HORIZONTAL_GENERAL = 'general' + HORIZONTAL_LEFT = 'left' + HORIZONTAL_RIGHT = 'right' + HORIZONTAL_CENTER = 'center' + HORIZONTAL_CENTER_CONTINUOUS = 'centerContinuous' + HORIZONTAL_JUSTIFY = 'justify' + VERTICAL_BOTTOM = 'bottom' + VERTICAL_TOP = 'top' + VERTICAL_CENTER = 'center' + VERTICAL_JUSTIFY = 'justify' + + __fields__ = ('horizontal', + 'vertical', + 'text_rotation', + 'wrap_text', + 'shrink_to_fit', + 'indent') + __slots__ = __fields__ + __leaf__ = True + + def __init__(self): + super(Alignment, self).__init__() + self.horizontal = self.HORIZONTAL_GENERAL + self.vertical = self.VERTICAL_BOTTOM + self.text_rotation = 0 + self.wrap_text = False + self.shrink_to_fit = False + self.indent = 0 + + +class NumberFormat(HashableObject): + """Numer formatting for use in styles.""" + FORMAT_GENERAL = 'General' + FORMAT_TEXT = '@' + FORMAT_NUMBER = '0' + FORMAT_NUMBER_00 = '0.00' + FORMAT_NUMBER_COMMA_SEPARATED1 = '#,##0.00' + FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-' + FORMAT_PERCENTAGE = '0%' + FORMAT_PERCENTAGE_00 = '0.00%' + FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd' + FORMAT_DATE_YYYYMMDD = 'yy-mm-dd' + FORMAT_DATE_DDMMYYYY = 'dd/mm/yy' + FORMAT_DATE_DMYSLASH = 'd/m/y' + FORMAT_DATE_DMYMINUS = 'd-m-y' + FORMAT_DATE_DMMINUS = 'd-m' + FORMAT_DATE_MYMINUS = 'm-y' + FORMAT_DATE_XLSX14 = 'mm-dd-yy' + FORMAT_DATE_XLSX15 = 'd-mmm-yy' + FORMAT_DATE_XLSX16 = 'd-mmm' + FORMAT_DATE_XLSX17 = 'mmm-yy' + FORMAT_DATE_XLSX22 = 'm/d/yy h:mm' + FORMAT_DATE_DATETIME = 'd/m/y h:mm' + FORMAT_DATE_TIME1 = 'h:mm AM/PM' + FORMAT_DATE_TIME2 = 'h:mm:ss AM/PM' + FORMAT_DATE_TIME3 = 'h:mm' + FORMAT_DATE_TIME4 = 'h:mm:ss' + FORMAT_DATE_TIME5 = 'mm:ss' + FORMAT_DATE_TIME6 = 'h:mm:ss' + FORMAT_DATE_TIME7 = 'i:s.S' + FORMAT_DATE_TIME8 = 'h:mm:ss@' + FORMAT_DATE_YYYYMMDDSLASH = 'yy/mm/dd@' + FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-' + FORMAT_CURRENCY_USD = '$#,##0_-' + FORMAT_CURRENCY_EUR_SIMPLE = '[$EUR ]#,##0.00_-' + _BUILTIN_FORMATS = { + 0: 'General', + 1: '0', + 2: '0.00', + 3: '#,##0', + 4: '#,##0.00', + + 9: '0%', + 10: '0.00%', + 11: '0.00E+00', + 12: '# ?/?', + 13: '# ??/??', + 14: 'mm-dd-yy', + 15: 'd-mmm-yy', + 16: 'd-mmm', + 17: 'mmm-yy', + 18: 'h:mm AM/PM', + 19: 'h:mm:ss AM/PM', + 20: 'h:mm', + 21: 'h:mm:ss', + 22: 'm/d/yy h:mm', + + 37: '#,##0 (#,##0)', + 38: '#,##0 [Red](#,##0)', + 39: '#,##0.00(#,##0.00)', + 40: '#,##0.00[Red](#,##0.00)', + + 41: '_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)', + 42: '_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)', + 43: '_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)', + + 44: '_("$"* #,##0.00_)_("$"* \(#,##0.00\)_("$"* "-"??_)_(@_)', + 45: 'mm:ss', + 46: '[h]:mm:ss', + 47: 'mmss.0', + 48: '##0.0E+0', + 49: '@', } + _BUILTIN_FORMATS_REVERSE = dict( + [(value, key) for key, value in _BUILTIN_FORMATS.items()]) + + __fields__ = ('_format_code', + '_format_index') + __slots__ = __fields__ + __leaf__ = True + + DATE_INDICATORS = 'dmyhs' + + def __init__(self): + super(NumberFormat, self).__init__() + self._format_code = self.FORMAT_GENERAL + self._format_index = 0 + + def _set_format_code(self, format_code = FORMAT_GENERAL): + """Setter for the format_code property.""" + self._format_code = format_code + self._format_index = self.builtin_format_id(format = format_code) + + def _get_format_code(self): + """Getter for the format_code property.""" + return self._format_code + + format_code = property(_get_format_code, _set_format_code) + + def builtin_format_code(self, index): + """Return one of the standard format codes by index.""" + return self._BUILTIN_FORMATS[index] + + def is_builtin(self, format = None): + """Check if a format code is a standard format code.""" + if format is None: + format = self._format_code + return format in self._BUILTIN_FORMATS.values() + + def builtin_format_id(self, format): + """Return the id of a standard style.""" + return self._BUILTIN_FORMATS_REVERSE.get(format, None) + + def is_date_format(self, format = None): + """Check if the number format is actually representing a date.""" + if format is None: + format = self._format_code + + return any([x in format for x in self.DATE_INDICATORS]) + +class Protection(HashableObject): + """Protection options for use in styles.""" + PROTECTION_INHERIT = 'inherit' + PROTECTION_PROTECTED = 'protected' + PROTECTION_UNPROTECTED = 'unprotected' + + __fields__ = ('locked', + 'hidden') + __slots__ = __fields__ + __leaf__ = True + + def __init__(self): + super(Protection, self).__init__() + self.locked = self.PROTECTION_INHERIT + self.hidden = self.PROTECTION_INHERIT + + +class Style(HashableObject): + """Style object containing all formatting details.""" + __fields__ = ('font', + 'fill', + 'borders', + 'alignment', + 'number_format', + 'protection') + __slots__ = __fields__ + + def __init__(self): + super(Style, self).__init__() + self.font = Font() + self.fill = Fill() + self.borders = Borders() + self.alignment = Alignment() + self.number_format = NumberFormat() + self.protection = Protection() + +DEFAULTS = Style() diff --git a/tablib/packages/openpyxl3/tests/__init__.py b/tablib/packages/openpyxl3/tests/__init__.py new file mode 100644 index 0000000..de89032 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/__init__.py @@ -0,0 +1,24 @@ +# file openpyxl/tests/__init__.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni diff --git a/tablib/packages/openpyxl3/tests/helper.py b/tablib/packages/openpyxl3/tests/helper.py new file mode 100644 index 0000000..a60712b --- /dev/null +++ b/tablib/packages/openpyxl3/tests/helper.py @@ -0,0 +1,94 @@ +# file openpyxl/tests/helper.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +import os +import os.path +import shutil +import difflib +from io import BytesIO as StringIO +from pprint import pprint +from tempfile import gettempdir + +# package imports +from openpyxl.shared.xmltools import fromstring, ElementTree +from openpyxl.shared.xmltools import pretty_indent + +# constants +DATADIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test_data')) +TMPDIR = os.path.join(gettempdir(), 'openpyxl_test_temp') + + +def make_tmpdir(): + try: + os.makedirs(TMPDIR) + except OSError: + pass + + +def clean_tmpdir(): + if os.path.isdir(TMPDIR): + shutil.rmtree(TMPDIR, ignore_errors = True) + + +def assert_equals_file_content(reference_file, fixture, filetype = 'xml'): + if os.path.isfile(fixture): + with open(fixture) as fixture_file: + fixture_content = fixture_file.read() + else: + fixture_content = fixture + + with open(reference_file) as expected_file: + expected_content = expected_file.read() + + if filetype == 'xml': + fixture_content = fromstring(fixture_content) + pretty_indent(fixture_content) + temp = StringIO() + ElementTree(fixture_content).write(temp) + fixture_content = temp.getvalue() + + expected_content = fromstring(expected_content) + pretty_indent(expected_content) + temp = StringIO() + ElementTree(expected_content).write(temp) + expected_content = temp.getvalue() + + fixture_lines = fixture_content.split('\n') + expected_lines = expected_content.split('\n') + differences = list(difflib.unified_diff(expected_lines, fixture_lines)) + if differences: + temp = StringIO() + pprint(differences, stream = temp) + assert False, 'Differences found : %s' % temp.getvalue() + +def get_xml(xml_node): + + io = StringIO() + ElementTree(xml_node).write(io, encoding = 'UTF-8') + ret = io.getvalue() + io.close() + return ret.replace('\n', '') diff --git a/tablib/packages/openpyxl3/tests/test_cell.py b/tablib/packages/openpyxl3/tests/test_cell.py new file mode 100644 index 0000000..edc35c0 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_cell.py @@ -0,0 +1,200 @@ +# file openpyxl/tests/test_cell.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from datetime import time, datetime + +# 3rd party imports +from nose.tools import eq_, raises, assert_raises + +# package imports +from openpyxl.worksheet import Worksheet +from openpyxl.workbook import Workbook +from openpyxl.shared.exc import ColumnStringIndexException, \ + CellCoordinatesException, DataTypeException +from openpyxl.cell import column_index_from_string, \ + coordinate_from_string, get_column_letter, Cell, absolute_coordinate + + +def test_coordinates(): + column, row = coordinate_from_string('ZF46') + eq_("ZF", column) + eq_(46, row) + + +@raises(CellCoordinatesException) +def test_invalid_coordinate(): + coordinate_from_string('AAA') + + +def test_absolute(): + eq_('$ZF$51', absolute_coordinate('ZF51')) + +def test_absolute_multiple(): + + eq_('$ZF$51:$ZF$53', absolute_coordinate('ZF51:ZF$53')) + + +def test_column_index(): + eq_(10, column_index_from_string('J')) + eq_(270, column_index_from_string('jJ')) + eq_(7030, column_index_from_string('jjj')) + + +def test_bad_column_index(): + + @raises(ColumnStringIndexException) + def _check(bad_string): + column_index_from_string(bad_string) + + bad_strings = ('JJJJ', '', '$', '1',) + for bad_string in bad_strings: + yield _check, bad_string + + +def test_column_letter_boundries(): + assert_raises(ColumnStringIndexException, get_column_letter, 0) + assert_raises(ColumnStringIndexException, get_column_letter, 18279) + + +def test_column_letter(): + eq_('ZZZ', get_column_letter(18278)) + eq_('JJJ', get_column_letter(7030)) + eq_('AB', get_column_letter(28)) + eq_('AA', get_column_letter(27)) + eq_('Z', get_column_letter(26)) + + +def test_initial_value(): + cell = Cell(None, 'A', 1, value = '17.5') + eq_(cell.TYPE_NUMERIC, cell.data_type) + + +class TestCellValueTypes(): + + @classmethod + def setup_class(cls): + cls.cell = Cell(None, 'A', 1) + + def test_1st(self): + eq_(self.cell.TYPE_NULL, self.cell.data_type) + + def test_null(self): + self.cell.value = None + eq_(self.cell.TYPE_NULL, self.cell.data_type) + + def test_numeric(self): + + def check_numeric(value): + self.cell.value = value + eq_(self.cell.TYPE_NUMERIC, self.cell.data_type) + + values = (42, '4.2', '-42.000', '0', 0, 0.0001, '0.9999', '99E-02') + for value in values: + yield check_numeric, value + + def test_string(self): + self.cell.value = 'hello' + eq_(self.cell.TYPE_STRING, self.cell.data_type) + + def test_formula(self): + self.cell.value = '=42' + eq_(self.cell.TYPE_FORMULA, self.cell.data_type) + + def test_boolean(self): + self.cell.value = True + eq_(self.cell.TYPE_BOOL, self.cell.data_type) + self.cell.value = False + eq_(self.cell.TYPE_BOOL, self.cell.data_type) + + def test_error_codes(self): + + def check_error(): + eq_(self.cell.TYPE_ERROR, self.cell.data_type) + + for error_string in self.cell.ERROR_CODES.keys(): + self.cell.value = error_string + yield check_error + + +def test_data_type_check(): + cell = Cell(None, 'A', 1) + cell.bind_value(None) + eq_(Cell.TYPE_NULL, cell._data_type) + + cell.bind_value('.0e000') + eq_(Cell.TYPE_NUMERIC, cell._data_type) + + cell.bind_value('-0.e-0') + eq_(Cell.TYPE_NUMERIC, cell._data_type) + + cell.bind_value('1E') + eq_(Cell.TYPE_STRING, cell._data_type) + +@raises(DataTypeException) +def test_set_bad_type(): + cell = Cell(None, 'A', 1) + cell.set_value_explicit(1, 'q') + + +def test_time(): + + def check_time(raw_value, coerced_value): + cell.value = raw_value + eq_(cell.value, coerced_value) + eq_(cell.TYPE_NUMERIC, cell.data_type) + + wb = Workbook() + ws = Worksheet(wb) + cell = Cell(ws, 'A', 1) + values = (('03:40:16', time(3, 40, 16)), ('03:40', time(3, 40)),) + for raw_value, coerced_value in values: + yield check_time, raw_value, coerced_value + + +def test_date_format_on_non_date(): + wb = Workbook() + ws = Worksheet(wb) + cell = Cell(ws, 'A', 1) + cell.value = datetime.now() + cell.value = 'testme' + eq_('testme', cell.value) + + +def test_repr(): + wb = Workbook() + ws = Worksheet(wb) + cell = Cell(ws, 'A', 1) + eq_(repr(cell), '', 'Got bad repr: %s' % repr(cell)) + +def test_is_date(): + wb = Workbook() + ws = Worksheet(wb) + cell = Cell(ws, 'A', 1) + cell.value = datetime.now() + eq_(cell.is_date(), True) + cell.value = 'testme' + eq_('testme', cell.value) + eq_(cell.is_date(), False) diff --git a/tablib/packages/openpyxl3/tests/test_chart.py b/tablib/packages/openpyxl3/tests/test_chart.py new file mode 100644 index 0000000..cc3fcda --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_chart.py @@ -0,0 +1,131 @@ +# file openpyxl/tests/test_chart.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +from nose.tools import eq_ + +from openpyxl.tests.helper import get_xml +from openpyxl.shared.xmltools import Element +from openpyxl.writer.charts import ChartWriter +from openpyxl.workbook import Workbook +from openpyxl.chart import BarChart, ScatterChart, Serie, Reference +from openpyxl.style import Color + +class TestChartWriter(object): + + def setUp(self): + + wb = Workbook() + ws = wb.get_active_sheet() + ws.title = 'data' + for i in range(10): + ws.cell(row = i, column = 0).value = i + self.chart = BarChart() + self.chart.title = 'TITLE' + self.chart.add_serie(Serie(Reference(ws, (0, 0), (10, 0)))) + self.chart._series[-1].color = Color.GREEN + self.cw = ChartWriter(self.chart) + self.root = Element('test') + + def test_write_title(self): + self.cw._write_title(self.root) + eq_(get_xml(self.root), 'TITLE') + + def test_write_xaxis(self): + + self.cw._write_axis(self.root, self.chart.x_axis, 'c:catAx') + eq_(get_xml(self.root), '') + + def test_write_yaxis(self): + + self.cw._write_axis(self.root, self.chart.y_axis, 'c:valAx') + eq_(get_xml(self.root), '') + + def test_write_series(self): + + self.cw._write_series(self.root) + eq_(get_xml(self.root), 'data!$A$1:$A$11General0123456789None') + + def test_write_legend(self): + + self.cw._write_legend(self.root) + eq_(get_xml(self.root), '') + + def test_write_print_settings(self): + + self.cw._write_print_settings(self.root) + eq_(get_xml(self.root), '') + + def test_write_chart(self): + + self.cw._write_chart(self.root) + eq_(get_xml(self.root), 'TITLEdata!$A$1:$A$11General0123456789None') + + +class TestScatterChartWriter(object): + + def setUp(self): + + wb = Workbook() + ws = wb.get_active_sheet() + ws.title = 'data' + for i in range(10): + ws.cell(row = i, column = 0).value = i + ws.cell(row = i, column = 1).value = i + self.scatterchart = ScatterChart() + self.scatterchart.add_serie(Serie(Reference(ws, (0, 0), (10, 0)), + xvalues = Reference(ws, (0, 1), (10, 1)))) + self.cw = ChartWriter(self.scatterchart) + self.root = Element('test') + + def test_write_xaxis(self): + + self.cw._write_axis(self.root, self.scatterchart.x_axis, 'c:valAx') + eq_(get_xml(self.root), '') + + + def test_write_yaxis(self): + + self.cw._write_axis(self.root, self.scatterchart.y_axis, 'c:valAx') + eq_(get_xml(self.root), '') + + def test_write_series(self): + + self.cw._write_series(self.root) + eq_(get_xml(self.root), 'data!$B$1:$B$11General0123456789Nonedata!$A$1:$A$11General0123456789None') + + def test_write_legend(self): + + self.cw._write_legend(self.root) + eq_(get_xml(self.root), '') + + def test_write_print_settings(self): + + self.cw._write_print_settings(self.root) + eq_(get_xml(self.root), '') + + def test_write_chart(self): + + self.cw._write_chart(self.root) + eq_(get_xml(self.root), 'data!$B$1:$B$11General0123456789Nonedata!$A$1:$A$11General0123456789None') diff --git a/tablib/packages/openpyxl3/tests/test_data/genuine/empty-no-string.xlsx b/tablib/packages/openpyxl3/tests/test_data/genuine/empty-no-string.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..39490ac32434e02546ef2a888f12dedf4f5b58f4 GIT binary patch literal 7842 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 literal 0 HcmV?d00001 diff --git a/tablib/packages/openpyxl3/tests/test_data/genuine/empty-with-styles.xlsx b/tablib/packages/openpyxl3/tests/test_data/genuine/empty-with-styles.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..250e9b4365699542af00bce79d17024ea1ae1131 GIT binary patch literal 7511 zcmeHMhg(xg_YO@Uv`ClUL3#-i0TJn45Ks^h2q2wMLhn_O-aAMUgGy1VGz}f;9f^b@ zRk{?BB42cOzujg1{R7``=RUdf=`O`+wX2*bKbX>(y!#1~swo zk|kE~xb$MNj#c9`DI;+Gf?i3)>UY&Pp+>&n$PT+3q*naAnd5O`Zf5QkUr`iT&`4g6 z-tTH>hl=H#yYPgN+V1zml-b9`s@_G$JKB#0o@8hW330(t1R1;4U3bP5cD2;T#KFL5 z5OOb{w^-?NU`cq4n$S4*;(-mE-F*i)&(%((9sZ>iJ4rmDU%$|LTsZzCL^bBpnVYe2 zUia1sGOdx4SIN4D?XKV?`qaE%Uf+;}YgNC^SP|c2ys$fvFtL_&$C5XOFa=2$M0aoh ziPSPaV(h)nVZAxN&nN{%+NlO6MwGWP(KW5aU`BiBG?hGWL z`>M%3?!$yQe`-k%h)j%$HEIoquzO1;GXUdM=jS*8?Z3HOjlMAZ0fs{w7;hp13}BhM zS~<803I4dV6u$r0&HiWHD<)|W<5%f{YFQH|BXu4L6Y#xPFug8-7L!#0_6e3HmS2dJsFK6^}&k36a zP9E2^@$LQdT8Imqdeo50+@AF8V>j9L^5GCfnwvi+0m z!7?f7`rRBIOob#8}j zgO;5}%h}knw-m#g?fiohG_qeil8b)IZ)q+lM1@;5?~@-r?EZQtnz^}U&(My~^*QvO zHWeqqKGzi56Nt`Q$;Jn@PaEC9jR$gZOpQ<8L(wpRSJW4eRoEK6?#yL;DoU|rh|78m z8eY+&A3J1uc^mm z6C3JGYCLirD*8sLn&Emtdx<;{QCFOU{M@#bh?=dZ$%j43Jl|%6JfjXRn_O^1kR>^e zyvY^TgO;Gd{`EHEj6Gwb@|aF+!BsOu^sIo71(|6l11(lH-;>>zY~{okbPjK_Gf2ht z?JSJ8u2!FPyHme$CMksJr*!Uu?cR(zbM|cp(w3B4g%6fU)*{ZRpgJzM*Bg~QNZ%_8 zP&rjRrrmDvM311Y@ibgkOvCnhfZaUc8{bu@EWW~1>=4Vll!afE7pFxFeX8yGZZ|a~ z=)S|9I7OUB;Kpz3$n=kZ`$(4^I1jfXuE=?yRRuImWwR1Lq>HF6Fc>haNnQ1mM>u%& zHWz%9XbCdRX>WTtEaX?2l1IordTYDOOibX4D_@d=r&K!p6<;E5E99<=e&V}%f>_({ z8OsG0r=Pyi|H@-Mye6FsFth#!^P&80_T6mEU9Bv2;jXq$)^0x>794vE_;U$DJPE4m zN(B`-G->X*lpO#H7*VofrJ1Fxo3TXc+ks4jW81=|G+c*?yi?SiJjH^Cc@e;^O>Z|q z#{$Arm*eGJC~=KR1#uN(gZ!TG<38oGH}oP9!G)NVd}R%!YA4txkD?(@S%EMaox1b@ ze}zzwh9>DFUQI%vpx1Gg_(s`f)U~?1CRz3t)dod@MFX}cc)uEwF4Wy^1VgAg%>I_- z_aVW(9jtzirCJYZ_fZ(sOmj>o_qEeLIqcfhKtUx)MUK^IRlnA1I7<= zn>RRv_o1@ ze>+fTat0i@>H)9Zn$q53hkB-S6v*(hx2CSAJiQsar^MpupB*;9$11NOnqb*D;|L9_ zZR(@97O9**ppNR;;Mv;#Z;CUI6{d^m$yMy? zwuuv)mSoOV<-aZzoCJO+#}bX(t+SjftB2mQZF_hk4A(>7k>JV@*@|7Ah-U3~x<=Sz zP}#?~!s#C9yWgX~{Dh99@IhTyec}45(RBsu?u7f*13(rr@BR!DNv$%#gN3l~V4A6w zh!5xu5}EFh(U%q!4>Tpa9kjegxfWop&k z%*Nlze(FN#bCy>W00N&dYg7%gO2)-RRO_^clcq(z_6i4nXE4I|+ms!*mvv0PQbxz3 z;r&3rCc02bXV?OCP^(w4tTGhyY|4`b4s+zLrN?)F zJoPHOOq0Id=@FA{W}1O+q3npNKmGKlAgs0q^K`7nO<@PVA?!wYK9|v>GMrPW)x!ruP%$K(q#pNDb7{++_B2#C08`kkx2F!bELL@7#lJAG)n z;4DDx#v=^~N+28t@GHt{rQwN;kZu4+6UL7}H;CTx7d!?G$wpAcCT4M4hWJM=2*xRe zK>GnOa^s9w@-4=6BTWhWc$#{s6Jcb+Wg?%7yukC%-*opse@p)KXfTfXJN$HYc(;vN zK)`xlFfMeVHGMYVaAlIY=w@EP-j`jPB`?GF^W!$|x2LDx*FmB-ti#bl`KN~)J<%01 zXJ?IFoI8%e%8Ew`N(zNWiv)~21aPqk7fx<=mlmovkghczX|M<3S{{&buI&0lt9nwG zSkHU1yF54#@@G6P8N;9@HiRqm=8J%@5Nk$3H4Da+H!lzPY9IdCEQhI7D4WXO&5jQA1r*w3wYc zfjZj57B?U>eT}%>!=cKP#vQ^p>hp-9K}Ch2Pc4j@d^uxRGVdxwY9hlzBo#>j#fwUf zZlv*%qVa9s9k?v5a*`czv-Vx()Rwvoq!eBpeKY;6p+-jxY9FhzTz$7ejg)8X%e*yes`*ttAzuYJLe5y{7G^JnkOOd&5r7$lgfaoLtFSVI+`oYT1V!vJym$b{GGXzGnC*Y-1!6F5=hOw6cx_ZvBk zB(9@p1z2|_z(UGvSB;!0h7a@KLuv;qPl^)o`?= zEV=f6t)Z#&v+TVa<_-cM3{U;FELQq6dk1hRvGIT@7PYY)R{!aI3}4D;s5}& zzoo1n8(=pZD=WC0(9iB?-ip!fbwXi;8=BRdpeALq-Y9zcJ7!?-b!R??KR z!$byX_emlSXl(X&^(MVT8OkrCQ5pY7WGX35*A>-{WM z&gpno!IVE133&lK4umLn^QAv9ikU|bR1{AbtfQi*>_v2+!bK5%sv-Vdo8_%u{e%1$ zjzv`Y4C_y5@MH5g*KQ!rg7mI^PQI%v=>GxHDh#3Ta2qL>WU$e;A< z`6mhQ;}}eTVlurAo{ku z`w{xSG^{lq{>Eez_Dd=hUM4XS!BBx0Qwv?j)X$bM3vE}Jvzw3w%+=~QVfNp$88dT$ zutZb!Het$+S-iLMJ-*p+`fMV{uTM5qZ&el^Q71JDOr5eud7K=UY~JMq$~|xzc=I;L zSJxr#xvC7(+ZG=6CFTh(SVHcze^lPOqfaO~a|ZQG5jr(EcKo&R1AU~xQYW%VjM_M1 zE?H$H&()|? zGiBb&Ki1JLZ8UL_)n|n)ks1h>^X^oGng&G>EJ-RQ@^R5$MBg^Qs6%{COHOjn60(=G z2}Day#DCvjjgY;oQ_ScyRS8gStl}DdrEN&A zFrjj|U*uTq(vVw1(UX645S7w!!=q)DwJ5rPyjSrmkl@;cAOr7i^|{ZUBBct~SZ5a? z6@vBH=xjH1dO3q78Haz0&5ZMYVRah0BT+6AuQxcAYk2_l>>G>4D1_+iaEk{>rTqPu zsxbCjh=LYXSOnD7`0M`L68CBxW|{5VQNcA0>OE5Sk*1|@rLx&OBOX60!mC&`*3+oN zImu$og_kZ$>r2^7J4_i!bh7h!UqvVjw++>HT8%w_=1!Id6&yNSepRNh+si@>NnO?S z-~w5YIO>Xa%CK6Ui7nGOU=gG)MwCJ#_R!uNGHXjy?~v}F%!Gq0onz}g7qJ4tM#*c; z?Y-A`yM=ePAdSJW2DbqTnM|0^N^_Kn=zM#t(SCn8ji1_y7mQ%wQ*D|b literal 0 HcmV?d00001 diff --git a/tablib/packages/openpyxl3/tests/test_data/genuine/empty.xlsx b/tablib/packages/openpyxl3/tests/test_data/genuine/empty.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..3749d02093e170795608aa9c00a2b8e4e2be3a8d GIT binary patch literal 11005 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

j@~^R=nw%o zamN={t&gS>+}A@l(K9uDi*fVzemvj`*o||dR~OhH>^%>&+o01&!gZ+AQ6k`Qz;wCm zd?T5I9xY-;fP&~)q~XyP;ZlP~9b|A-P~N4g|6!YvpFZ8VN`?KjEI%eAkB2Z#+-d!) zB=;&iE;rJykmQq{HY4i;yc?*%{KP~9l?Dr;;Zg22!z;g)(QXk{Qvi6vtO8C#V*Vi`Co^MfeYfaNY2Et<_W$MmHV>zIqV9rwI8?# zkrVDuEtiQ6%a=XFE;44E0`C;2u?Qt-=)+Elniz_gj6l8ccKWXM^UO5DvMer3y@9p9 z&dS{v=iPu)r_mFvE?QH~{aLLyE)F-aol~rpv-{4T^_kfadMj{p6%69(-yb9@5+)e@ z4mMa1ZR(XOT7E#@f2T^*%91ah02NLbQGhLmc501iyEeg>*4PUeIn~8m=B`+DM31U$-^aac zm5aUWgpJVg7}5u)yPb*C9#Td|(>11;!1eBw6`#xPS<(WbY@hS9Q`{{#jo!zb9-5B3 zJ9i#5Rx|R6$d@^Hmj^?URf6~TEdx|1*8Y;>KVl`s^0hXf5uZGBVhgdSqM@*F$L>K> zF@-_#cY)u{Mj&3T;4v|7La~o_eItBI3-vZ<$<>HB$e_p!ew$E#6L7_CO3VaAwi7*n zq3*^V8R+oM;B_yto|k%mOQhjIeVYcP8XF>T;BqOJ+u(?eh@L1mm9}vEM`A3-;gQnb zob$Vz02x809TIV3Ss6LjLJFE#oJbdQXrHu{U8G7EtB9njIboNvHyRp|wNx~K2ide;AM75xx?2KG zPX+zeb(h_irONX$TpZeU^p6gJG@>7?-t6Lrx zEW@WUt}$SOB69(H6^^?Xt5r@%13-RS{t3X@~0~0k( z z)hcip-|!HE)W!iuf>(Eu z1oy?JVsL!yjW6Hs=Fxp;GdZBUWs(gRWZMF%B79=On zvXV&iHeL&viro!MK{jH@@w?A$QM@UyIIdJt0+^$gdqtY7Nt-XkCiKq{t9=od%Rt~8fHvcBx znm?8)*2NAwI)S*Zt58~FvZx}VL9;MJ=Y1c;@6|4~I`B(ki{a({AeB9OxZdZ4Ro7Qt!sN|TtOsV|yz7wVL`)xK7jee=< z_^r$G6UZ%R1xG$Y|9PhI%Wq|>EZP(>qj~F^KX#pM0O4b;7N0?=C6+A#D>xTqZmmYp z%71h(gcSOA1WKFLdzW59q~be{X?sz(2)>Z^0H^Y@CXGl`Ak#B|J38{dkIh*XwD#G% z5z+ziq|bP=n51sth%*0tT)jwEpUKVisIx{NCity1{O= zhU7XOIv8Fx`JPRO&f(p;0Qi>j_^uX^p?VIzAM;s@qXTQZku$-&vuJHl@oaT<yyy&E|SauE}W9$LS~rgCB6 zoLjCVk4Sufx?J*cHn$hzL)5km(8BO;zRSLQLWrBRoo4jI249jqLPkM%zS40M4SxVd zXf@}D$pxp^cS6mBQ!Gn@{Be>hc}AyW?Ur)}8(lsRL-VI;~Ue`+Y41Gvo&NpPt?Bd)__zRmL}~#Ep&wI#IGs~UW8Ds zvx7d2dN@CV`(5iRPyPMhfOFv{U`s&<*VK$`4HX@1?Hpek+Bz8jY(823e@kiLUIlo? zPXK$Eu|kevR`NC-K-AWuO(2&Yqc1o0!*@9MTs`eYX?Txj2B-#uRc zcoePOefzqL22X3|g`~ZhA|PTmUv;0TjbVF@5uw}=SY7rywg5_Q4_O{tZW)YrNJH(LHCh_OU4+~P??#yP?*&e!P{4hmqs-Xv<9g(;?6|cmc zL=cuKzR##K3fm!JODT@6b4Q%MoAr;o91nht&uzSmvf#?wM_dbY+`HV?Xl(oS&}D+j z+VIC6i;ot+V53@(GDqXOUxcjPXJqC#83tIn$1zqdbI4r-dSRdzu= z!`kvSAc`*}@24YHFyp1nh7ixVJu}7n{WL=vLfp3rtj1Ju^$qQ>YSg#0`>z;(%J7dZ zGoi}{T!suehPp%vI10~VzzY$6%~FWyq236v_k3f_i$g6FA_g~sqO>cd4}D;2Al=F>8Bjai;%dJM&TOX|WgIZMTNl7J_kGU{ntI8Q=A3;2g|~urO4d z5&|yUTXN2Q%JAj@%|4!BVsRj;MX(c6Xm85<3AK9a`Th1PZHr~Yl!@%Up`!(>&3qlS8=F zu7MUC!tpk7Gz}S->>#2L_L9-0xuHEB@5bji=|Ank7|90f1VDsAs!V+)!@kuxQtkAO zt?J(t=;;(#J)j3@SK|+x=NpdD+f}K@uS;rmfco!sFT%yN7BXhaxK!ztm*|7SjlL}F zB3pYc(YhA3SIxL?`1BQ1U+LrzGZf3$RH^y}^!tXd5Uu<7<5{H`snEIlt>?QF@3@?} zyO}xEWENk1M|3?6-@Gi`Bn5ZdA6X~}NJenR>Guok{@uI(KL2K!oubU&3I4wP=ik7e z=M1n3{be!GQ{dm%Qv4Md3ZCZw=am&td7jqG|76++uN{0+J%0**T5kOlyo>O^;C~cf zpRzoyUH!?j4$gS}s|kN)`Blk!%JMWZ{U?hI#&21ErKz8?JWVYA$ua@XGyX2k_>|#k z!tPIo7VJM5{x^O16#6tt^CuJ%oN5RE`O}omQ-Y@<^PdFuB)|OzzXIr|EYnW{qCZ)P z$$zo@Ej)UP{(C_3Cl&x$1f%~Eo;-#B-GlrUZch0Z_`m##q6`dJ?EnA@_!kQ{m^U;( Gul@&`W-g-u literal 0 HcmV?d00001 diff --git a/tablib/packages/openpyxl3/tests/test_data/genuine/merge_range.xlsx b/tablib/packages/openpyxl3/tests/test_data/genuine/merge_range.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..9452e362b623108660651ae51b199adb3d059133 GIT binary patch literal 8742 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 literal 0 HcmV?d00001 diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/app-multi-titles.xml b/tablib/packages/openpyxl3/tests/test_data/reader/app-multi-titles.xml new file mode 100644 index 0000000..b32ec40 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/reader/app-multi-titles.xml @@ -0,0 +1,43 @@ + + + 0 + false + + + + Worksheets + + + 7 + + + Named Ranges + + + 7 + + + + + + ToC + ContractYear + ContractTier + Demand + LinearizedFunction + Market + Transmission + Excel_BuiltIn_Print_Area_10 + Excel_BuiltIn_Print_Area_12 + Excel_BuiltIn_Print_Area_13 + TS_discount_rate!Excel_BuiltIn_Print_Area_26 + Excel_BuiltIn_Print_Area_27 + Excel_BuiltIn_Print_Area_28 + Excel_BuiltIn_Print_Area_29 + + + false + false + false + 12.0000 + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/empty-workbook-styles.xml b/tablib/packages/openpyxl3/tests/test_data/reader/empty-workbook-styles.xml new file mode 100644 index 0000000..4819c45 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/reader/empty-workbook-styles.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/merged-ranges.xml b/tablib/packages/openpyxl3/tests/test_data/reader/merged-ranges.xml new file mode 100644 index 0000000..0cb17cf --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/reader/merged-ranges.xml @@ -0,0 +1,2 @@ + +12340123464646546464610 \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/null_archive.xlsx b/tablib/packages/openpyxl3/tests/test_data/reader/null_archive.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..967263ab8a68d2f5626d2ba7b24575fa340bf924 GIT binary patch literal 126 zcmWIWW@h1H0D+T5%64D|l;8u>d8Iiy@oAYksd^PT#T5bGj7%a7xK)ERGBPNDC?G)B S%gP24WduSSAgu%9FaQ96VG&#a literal 0 HcmV?d00001 diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/null_file.xlsx b/tablib/packages/openpyxl3/tests/test_data/reader/null_file.xlsx new file mode 100644 index 0000000..e69de29 diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/shared-strings-rich.xml b/tablib/packages/openpyxl3/tests/test_data/reader/shared-strings-rich.xml new file mode 100644 index 0000000..b3b31f6 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/reader/shared-strings-rich.xml @@ -0,0 +1,33 @@ + + + + Welcome + + + + to the best + + + + + + + + + shop in + + + + + + + + + + town + + + + let's play + + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings-emptystring.xml b/tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings-emptystring.xml new file mode 100644 index 0000000..1dd7caf --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings-emptystring.xml @@ -0,0 +1,3 @@ + +Testing empty cell + diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings.xml b/tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings.xml new file mode 100644 index 0000000..aef87a0 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings.xml @@ -0,0 +1,2 @@ + +This is cell A1 in Sheet 1This is cell G5 \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/sheet2.xml b/tablib/packages/openpyxl3/tests/test_data/reader/sheet2.xml new file mode 100644 index 0000000..0176e57 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/reader/sheet2.xml @@ -0,0 +1,2 @@ + +10.0120.0230.0340.04510.0560.0677.0000000000000007E-280.0890.09100.1110.11120.12130.13140.14000000000000001150.15160.16170.17180.18190.19200.2210.21220.22230.23240.24250.25260.26270.27280.28000000000000003290.28999999999999998300.3 \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/simple-styles.xml b/tablib/packages/openpyxl3/tests/test_data/reader/simple-styles.xml new file mode 100644 index 0000000..2f3b255 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/reader/simple-styles.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/workbook.xml b/tablib/packages/openpyxl3/tests/test_data/reader/workbook.xml new file mode 100644 index 0000000..2697d4c --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/reader/workbook.xml @@ -0,0 +1,2 @@ + +'My Sheeet'!$D$8 \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/.rels b/tablib/packages/openpyxl3/tests/test_data/writer/expected/.rels new file mode 100644 index 0000000..450d174 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/[Content_Types].xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/[Content_Types].xml new file mode 100644 index 0000000..39285bb --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/[Content_Types].xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/app.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/app.xml new file mode 100644 index 0000000..a7cccf7 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/app.xml @@ -0,0 +1,2 @@ + +Microsoft Excel0falsefalsefalsefalse12.0000Worksheets3SheetSheet1Sheet2 \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/core.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/core.xml new file mode 100644 index 0000000..4a99a55 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/core.xml @@ -0,0 +1,2 @@ + +TEST_USERSOMEBODY2010-04-01T20:30:00Z2010-04-05T14:05:30Z \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/font.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/font.xml new file mode 100644 index 0000000..dabe15d --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/font.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sharedStrings.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sharedStrings.xml new file mode 100644 index 0000000..ff7ec02 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sharedStrings.xml @@ -0,0 +1,2 @@ + +helloworldnice \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1.xml new file mode 100644 index 0000000..ab96c9f --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + 0 + + + + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_auto_filter.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_auto_filter.xml new file mode 100644 index 0000000..b4178b8 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_auto_filter.xml @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_formula.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_formula.xml new file mode 100644 index 0000000..ed98da2 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_formula.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + 10 + + + + + 32 + + + + + F1+F2 + + + + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_both.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_both.xml new file mode 100644 index 0000000..a83d2a0 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_both.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + 0 + + + + diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_horiz.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_horiz.xml new file mode 100644 index 0000000..f5aa23f --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_horiz.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + 0 + + + + diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_vert.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_vert.xml new file mode 100644 index 0000000..12ca034 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_vert.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + 0 + + + + diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_height.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_height.xml new file mode 100644 index 0000000..428bbfa --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_height.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + 10 + + + + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml new file mode 100644 index 0000000..70e4eb4 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml.rels b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml.rels new file mode 100644 index 0000000..843c040 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml.rels @@ -0,0 +1,6 @@ + + + + + + diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_style.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_style.xml new file mode 100644 index 0000000..27af47e --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_style.xml @@ -0,0 +1 @@ +0.13 \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/simple-styles.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/simple-styles.xml new file mode 100644 index 0000000..2f3b255 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/simple-styles.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/styles.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/styles.xml new file mode 100644 index 0000000..6a1ffd1 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/styles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/theme1.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/theme1.xml new file mode 100644 index 0000000..012e249 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/theme1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml new file mode 100644 index 0000000..0e397a7 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml.rels b/tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml.rels new file mode 100644 index 0000000..c41181c --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_dump.py b/tablib/packages/openpyxl3/tests/test_dump.py new file mode 100644 index 0000000..714cfb3 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_dump.py @@ -0,0 +1,109 @@ + +# file openpyxl/tests/test_dump.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from datetime import time, datetime + +# 3rd party imports +from nose.tools import eq_, raises, assert_raises + +from openpyxl.workbook import Workbook +from openpyxl.cell import get_column_letter + +from openpyxl.reader.excel import load_workbook + +from openpyxl.writer.strings import StringTableBuilder + +from tempfile import NamedTemporaryFile +import os +import shutil + +def test_dump_sheet(): + + test_file = NamedTemporaryFile(prefix='openpyxl.', suffix='.xlsx', delete=False) + test_file.close() + test_filename = test_file.name + + wb = Workbook(optimized_write = True) + + ws = wb.create_sheet() + + letters = [get_column_letter(x+1) for x in range(20)] + + expected_rows = [] + + for row in range(20): + + expected_rows.append(['%s%d' % (letter, row+1) for letter in letters]) + + for row in range(20): + + expected_rows.append([(row+1) for letter in letters]) + + for row in range(10): + + expected_rows.append([datetime(2010, ((x % 12)+1), row+1) for x in range(len(letters))]) + + for row in range(20): + + expected_rows.append(['=%s%d' % (letter, row+1) for letter in letters]) + + for row in expected_rows: + + ws.append(row) + + wb.save(test_filename) + + wb2 = load_workbook(test_filename, True) + + ws = wb2.worksheets[0] + + + for ex_row, ws_row in zip(expected_rows[:-20], ws.iter_rows()): + + for ex_cell, ws_cell in zip(ex_row, ws_row): + + eq_(ex_cell, ws_cell.internal_value) + + os.remove(test_filename) + + +def test_table_builder(): + + sb = StringTableBuilder() + + result = {'a':0, 'b':1, 'c':2, 'd':3} + + for letter in sorted(result.keys()): + + for x in range(5): + + sb.add(letter) + + table = dict(sb.get_table()) + + for key,idx in result.items(): + eq_(idx, table[key]) diff --git a/tablib/packages/openpyxl3/tests/test_iter.py b/tablib/packages/openpyxl3/tests/test_iter.py new file mode 100644 index 0000000..b34ab95 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_iter.py @@ -0,0 +1,112 @@ +# file openpyxl/tests/test_iter.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +from nose.tools import eq_, raises, assert_raises +import os.path as osp +from openpyxl.tests.helper import DATADIR +from openpyxl.reader.iter_worksheet import get_range_boundaries +from openpyxl.reader.excel import load_workbook +import datetime + +class TestWorksheet(object): + + workbook_name = osp.join(DATADIR, 'genuine', 'empty.xlsx') + +class TestText(TestWorksheet): + sheet_name = 'Sheet1 - Text' + + expected = [['This is cell A1 in Sheet 1', None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, 'This is cell G5'], ] + + def test_read_fast_integrated(self): + + wb = load_workbook(filename = self.workbook_name, use_iterators = True) + ws = wb.get_sheet_by_name(name = self.sheet_name) + + for row, expected_row in zip(ws.iter_rows(), self.expected): + + row_values = [x.internal_value for x in row] + + eq_(row_values, expected_row) + + + def test_get_boundaries_range(self): + + eq_(get_range_boundaries('C1:C4'), (3, 1, 3, 4)) + + def test_get_boundaries_one(self): + + + eq_(get_range_boundaries('C1'), (3, 1, 4, 1)) + + def test_read_single_cell_range(self): + + wb = load_workbook(filename = self.workbook_name, use_iterators = True) + ws = wb.get_sheet_by_name(name = self.sheet_name) + + eq_('This is cell A1 in Sheet 1', list(ws.iter_rows('A1'))[0][0].internal_value) + +class TestIntegers(TestWorksheet): + + sheet_name = 'Sheet2 - Numbers' + + expected = [[x + 1] for x in range(30)] + + query_range = 'D1:E30' + + def test_read_fast_integrated(self): + + wb = load_workbook(filename = self.workbook_name, use_iterators = True) + ws = wb.get_sheet_by_name(name = self.sheet_name) + + for row, expected_row in zip(ws.iter_rows(self.query_range), self.expected): + + row_values = [x.internal_value for x in row] + + eq_(row_values, expected_row) + +class TestFloats(TestWorksheet): + + sheet_name = 'Sheet2 - Numbers' + query_range = 'K1:L30' + expected = expected = [[(x + 1) / 100.0] for x in range(30)] + +class TestDates(TestWorksheet): + + sheet_name = 'Sheet4 - Dates' + + def test_read_single_cell_date(self): + + wb = load_workbook(filename = self.workbook_name, use_iterators = True) + ws = wb.get_sheet_by_name(name = self.sheet_name) + + eq_(datetime.datetime(1973, 5, 20), list(ws.iter_rows('A1'))[0][0].internal_value) + eq_(datetime.datetime(1973, 5, 20, 9, 15, 2), list(ws.iter_rows('C1'))[0][0].internal_value) + + + diff --git a/tablib/packages/openpyxl3/tests/test_meta.py b/tablib/packages/openpyxl3/tests/test_meta.py new file mode 100644 index 0000000..3aefdcf --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_meta.py @@ -0,0 +1,50 @@ +# file openpyxl/tests/test_meta.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +import os.path + +# package imports +from openpyxl.tests.helper import DATADIR, assert_equals_file_content +from openpyxl.writer.workbook import write_content_types, write_root_rels +from openpyxl.workbook import Workbook + + +def test_write_content_types(): + wb = Workbook() + wb.create_sheet() + wb.create_sheet() + content = write_content_types(wb) + reference_file = os.path.join(DATADIR, 'writer', 'expected', + '[Content_Types].xml') + assert_equals_file_content(reference_file, content) + + +def test_write_root_rels(): + wb = Workbook() + content = write_root_rels(wb) + reference_file = os.path.join(DATADIR, 'writer', 'expected', '.rels') + assert_equals_file_content(reference_file, content) diff --git a/tablib/packages/openpyxl3/tests/test_named_range.py b/tablib/packages/openpyxl3/tests/test_named_range.py new file mode 100644 index 0000000..a56ed86 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_named_range.py @@ -0,0 +1,102 @@ +# file openpyxl/tests/test_named_range.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +import os.path + +# 3rd-party imports +from nose.tools import eq_, assert_raises + +# package imports +from openpyxl.tests.helper import DATADIR +from openpyxl.namedrange import split_named_range +from openpyxl.reader.workbook import read_named_ranges +from openpyxl.shared.exc import NamedRangeException +from openpyxl.reader.excel import load_workbook + + +def test_split(): + eq_([('My Sheet', '$D$8'), ], split_named_range("'My Sheet'!$D$8")) + + +def test_split_no_quotes(): + eq_([('HYPOTHESES', '$B$3:$L$3'), ], split_named_range('HYPOTHESES!$B$3:$L$3')) + + +def test_bad_range_name(): + assert_raises(NamedRangeException, split_named_range, 'HYPOTHESES$B$3') + + +def test_read_named_ranges(): + + class DummyWs(object): + title = 'My Sheeet' + + def __str__(self): + return self.title + + class DummyWB(object): + + def get_sheet_by_name(self, name): + return DummyWs() + + with open(os.path.join(DATADIR, 'reader', 'workbook.xml')) as handle: + content = handle.read() + named_ranges = read_named_ranges(content, DummyWB()) + eq_(["My Sheeet!$D$8"], [str(range) for range in named_ranges]) + +def test_oddly_shaped_named_ranges(): + + ranges_counts = ((4, 'TEST_RANGE'), + (3, 'TRAP_1'), + (13, 'TRAP_2')) + + def check_ranges(ws, count, range_name): + + eq_(count, len(ws.range(range_name))) + + wb = load_workbook(os.path.join(DATADIR, 'genuine', 'merge_range.xlsx'), + use_iterators = False) + + ws = wb.worksheets[0] + + for count, range_name in ranges_counts: + + yield check_ranges, ws, count, range_name + + +def test_merged_cells_named_range(): + + wb = load_workbook(os.path.join(DATADIR, 'genuine', 'merge_range.xlsx'), + use_iterators = False) + + ws = wb.worksheets[0] + + cell = ws.range('TRAP_3') + + eq_('B15', cell.get_coordinate()) + + eq_(10, cell.value) diff --git a/tablib/packages/openpyxl3/tests/test_number_format.py b/tablib/packages/openpyxl3/tests/test_number_format.py new file mode 100644 index 0000000..2f5c9da --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_number_format.py @@ -0,0 +1,142 @@ +# file openpyxl/tests/test_number_format.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +from datetime import datetime, date + +# 3rd party imports +from nose.tools import eq_, assert_almost_equal, assert_raises + +# package imports +from openpyxl.workbook import Workbook +from openpyxl.worksheet import Worksheet +from openpyxl.cell import Cell +from openpyxl.style import NumberFormat +from openpyxl.shared.date_time import SharedDate + + +class TestNumberFormat(object): + + @classmethod + def setup_class(cls): + cls.workbook = Workbook() + cls.worksheet = Worksheet(cls.workbook, 'Test') + cls.sd = SharedDate() + + def test_convert_date_to_julian(self): + eq_(40167, self.sd.to_julian(2009, 12, 20)) + + def test_convert_date_from_julian(self): + + def test_date_equal(julian, datetime): + + eq_(self.sd.from_julian(julian), datetime) + + date_pairs= ( + (40167, datetime(2009, 12, 20)), + (21980, datetime(1960, 03, 05)), + ) + + for count, dt in date_pairs: + yield test_date_equal, count, dt + + def test_convert_datetime_to_julian(self): + eq_(40167, self.sd.datetime_to_julian(datetime(2009, 12, 20))) + + def test_insert_float(self): + self.worksheet.cell('A1').value = 3.14 + eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) + + def test_insert_percentage(self): + self.worksheet.cell('A1').value = '3.14%' + eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) + assert_almost_equal(0.0314, self.worksheet.cell('A1').value) + + def test_insert_datetime(self): + self.worksheet.cell('A1').value = date.today() + eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) + + def test_insert_date(self): + self.worksheet.cell('A1').value = datetime.now() + eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) + + def test_internal_date(self): + dt = datetime(2010, 7, 13, 6, 37, 41) + self.worksheet.cell('A3').value = dt + eq_(40372.27616898148, self.worksheet.cell('A3')._value) + + def test_datetime_interpretation(self): + dt = datetime(2010, 7, 13, 6, 37, 41) + self.worksheet.cell('A3').value = dt + eq_(dt, self.worksheet.cell('A3').value) + + def test_date_interpretation(self): + dt = date(2010, 7, 13) + self.worksheet.cell('A3').value = dt + eq_(datetime(2010, 7, 13, 0, 0), self.worksheet.cell('A3').value) + + def test_number_format_style(self): + self.worksheet.cell('A1').value = '12.6%' + eq_(NumberFormat.FORMAT_PERCENTAGE, \ + self.worksheet.cell('A1').style.number_format.format_code) + + def test_date_format_on_non_date(self): + cell = self.worksheet.cell('A1') + + def check_date_pair(count, date_string): + cell.value = datetime.strptime(date_string, '%Y-%m-%d') + eq_(count, cell._value) + + date_pairs = ( + (15, '1900-01-15'), + (59, '1900-02-28'), + (61, '1900-03-01'), + (367, '1901-01-01'), + (2958465, '9999-12-31'), ) + for count, date_string in date_pairs: + yield check_date_pair, count, date_string + + def test_1900_leap_year(self): + assert_raises(ValueError, self.sd.from_julian, 60) + assert_raises(ValueError, self.sd.to_julian, 1900, 2, 29) + + def test_bad_date(self): + + def check_bad_date(year, month, day): + assert_raises(ValueError, self.sd.to_julian, year, month, day) + + bad_dates = ((1776, 07, 04), (1899, 12, 31), ) + for year, month, day in bad_dates: + yield check_bad_date, year, month, day + + def test_bad_julian_date(self): + assert_raises(ValueError, self.sd.from_julian, -1) + + def test_mac_date(self): + self.sd.excel_base_date = self.sd.CALENDAR_MAC_1904 + assert_raises(NotImplementedError, self.sd.to_julian, 2000, 1, 1) + assert_raises(NotImplementedError, self.sd.from_julian, 1) + self.sd.excel_base_date = self.sd.CALENDAR_WINDOWS_1900 diff --git a/tablib/packages/openpyxl3/tests/test_password_hash.py b/tablib/packages/openpyxl3/tests/test_password_hash.py new file mode 100644 index 0000000..fd2fddf --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_password_hash.py @@ -0,0 +1,41 @@ +# file openpyxl/tests/test_password_hash.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# 3rd party imports +from nose.tools import eq_ + +# package imports +from openpyxl.shared.password_hasher import hash_password +from openpyxl.worksheet import SheetProtection + + +def test_hasher(): + eq_('CBEB', hash_password('test')) + + +def test_sheet_protection(): + protection = SheetProtection() + protection.password = 'test' + eq_('CBEB', protection.password) diff --git a/tablib/packages/openpyxl3/tests/test_props.py b/tablib/packages/openpyxl3/tests/test_props.py new file mode 100644 index 0000000000000000000000000000000000000000..1c5adb64a9f03e67c98e58fbe7fb1c606cce0ec4 GIT binary patch literal 9060 zcmds+ZBJXt702gwKE+k~!p>@O!jiUXq?JM-B&c8zaW>7i72?SY4Pe$LCA(EWaKAJkL+y_Zc9MDq`=G?MVXHrB_d>MJb*@NV(SM}z zoAg99pR`bMF8q66^v3B*=O_AH2;a3%Fe`+Qy|Z*I?h4@_rz7pc;c5C>D?{6LIfj~_ z=){rMeh>uvx58G~8O|f=Sa_YnNLX*Rhn8GZK)UYGhI2O(4e*?o9C3Q6`JK^$G8~=S z2>&DTaG^ElvCs}HA0;1JhTg6AorACfl6JL@v_*QD-ZW@Dls2<8r-C|?rJO%Fcm6or zJXwg(3&T7zFDOi3SQmSpOnCu2m`;TEvt$2wuT8}V^0Zt#P1g9kP^KQ?I`=VS_bVmc05ZkNZRG8Q#fFQ-~_J}$I| z?z4^CbO^6uW0%MM$n{Y7DzG`sowy!J#?RRXdEAOa$uO4wU0S?5@ZdjiKQbM-tbY;T z*e+l~|3g6`LcPJ z>!cdR^;1|9&%G3eW5ZMRIhUFh=PV$bwsLFY9e9K110oKrH{xkn>+gdBqzGi#ryR@A)wYsU5hk{^-{fB0scltd@uLbv3V^QKb5L{pL z_gVpWVRL4`7kpp1@UfTa4@Tpquz__?^js6$X;(Wtf`u-yoZ8UJTh00XN^37lnth#w zHt@Up8h3$)e~1bYsLp1v!0uoXP+=o6B; z#@0<2*x_KR%R;>DiTZn^@k%Gr5->Z`A`&CT3;lB4A@z>UvSfX;A(~$oPrGJ|P5s{0 zA3TTMu%2r=Y=||Qdc;4lMnHy~cnjLu(9X6nF>}8`0(64i*u@vHE4p@%sZz!p(KJxl zgIRbBn%*-BTw~#}P6v#^jnl-=Rer+LL+@>I<5@vR{7wWx|qkxhkSa<4&*7IsE1=a!zy*>aRUqekLUQ6#tt3O zx&EBP=gK{FSmgV$(SE5BehT@-QTkcyWcKKo_Eqm3`dSd=uv5c-tQplcau~IN{>H1w zVLH?dI`Dm_*{iOkd-{`oh4A`Q(dh|R%+pmPLtQ^;;P)%bwstn{yjmC z^tsfm4qs^ZM@bU40Pjiqpl|w)I&2+xcuw@m(1<>{CywbZyav{>;N9zr&UKC3iYkv- z@_C+LiT(#+I22CoPqiK!B>foO-gV2~pCm=m_#8=MG=Q!SHU21Xp)yW13ws|*VmuQ5 z`L0@VR!V&BNF0H)rx?P=KUl9Y(%6(Wp!0pfdGSumDFcHR+W61s=@riO;U0os=wHyq z#Li+ZB)pb%J9*dIsx>=aHT0wq$@#182u9)Dd>#pFG#I+xSG^ z9dVcCY4V1zbL+h*dv`dgXYi15u^dEYNBEqZd08KBGC#{(fJlQ|ZZvn&d>UZ9*VAe*P`g{nx|aK#`AolnmY@8bkdt@?JQ<9xh{@`_;_Ih)@qb zd(X6@9E1hv6Ibjzx<%?J{ZV_KiPur8x8DrACfOnhdEF*^anJO(L3&f()xz(ZWAqYr z&-a+A;^-ND`k?#Nw0TiNZq8mq4>Z5B&BJk@xDE=`mtP*?^R*e;CAtTqv@!LEjgl_^ zDd3Ity=0~;hrcxU2UeHz{-)r$kMU;8nZ-PGbR+i85IPsS?^B_+lCyH?j7arr>ixg#kX*_e_COuHzt$ee_h&O;8@@%ifuO)UE-(8p zG;S(8FN;6(>+#IJYndBB^yGd6SSxP=Pc*i5oo$jeZvZaIGPHkfd1$q~i}(x<+9=oS zrPKHl58!Ru?|5xgzT|zd+u{L{1!BBkAD89Ib(eme4vdP4g`-}Ek5~J+WokrSpPq$- z+b@YV?=u%g+MRB`Zo8-${8yEi()KJ0d^Hy7rgJ^}*PW?rV_EMCnc?#P!<$^9U+hc& E1ogd)IRF3v literal 0 HcmV?d00001 diff --git a/tablib/packages/openpyxl3/tests/test_read.py b/tablib/packages/openpyxl3/tests/test_read.py new file mode 100644 index 0000000..eb6ab07 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_read.py @@ -0,0 +1,134 @@ +# file openpyxl/tests/test_read.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +import os.path + +# 3rd party imports +from nose.tools import eq_, raises + +# package imports +from openpyxl.tests.helper import DATADIR +from openpyxl.worksheet import Worksheet +from openpyxl.workbook import Workbook +from openpyxl.style import NumberFormat, Style +from openpyxl.reader.worksheet import read_worksheet, read_dimension +from openpyxl.reader.excel import load_workbook +from openpyxl.shared.exc import InvalidFileException + + +def test_read_standalone_worksheet(): + + class DummyWb(object): + + def get_sheet_by_name(self, value): + return None + + path = os.path.join(DATADIR, 'reader', 'sheet2.xml') + with open(path) as handle: + ws = read_worksheet(handle.read(), DummyWb(), + 'Sheet 2', {1: 'hello'}, {1: Style()}) + assert isinstance(ws, Worksheet) + eq_(ws.cell('G5').value, 'hello') + eq_(ws.cell('D30').value, 30) + eq_(ws.cell('K9').value, 0.09) + + +def test_read_standard_workbook(): + path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') + wb = load_workbook(path) + assert isinstance(wb, Workbook) + +def test_read_standard_workbook_from_fileobj(): + path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') + fo = open(path, mode = 'rb') + wb = load_workbook(fo) + assert isinstance(wb, Workbook) + +def test_read_worksheet(): + path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') + wb = load_workbook(path) + sheet2 = wb.get_sheet_by_name('Sheet2 - Numbers') + assert isinstance(sheet2, Worksheet) + eq_('This is cell G5', sheet2.cell('G5').value) + eq_(18, sheet2.cell('D18').value) + + +def test_read_nostring_workbook(): + genuine_wb = os.path.join(DATADIR, 'genuine', 'empty-no-string.xlsx') + wb = load_workbook(genuine_wb) + assert isinstance(wb, Workbook) + +@raises(InvalidFileException) +def test_read_empty_file(): + + null_file = os.path.join(DATADIR, 'reader', 'null_file.xlsx') + wb = load_workbook(null_file) + +@raises(InvalidFileException) +def test_read_empty_archive(): + + null_file = os.path.join(DATADIR, 'reader', 'null_archive.xlsx') + wb = load_workbook(null_file) + +def test_read_dimension(): + + path = os.path.join(DATADIR, 'reader', 'sheet2.xml') + + with open(path) as handle: + + dimension = read_dimension(xml_source = handle.read()) + + eq_(('D', 1, 'K', 30), dimension) + +class TestReadWorkbookWithStyles(object): + + @classmethod + def setup_class(cls): + cls.genuine_wb = os.path.join(DATADIR, 'genuine', \ + 'empty-with-styles.xlsx') + wb = load_workbook(cls.genuine_wb) + cls.ws = wb.get_sheet_by_name('Sheet1') + + def test_read_general_style(self): + eq_(self.ws.cell('A1').style.number_format.format_code, + NumberFormat.FORMAT_GENERAL) + + def test_read_date_style(self): + eq_(self.ws.cell('A2').style.number_format.format_code, + NumberFormat.FORMAT_DATE_XLSX14) + + def test_read_number_style(self): + eq_(self.ws.cell('A3').style.number_format.format_code, + NumberFormat.FORMAT_NUMBER_00) + + def test_read_time_style(self): + eq_(self.ws.cell('A4').style.number_format.format_code, + NumberFormat.FORMAT_DATE_TIME3) + + def test_read_percentage_style(self): + eq_(self.ws.cell('A5').style.number_format.format_code, + NumberFormat.FORMAT_PERCENTAGE_00) diff --git a/tablib/packages/openpyxl3/tests/test_strings.py b/tablib/packages/openpyxl3/tests/test_strings.py new file mode 100644 index 0000000..bf3cc57 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_strings.py @@ -0,0 +1,68 @@ +# file openpyxl/tests/test_strings.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +import os.path + +# 3rd party imports +from nose.tools import eq_ + +# package imports +from openpyxl.tests.helper import DATADIR +from openpyxl.workbook import Workbook +from openpyxl.writer.strings import create_string_table +from openpyxl.reader.strings import read_string_table + + +def test_create_string_table(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('B12').value = 'hello' + ws.cell('B13').value = 'world' + ws.cell('D28').value = 'hello' + table = create_string_table(wb) + eq_({'hello': 1, 'world': 0}, table) + + +def test_read_string_table(): + with open(os.path.join(DATADIR, 'reader', 'sharedStrings.xml')) as handle: + content = handle.read() + string_table = read_string_table(content) + eq_({0: 'This is cell A1 in Sheet 1', 1: 'This is cell G5'}, string_table) + +def test_empty_string(): + with open(os.path.join(DATADIR, 'reader', 'sharedStrings-emptystring.xml')) as handle: + content = handle.read() + string_table = read_string_table(content) + eq_({0: 'Testing empty cell', 1:''}, string_table) + +def test_formatted_string_table(): + with open(os.path.join(DATADIR, 'reader', 'shared-strings-rich.xml')) \ + as handle: + content = handle.read() + string_table = read_string_table(content) + eq_({0: 'Welcome', 1: 'to the best shop in town', + 2: " let's play "}, string_table) diff --git a/tablib/packages/openpyxl3/tests/test_style.py b/tablib/packages/openpyxl3/tests/test_style.py new file mode 100644 index 0000000..a11ea43 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_style.py @@ -0,0 +1,181 @@ +# file openpyxl/tests/test_style.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +import os.path +import datetime + +# 3rd party imports +from nose.tools import eq_, assert_false, ok_ + +# package imports +from openpyxl.tests.helper import DATADIR, assert_equals_file_content, get_xml +from openpyxl.reader.style import read_style_table +from openpyxl.workbook import Workbook +from openpyxl.style import NumberFormat +from openpyxl.writer.styles import StyleWriter +from openpyxl.style import NumberFormat, Border, Color + + +class TestCreateStyle(object): + + @classmethod + def setup_class(cls): + now = datetime.datetime.now() + cls.workbook = Workbook() + cls.worksheet = cls.workbook.create_sheet() + cls.worksheet.cell(coordinate = 'A1').value = '12.34%' + cls.worksheet.cell(coordinate = 'B4').value = now + cls.worksheet.cell(coordinate = 'B5').value = now + cls.worksheet.cell(coordinate = 'C14').value = 'This is a test' + cls.worksheet.cell(coordinate = 'D9').value = '31.31415' + cls.worksheet.cell(coordinate = 'D9').style.number_format.format_code = \ + NumberFormat.FORMAT_NUMBER_00 + cls.writer = StyleWriter(cls.workbook) + + def test_create_style_table(self): + eq_(3, len(self.writer.style_table)) + + def test_write_style_table(self): + reference_file = os.path.join(DATADIR, 'writer', 'expected', 'simple-styles.xml') + assert_equals_file_content(reference_file, self.writer.write_table()) + +class TestStyleWriter(object): + + def setUp(self): + + self.workbook = Workbook() + self.worksheet = self.workbook.create_sheet() + + def test_no_style(self): + + w = StyleWriter(self.workbook) + eq_(0, len(w.style_table)) + + def test_nb_style(self): + + for i in range(1, 6): + self.worksheet.cell(row=1, column=i).style.font.size += i + w = StyleWriter(self.workbook) + eq_(5, len(w.style_table)) + + self.worksheet.cell('A10').style.borders.top = Border.BORDER_THIN + w = StyleWriter(self.workbook) + eq_(6, len(w.style_table)) + + def test_style_unicity(self): + + for i in range(1, 6): + self.worksheet.cell(row=1, column=i).style.font.bold = True + w = StyleWriter(self.workbook) + eq_(1, len(w.style_table)) + + def test_fonts(self): + + self.worksheet.cell('A1').style.font.size = 12 + self.worksheet.cell('A1').style.font.bold = True + w = StyleWriter(self.workbook) + w._write_fonts() + eq_(get_xml(w._root), '') + + def test_fills(self): + + self.worksheet.cell('A1').style.fill.fill_type = 'solid' + self.worksheet.cell('A1').style.fill.start_color.index = Color.DARKYELLOW + w = StyleWriter(self.workbook) + w._write_fills() + eq_(get_xml(w._root), '') + + def test_borders(self): + + self.worksheet.cell('A1').style.borders.top.border_style = Border.BORDER_THIN + self.worksheet.cell('A1').style.borders.top.color.index = Color.DARKYELLOW + w = StyleWriter(self.workbook) + w._write_borders() + eq_(get_xml(w._root), '') + + def test_write_cell_xfs_1(self): + + self.worksheet.cell('A1').style.font.size = 12 + w = StyleWriter(self.workbook) + ft = w._write_fonts() + nft = w._write_number_formats() + w._write_cell_xfs(nft, ft, {}, {}) + xml = get_xml(w._root) + ok_('applyFont="1"' in xml) + ok_('applyFillId="1"' not in xml) + ok_('applyBorder="1"' not in xml) + ok_('applyAlignment="1"' not in xml) + + def test_alignment(self): + self.worksheet.cell('A1').style.alignment.horizontal = 'center' + self.worksheet.cell('A1').style.alignment.vertical = 'center' + w = StyleWriter(self.workbook) + nft = w._write_number_formats() + w._write_cell_xfs(nft,{},{},{}) + xml = get_xml(w._root) + ok_('applyAlignment="1"' in xml) + ok_('horizontal="center"' in xml) + ok_('vertical="center"' in xml) + + +#def test_format_comparisions(): +# format1 = NumberFormat() +# format2 = NumberFormat() +# format3 = NumberFormat() +# format1.format_code = 'm/d/yyyy' +# format2.format_code = 'm/d/yyyy' +# format3.format_code = 'mm/dd/yyyy' +# assert not format1 < format2 +# assert format1 < format3 +# assert format1 == format2 +# assert format1 != format3 + + +def test_builtin_format(): + format = NumberFormat() + format.format_code = '0.00' + eq_(format.builtin_format_code(2), format._format_code) + + +def test_read_style(): + reference_file = os.path.join(DATADIR, 'reader', 'simple-styles.xml') + with open(reference_file, 'r') as handle: + content = handle.read() + style_table = read_style_table(content) + eq_(4, len(style_table)) + eq_(NumberFormat._BUILTIN_FORMATS[9], + style_table[1].number_format.format_code) + eq_('yyyy-mm-dd', style_table[2].number_format.format_code) + + +def test_read_cell_style(): + reference_file = os.path.join( + DATADIR, 'reader', 'empty-workbook-styles.xml') + with open(reference_file, 'r') as handle: + content = handle.read() + style_table = read_style_table(content) + eq_(2, len(style_table)) diff --git a/tablib/packages/openpyxl3/tests/test_theme.py b/tablib/packages/openpyxl3/tests/test_theme.py new file mode 100644 index 0000000..b0f6f00 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_theme.py @@ -0,0 +1,37 @@ +# file openpyxl/tests/test_theme.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +import os.path + +# package imports +from openpyxl.tests.helper import DATADIR, assert_equals_file_content +from openpyxl.writer.theme import write_theme + + +def test_write_theme(): + content = write_theme() + assert_equals_file_content( + os.path.join(DATADIR, 'writer', 'expected', 'theme1.xml'), content) diff --git a/tablib/packages/openpyxl3/tests/test_workbook.py b/tablib/packages/openpyxl3/tests/test_workbook.py new file mode 100644 index 0000000..20191d2 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_workbook.py @@ -0,0 +1,148 @@ +# file openpyxl/tests/test_workbook.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# 3rd party imports +from nose.tools import eq_, with_setup, raises +import os.path as osp + +# package imports +from openpyxl.workbook import Workbook +from openpyxl.reader.excel import load_workbook +from openpyxl.namedrange import NamedRange +from openpyxl.shared.exc import ReadOnlyWorkbookException +from openpyxl.tests.helper import TMPDIR, clean_tmpdir, make_tmpdir + +import datetime + +def test_get_active_sheet(): + wb = Workbook() + active_sheet = wb.get_active_sheet() + eq_(active_sheet, wb.worksheets[0]) + + +def test_create_sheet(): + wb = Workbook() + new_sheet = wb.create_sheet(0) + eq_(new_sheet, wb.worksheets[0]) + + +@raises(ReadOnlyWorkbookException) +def test_create_sheet_readonly(): + wb = Workbook() + wb._set_optimized_read() + wb.create_sheet() + + +def test_remove_sheet(): + wb = Workbook() + new_sheet = wb.create_sheet(0) + wb.remove_sheet(new_sheet) + assert new_sheet not in wb.worksheets + + +def test_get_sheet_by_name(): + wb = Workbook() + new_sheet = wb.create_sheet() + title = 'my sheet' + new_sheet.title = title + found_sheet = wb.get_sheet_by_name(title) + eq_(new_sheet, found_sheet) + + +def test_get_index(): + wb = Workbook() + new_sheet = wb.create_sheet(0) + sheet_index = wb.get_index(new_sheet) + eq_(sheet_index, 0) + + +def test_get_sheet_names(): + wb = Workbook() + names = ['Sheet', 'Sheet1', 'Sheet2', 'Sheet3', 'Sheet4', 'Sheet5'] + for count in range(5): + wb.create_sheet(0) + actual_names = wb.get_sheet_names() + eq_(sorted(actual_names), sorted(names)) + + +def test_get_named_ranges(): + wb = Workbook() + eq_(wb.get_named_ranges(), wb._named_ranges) + + +def test_add_named_range(): + wb = Workbook() + new_sheet = wb.create_sheet() + named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) + wb.add_named_range(named_range) + named_ranges_list = wb.get_named_ranges() + assert named_range in named_ranges_list + + +def test_get_named_range(): + wb = Workbook() + new_sheet = wb.create_sheet() + named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) + wb.add_named_range(named_range) + found_named_range = wb.get_named_range('test_nr') + eq_(named_range, found_named_range) + + +def test_remove_named_range(): + wb = Workbook() + new_sheet = wb.create_sheet() + named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) + wb.add_named_range(named_range) + wb.remove_named_range(named_range) + named_ranges_list = wb.get_named_ranges() + assert named_range not in named_ranges_list + +@with_setup(setup = make_tmpdir, teardown = clean_tmpdir) +def test_add_local_named_range(): + wb = Workbook() + new_sheet = wb.create_sheet() + named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) + named_range.local_only = True + wb.add_named_range(named_range) + dest_filename = osp.join(TMPDIR, 'local_named_range_book.xlsx') + wb.save(dest_filename) + + +@with_setup(setup = make_tmpdir, teardown = clean_tmpdir) +def test_write_regular_date(): + + today = datetime.datetime(2010, 1, 18, 14, 15, 20, 1600) + + book = Workbook() + sheet = book.get_active_sheet() + sheet.cell("A1").value = today + dest_filename = osp.join(TMPDIR, 'date_read_write_issue.xlsx') + book.save(dest_filename) + + test_book = load_workbook(dest_filename) + test_sheet = test_book.get_active_sheet() + + eq_(test_sheet.cell("A1").value, today) + diff --git a/tablib/packages/openpyxl3/tests/test_worksheet.py b/tablib/packages/openpyxl3/tests/test_worksheet.py new file mode 100644 index 0000000..1894f1d --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_worksheet.py @@ -0,0 +1,258 @@ +# file openpyxl/tests/test_worksheet.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# 3rd party imports +from nose.tools import eq_, raises, assert_raises + +# package imports +from openpyxl.workbook import Workbook +from openpyxl.worksheet import Worksheet, Relationship, flatten +from openpyxl.cell import Cell +from openpyxl.shared.exc import CellCoordinatesException, \ + SheetTitleException, InsufficientCoordinatesException, \ + NamedRangeException + + +class TestWorksheet(): + + @classmethod + def setup_class(cls): + cls.wb = Workbook() + + def test_new_worksheet(self): + ws = Worksheet(self.wb) + eq_(self.wb, ws._parent) + + def test_new_sheet_name(self): + self.wb.worksheets = [] + ws = Worksheet(self.wb, title = '') + eq_(repr(ws), '') + + def test_get_cell(self): + ws = Worksheet(self.wb) + cell = ws.cell('A1') + eq_(cell.get_coordinate(), 'A1') + + @raises(SheetTitleException) + def test_set_bad_title(self): + Worksheet(self.wb, 'X' * 50) + + def test_set_bad_title_character(self): + assert_raises(SheetTitleException, Worksheet, self.wb, '[') + assert_raises(SheetTitleException, Worksheet, self.wb, ']') + assert_raises(SheetTitleException, Worksheet, self.wb, '*') + assert_raises(SheetTitleException, Worksheet, self.wb, ':') + assert_raises(SheetTitleException, Worksheet, self.wb, '?') + assert_raises(SheetTitleException, Worksheet, self.wb, '/') + assert_raises(SheetTitleException, Worksheet, self.wb, '\\') + + def test_worksheet_dimension(self): + ws = Worksheet(self.wb) + eq_('A1:A1', ws.calculate_dimension()) + ws.cell('B12').value = 'AAA' + eq_('A1:B12', ws.calculate_dimension()) + + def test_worksheet_range(self): + ws = Worksheet(self.wb) + xlrange = ws.range('A1:C4') + assert isinstance(xlrange, tuple) + eq_(4, len(xlrange)) + eq_(3, len(xlrange[0])) + + def test_worksheet_named_range(self): + ws = Worksheet(self.wb) + self.wb.create_named_range('test_range', ws, 'C5') + xlrange = ws.range('test_range') + assert isinstance(xlrange, Cell) + eq_(5, xlrange.row) + + @raises(NamedRangeException) + def test_bad_named_range(self): + ws = Worksheet(self.wb) + ws.range('bad_range') + + @raises(NamedRangeException) + def test_named_range_wrong_sheet(self): + ws1 = Worksheet(self.wb) + ws2 = Worksheet(self.wb) + self.wb.create_named_range('wrong_sheet_range', ws1, 'C5') + ws2.range('wrong_sheet_range') + + def test_cell_offset(self): + ws = Worksheet(self.wb) + eq_('C17', ws.cell('B15').offset(2, 1).get_coordinate()) + + def test_range_offset(self): + ws = Worksheet(self.wb) + xlrange = ws.range('A1:C4', 1, 3) + assert isinstance(xlrange, tuple) + eq_(4, len(xlrange)) + eq_(3, len(xlrange[0])) + eq_('D2', xlrange[0][0].get_coordinate()) + + def test_cell_alternate_coordinates(self): + ws = Worksheet(self.wb) + cell = ws.cell(row = 8, column = 4) + eq_('E9', cell.get_coordinate()) + + @raises(InsufficientCoordinatesException) + def test_cell_insufficient_coordinates(self): + ws = Worksheet(self.wb) + cell = ws.cell(row = 8) + + def test_cell_range_name(self): + ws = Worksheet(self.wb) + self.wb.create_named_range('test_range_single', ws, 'B12') + assert_raises(CellCoordinatesException, ws.cell, 'test_range_single') + c_range_name = ws.range('test_range_single') + c_range_coord = ws.range('B12') + c_cell = ws.cell('B12') + eq_(c_range_coord, c_range_name) + eq_(c_range_coord, c_cell) + + def test_garbage_collect(self): + ws = Worksheet(self.wb) + ws.cell('A1').value = '' + ws.cell('B2').value = '0' + ws.cell('C4').value = 0 + ws.garbage_collect() + eq_(ws.get_cell_collection(), [ws.cell('B2'), ws.cell('C4')]) + + def test_hyperlink_relationships(self): + ws = Worksheet(self.wb) + eq_(len(ws.relationships), 0) + + ws.cell('A1').hyperlink = "http://test.com" + eq_(len(ws.relationships), 1) + eq_("rId1", ws.cell('A1').hyperlink_rel_id) + eq_("rId1", ws.relationships[0].id) + eq_("http://test.com", ws.relationships[0].target) + eq_("External", ws.relationships[0].target_mode) + + ws.cell('A2').hyperlink = "http://test2.com" + eq_(len(ws.relationships), 2) + eq_("rId2", ws.cell('A2').hyperlink_rel_id) + eq_("rId2", ws.relationships[1].id) + eq_("http://test2.com", ws.relationships[1].target) + eq_("External", ws.relationships[1].target_mode) + + @raises(ValueError) + def test_bad_relationship_type(self): + rel = Relationship('bad_type') + + def test_append_list(self): + ws = Worksheet(self.wb) + + ws.append(['This is A1', 'This is B1']) + + eq_('This is A1', ws.cell('A1').value) + eq_('This is B1', ws.cell('B1').value) + + def test_append_dict_letter(self): + ws = Worksheet(self.wb) + + ws.append({'A' : 'This is A1', 'C' : 'This is C1'}) + + eq_('This is A1', ws.cell('A1').value) + eq_('This is C1', ws.cell('C1').value) + + def test_append_dict_index(self): + ws = Worksheet(self.wb) + + ws.append({0 : 'This is A1', 2 : 'This is C1'}) + + eq_('This is A1', ws.cell('A1').value) + eq_('This is C1', ws.cell('C1').value) + + @raises(TypeError) + def test_bad_append(self): + ws = Worksheet(self.wb) + ws.append("test") + + def test_append_2d_list(self): + + ws = Worksheet(self.wb) + + ws.append(['This is A1', 'This is B1']) + ws.append(['This is A2', 'This is B2']) + + vals = ws.range('A1:B2') + + eq_((('This is A1', 'This is B1'), + ('This is A2', 'This is B2'),), flatten(vals)) + + def test_rows(self): + + ws = Worksheet(self.wb) + + ws.cell('A1').value = 'first' + ws.cell('C9').value = 'last' + + rows = ws.rows + + eq_(len(rows), 9) + + eq_(rows[0][0].value, 'first') + eq_(rows[-1][-1].value, 'last') + + def test_cols(self): + + ws = Worksheet(self.wb) + + ws.cell('A1').value = 'first' + ws.cell('C9').value = 'last' + + cols = ws.columns + + eq_(len(cols), 3) + + eq_(cols[0][0].value, 'first') + eq_(cols[-1][-1].value, 'last') + + def test_auto_filter(self): + ws = Worksheet(self.wb) + ws.auto_filter = ws.range('a1:f1') + assert ws.auto_filter == 'A1:F1' + + ws.auto_filter = '' + assert ws.auto_filter is None + + ws.auto_filter = 'c1:g9' + assert ws.auto_filter == 'C1:G9' + + def test_freeze(self): + ws = Worksheet(self.wb) + ws.freeze_panes = ws.cell('b2') + assert ws.freeze_panes == 'B2' + + ws.freeze_panes = '' + assert ws.freeze_panes is None + + ws.freeze_panes = 'c5' + assert ws.freeze_panes == 'C5' + + ws.freeze_panes = ws.cell('A1') + assert ws.freeze_panes is None + diff --git a/tablib/packages/openpyxl3/tests/test_write.py b/tablib/packages/openpyxl3/tests/test_write.py new file mode 100644 index 0000000..51e3c15 --- /dev/null +++ b/tablib/packages/openpyxl3/tests/test_write.py @@ -0,0 +1,203 @@ +# file openpyxl/tests/test_write.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +# Python stdlib imports +from __future__ import with_statement +from io import BytesIO as StringIO +import os.path + +# 3rd party imports +from nose.tools import eq_, with_setup, raises + +# package imports +from openpyxl.tests.helper import TMPDIR, DATADIR, \ + assert_equals_file_content, clean_tmpdir, make_tmpdir +from openpyxl.workbook import Workbook +from openpyxl.reader.excel import load_workbook +from openpyxl.writer.excel import save_workbook, save_virtual_workbook, \ + ExcelWriter +from openpyxl.writer.workbook import write_workbook, write_workbook_rels +from openpyxl.writer.worksheet import write_worksheet, write_worksheet_rels +from openpyxl.writer.strings import write_string_table +from openpyxl.writer.styles import StyleWriter + + +@with_setup(setup = make_tmpdir, teardown = clean_tmpdir) +def test_write_empty_workbook(): + wb = Workbook() + dest_filename = os.path.join(TMPDIR, 'empty_book.xlsx') + save_workbook(wb, dest_filename) + assert os.path.isfile(dest_filename) + + +def test_write_virtual_workbook(): + old_wb = Workbook() + saved_wb = save_virtual_workbook(old_wb) + new_wb = load_workbook(StringIO(saved_wb)) + assert new_wb + + +def test_write_workbook_rels(): + wb = Workbook() + content = write_workbook_rels(wb) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'workbook.xml.rels'), content) + + +def test_write_workbook(): + wb = Workbook() + content = write_workbook(wb) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'workbook.xml'), content) + + +def test_write_string_table(): + table = {'hello': 1, 'world': 2, 'nice': 3} + content = write_string_table(table) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sharedStrings.xml'), content) + + +def test_write_worksheet(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F42').value = 'hello' + content = write_worksheet(ws, {'hello': 0}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1.xml'), content) + + +def test_write_hidden_worksheet(): + wb = Workbook() + ws = wb.create_sheet() + ws.sheet_state = ws.SHEETSTATE_HIDDEN + ws.cell('F42').value = 'hello' + content = write_worksheet(ws, {'hello': 0}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1.xml'), content) + + +def test_write_formula(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F1').value = 10 + ws.cell('F2').value = 32 + ws.cell('F3').value = '=F1+F2' + content = write_worksheet(ws, {}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_formula.xml'), content) + + +def test_write_style(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F1').value = '13%' + style_id_by_hash = StyleWriter(wb).get_style_by_hash() + content = write_worksheet(ws, {}, style_id_by_hash) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_style.xml'), content) + + +def test_write_height(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F1').value = 10 + ws.row_dimensions[ws.cell('F1').row].height = 30 + content = write_worksheet(ws, {}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_height.xml'), content) + + +def test_write_hyperlink(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('A1').value = "test" + ws.cell('A1').hyperlink = "http://test.com" + content = write_worksheet(ws, {'test': 0}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_hyperlink.xml'), content) + + +def test_write_hyperlink_rels(): + wb = Workbook() + ws = wb.create_sheet() + eq_(0, len(ws.relationships)) + ws.cell('A1').value = "test" + ws.cell('A1').hyperlink = "http://test.com/" + eq_(1, len(ws.relationships)) + ws.cell('A2').value = "test" + ws.cell('A2').hyperlink = "http://test2.com/" + eq_(2, len(ws.relationships)) + content = write_worksheet_rels(ws, 1) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_hyperlink.xml.rels'), content) + + +def test_hyperlink_value(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('A1').hyperlink = "http://test.com" + eq_("http://test.com", ws.cell('A1').value) + ws.cell('A1').value = "test" + eq_("test", ws.cell('A1').value) + +def test_write_auto_filter(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F42').value = 'hello' + ws.auto_filter = 'A1:F1' + content = write_worksheet(ws, {'hello': 0}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_auto_filter.xml'), content) + +def test_freeze_panes_horiz(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F42').value = 'hello' + ws.freeze_panes = 'A4' + content = write_worksheet(ws, {'hello': 0}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_freeze_panes_horiz.xml'), content) + +def test_freeze_panes_vert(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F42').value = 'hello' + ws.freeze_panes = 'D1' + content = write_worksheet(ws, {'hello': 0}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_freeze_panes_vert.xml'), content) + pass + +def test_freeze_panes_both(): + wb = Workbook() + ws = wb.create_sheet() + ws.cell('F42').value = 'hello' + ws.freeze_panes = 'D4' + content = write_worksheet(ws, {'hello': 0}, {}) + assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ + 'sheet1_freeze_panes_both.xml'), content) + + diff --git a/tablib/packages/openpyxl3/workbook.py b/tablib/packages/openpyxl3/workbook.py new file mode 100644 index 0000000..bbb14b6 --- /dev/null +++ b/tablib/packages/openpyxl3/workbook.py @@ -0,0 +1,186 @@ +# file openpyxl/workbook.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Workbook is the top-level container for all document information.""" + +__docformat__ = "restructuredtext en" + +# Python stdlib imports +import datetime +import os + +# package imports +from .worksheet import Worksheet +from .writer.dump_worksheet import DumpWorksheet, save_dump +from .writer.strings import StringTableBuilder +from .namedrange import NamedRange +from .style import Style +from .writer.excel import save_workbook +from .shared.exc import ReadOnlyWorkbookException + + +class DocumentProperties(object): + """High-level properties of the document.""" + + def __init__(self): + self.creator = 'Unknown' + self.last_modified_by = self.creator + self.created = datetime.datetime.now() + self.modified = datetime.datetime.now() + self.title = 'Untitled' + self.subject = '' + self.description = '' + self.keywords = '' + self.category = '' + self.company = 'Microsoft Corporation' + + +class DocumentSecurity(object): + """Security information about the document.""" + + def __init__(self): + self.lock_revision = False + self.lock_structure = False + self.lock_windows = False + self.revision_password = '' + self.workbook_password = '' + + +class Workbook(object): + """Workbook is the container for all other parts of the document.""" + + def __init__(self, optimized_write = False): + self.worksheets = [] + self._active_sheet_index = 0 + self._named_ranges = [] + self.properties = DocumentProperties() + self.style = Style() + self.security = DocumentSecurity() + self.__optimized_write = optimized_write + self.__optimized_read = False + self.strings_table_builder = StringTableBuilder() + + if not optimized_write: + self.worksheets.append(Worksheet(self)) + + def _set_optimized_read(self): + self.__optimized_read = True + + def get_active_sheet(self): + """Returns the current active sheet.""" + return self.worksheets[self._active_sheet_index] + + def create_sheet(self, index = None): + """Create a worksheet (at an optional index). + + :param index: optional position at which the sheet will be inserted + :type index: int + + """ + + if self.__optimized_read: + raise ReadOnlyWorkbookException('Cannot create new sheet in a read-only workbook') + + if self.__optimized_write : + new_ws = DumpWorksheet(parent_workbook = self) + else: + new_ws = Worksheet(parent_workbook = self) + + self.add_sheet(worksheet = new_ws, index = index) + return new_ws + + def add_sheet(self, worksheet, index = None): + """Add an existing worksheet (at an optional index).""" + if index is None: + index = len(self.worksheets) + self.worksheets.insert(index, worksheet) + + def remove_sheet(self, worksheet): + """Remove a worksheet from this workbook.""" + self.worksheets.remove(worksheet) + + def get_sheet_by_name(self, name): + """Returns a worksheet by its name. + + Returns None if no worksheet has the name specified. + + :param name: the name of the worksheet to look for + :type name: string + + """ + requested_sheet = None + for sheet in self.worksheets: + if sheet.title == name: + requested_sheet = sheet + break + return requested_sheet + + def get_index(self, worksheet): + """Return the index of the worksheet.""" + return self.worksheets.index(worksheet) + + def get_sheet_names(self): + """Returns the list of the names of worksheets in the workbook. + + Names are returned in the worksheets order. + + :rtype: list of strings + + """ + return [s.title for s in self.worksheets] + + def create_named_range(self, name, worksheet, range): + """Create a new named_range on a worksheet""" + assert isinstance(worksheet, Worksheet) + named_range = NamedRange(name, [(worksheet, range)]) + self.add_named_range(named_range) + + def get_named_ranges(self): + """Return all named ranges""" + return self._named_ranges + + def add_named_range(self, named_range): + """Add an existing named_range to the list of named_ranges.""" + self._named_ranges.append(named_range) + + def get_named_range(self, name): + """Return the range specified by name.""" + requested_range = None + for named_range in self._named_ranges: + if named_range.name == name: + requested_range = named_range + break + return requested_range + + def remove_named_range(self, named_range): + """Remove a named_range from this workbook.""" + self._named_ranges.remove(named_range) + + def save(self, filename): + """ shortcut """ + if self.__optimized_write: + save_dump(self, filename) + else: + save_workbook(self, filename) diff --git a/tablib/packages/openpyxl3/worksheet.py b/tablib/packages/openpyxl3/worksheet.py new file mode 100644 index 0000000..2eb6499 --- /dev/null +++ b/tablib/packages/openpyxl3/worksheet.py @@ -0,0 +1,534 @@ +# file openpyxl/worksheet.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Worksheet is the 2nd-level container in Excel.""" + +# Python stdlib imports +import re + +# package imports +from . import cell +from .cell import coordinate_from_string, \ + column_index_from_string, get_column_letter +from .shared.exc import SheetTitleException, \ + InsufficientCoordinatesException, CellCoordinatesException, \ + NamedRangeException +from .shared.password_hasher import hash_password +from .style import Style, DEFAULTS as DEFAULTS_STYLE +from .drawing import Drawing + +_DEFAULTS_STYLE_HASH = hash(DEFAULTS_STYLE) + +def flatten(results): + + rows = [] + + for row in results: + + cells = [] + + for cell in row: + + cells.append(cell.value) + + rows.append(tuple(cells)) + + return tuple(rows) + + +class Relationship(object): + """Represents many kinds of relationships.""" + # TODO: Use this object for workbook relationships as well as + # worksheet relationships + TYPES = { + 'hyperlink': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', + 'drawing':'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing', + #'worksheet': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet', + #'sharedStrings': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings', + #'styles': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles', + #'theme': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme', + } + + def __init__(self, rel_type): + if rel_type not in self.TYPES: + raise ValueError("Invalid relationship type %s" % rel_type) + self.type = self.TYPES[rel_type] + self.target = "" + self.target_mode = "" + self.id = "" + + +class PageSetup(object): + """Information about page layout for this sheet""" + pass + + +class HeaderFooter(object): + """Information about the header/footer for this sheet.""" + pass + + +class SheetView(object): + """Information about the visible portions of this sheet.""" + pass + + +class RowDimension(object): + """Information about the display properties of a row.""" + __slots__ = ('row_index', + 'height', + 'visible', + 'outline_level', + 'collapsed', + 'style_index',) + + def __init__(self, index = 0): + self.row_index = index + self.height = -1 + self.visible = True + self.outline_level = 0 + self.collapsed = False + self.style_index = None + + +class ColumnDimension(object): + """Information about the display properties of a column.""" + __slots__ = ('column_index', + 'width', + 'auto_size', + 'visible', + 'outline_level', + 'collapsed', + 'style_index',) + + def __init__(self, index = 'A'): + self.column_index = index + self.width = -1 + self.auto_size = False + self.visible = True + self.outline_level = 0 + self.collapsed = False + self.style_index = 0 + + +class PageMargins(object): + """Information about page margins for view/print layouts.""" + + def __init__(self): + self.left = self.right = 0.7 + self.top = self.bottom = 0.75 + self.header = self.footer = 0.3 + + +class SheetProtection(object): + """Information about protection of various aspects of a sheet.""" + + def __init__(self): + self.sheet = False + self.objects = False + self.scenarios = False + self.format_cells = False + self.format_columns = False + self.format_rows = False + self.insert_columns = False + self.insert_rows = False + self.insert_hyperlinks = False + self.delete_columns = False + self.delete_rows = False + self.select_locked_cells = False + self.sort = False + self.auto_filter = False + self.pivot_tables = False + self.select_unlocked_cells = False + self._password = '' + + def set_password(self, value = '', already_hashed = False): + """Set a password on this sheet.""" + if not already_hashed: + value = hash_password(value) + self._password = value + + def _set_raw_password(self, value): + """Set a password directly, forcing a hash step.""" + self.set_password(value, already_hashed = False) + + def _get_raw_password(self): + """Return the password value, regardless of hash.""" + return self._password + + password = property(_get_raw_password, _set_raw_password, + 'get/set the password (if already hashed, ' + 'use set_password() instead)') + + +class Worksheet(object): + """Represents a worksheet. + + Do not create worksheets yourself, + use :func:`openpyxl.workbook.Workbook.create_sheet` instead + + """ + BREAK_NONE = 0 + BREAK_ROW = 1 + BREAK_COLUMN = 2 + + SHEETSTATE_VISIBLE = 'visible' + SHEETSTATE_HIDDEN = 'hidden' + SHEETSTATE_VERYHIDDEN = 'veryHidden' + + def __init__(self, parent_workbook, title = 'Sheet'): + self._parent = parent_workbook + self._title = '' + if not title: + self.title = 'Sheet%d' % (1 + len(self._parent.worksheets)) + else: + self.title = title + self.row_dimensions = {} + self.column_dimensions = {} + self._cells = {} + self._styles = {} + self._charts = [] + self.relationships = [] + self.selected_cell = 'A1' + self.active_cell = 'A1' + self.sheet_state = self.SHEETSTATE_VISIBLE + self.page_setup = PageSetup() + self.page_margins = PageMargins() + self.header_footer = HeaderFooter() + self.sheet_view = SheetView() + self.protection = SheetProtection() + self.show_gridlines = True + self.print_gridlines = False + self.show_summary_below = True + self.show_summary_right = True + self.default_row_dimension = RowDimension() + self.default_column_dimension = ColumnDimension() + self._auto_filter = None + self._freeze_panes = None + + def __repr__(self): + return '' % self.title + + def garbage_collect(self): + """Delete cells that are not storing a value.""" + delete_list = [coordinate for coordinate, cell in \ + self._cells.items() if (cell.value in ('', None) and \ + hash(cell.style) == _DEFAULTS_STYLE_HASH)] + for coordinate in delete_list: + del self._cells[coordinate] + + def get_cell_collection(self): + """Return an unordered list of the cells in this worksheet.""" + return self._cells.values() + + def _set_title(self, value): + """Set a sheet title, ensuring it is valid.""" + bad_title_char_re = re.compile(r'[\\*?:/\[\]]') + if bad_title_char_re.search(value): + msg = 'Invalid character found in sheet title' + raise SheetTitleException(msg) + + # check if sheet_name already exists + # do this *before* length check + if self._parent.get_sheet_by_name(value): + # use name, but append with lowest possible integer + i = 1 + while self._parent.get_sheet_by_name('%s%d' % (value, i)): + i += 1 + value = '%s%d' % (value, i) + if len(value) > 31: + msg = 'Maximum 31 characters allowed in sheet title' + raise SheetTitleException(msg) + self._title = value + + def _get_title(self): + """Return the title for this sheet.""" + return self._title + + title = property(_get_title, _set_title, doc = + 'Get or set the title of the worksheet. ' + 'Limited to 31 characters, no special characters.') + + def _set_auto_filter(self, range): + # Normalize range to a str or None + if not range: + range = None + elif isinstance(range, str): + range = range.upper() + else: # Assume a range + range = range[0][0].address + ':' + range[-1][-1].address + self._auto_filter = range + + def _get_auto_filter(self): + return self._auto_filter + + auto_filter = property(_get_auto_filter, _set_auto_filter, doc = + 'get or set auto filtering on columns') + def _set_freeze_panes(self, topLeftCell): + if not topLeftCell: + topLeftCell = None + elif isinstance(topLeftCell, str): + topLeftCell = topLeftCell.upper() + else: # Assume a cell + topLeftCell = topLeftCell.address + if topLeftCell == 'A1': + topLeftCell = None + self._freeze_panes = topLeftCell + + def _get_freeze_panes(self): + return self._freeze_panes + + freeze_panes = property(_get_freeze_panes,_set_freeze_panes, doc = + "Get or set frozen panes") + + def cell(self, coordinate = None, row = None, column = None): + """Returns a cell object based on the given coordinates. + + Usage: cell(coodinate='A15') **or** cell(row=15, column=1) + + If `coordinates` are not given, then row *and* column must be given. + + Cells are kept in a dictionary which is empty at the worksheet + creation. Calling `cell` creates the cell in memory when they + are first accessed, to reduce memory usage. + + :param coordinate: coordinates of the cell (e.g. 'B12') + :type coordinate: string + + :param row: row index of the cell (e.g. 4) + :type row: int + + :param column: column index of the cell (e.g. 3) + :type column: int + + :raise: InsufficientCoordinatesException when coordinate or (row and column) are not given + + :rtype: :class:`openpyxl.cell.Cell` + + """ + if not coordinate: + if (row is None or column is None): + msg = "You have to provide a value either for " \ + "'coordinate' or for 'row' *and* 'column'" + raise InsufficientCoordinatesException(msg) + else: + coordinate = '%s%s' % (get_column_letter(column + 1), row + 1) + else: + coordinate = coordinate.replace('$', '') + + return self._get_cell(coordinate) + + def _get_cell(self, coordinate): + + if not coordinate in self._cells: + column, row = coordinate_from_string(coordinate) + new_cell = cell.Cell(self, column, row) + self._cells[coordinate] = new_cell + if column not in self.column_dimensions: + self.column_dimensions[column] = ColumnDimension(column) + if row not in self.row_dimensions: + self.row_dimensions[row] = RowDimension(row) + return self._cells[coordinate] + + def get_highest_row(self): + """Returns the maximum row index containing data + + :rtype: int + """ + if self.row_dimensions: + return max(self.row_dimensions.keys()) + else: + return 1 + + def get_highest_column(self): + """Get the largest value for column currently stored. + + :rtype: int + """ + if self.column_dimensions: + return max([column_index_from_string(column_index) + for column_index in self.column_dimensions]) + else: + return 1 + + def calculate_dimension(self): + """Return the minimum bounding range for all cells containing data.""" + return 'A1:%s%d' % (get_column_letter(self.get_highest_column()), + self.get_highest_row()) + + def range(self, range_string, row = 0, column = 0): + """Returns a 2D array of cells, with optional row and column offsets. + + :param range_string: cell range string or `named range` name + :type range_string: string + + :param row: number of rows to offset + :type row: int + + :param column: number of columns to offset + :type column: int + + :rtype: tuples of tuples of :class:`openpyxl.cell.Cell` + + """ + if ':' in range_string: + # R1C1 range + result = [] + min_range, max_range = range_string.split(':') + min_col, min_row = coordinate_from_string(min_range) + max_col, max_row = coordinate_from_string(max_range) + if column: + min_col = get_column_letter( + column_index_from_string(min_col) + column) + max_col = get_column_letter( + column_index_from_string(max_col) + column) + min_col = column_index_from_string(min_col) + max_col = column_index_from_string(max_col) + cache_cols = {} + for col in range(min_col, max_col + 1): + cache_cols[col] = get_column_letter(col) + rows = range(min_row + row, max_row + row + 1) + cols = range(min_col, max_col + 1) + for row in rows: + new_row = [] + for col in cols: + new_row.append(self.cell('%s%s' % (cache_cols[col], row))) + result.append(tuple(new_row)) + return tuple(result) + else: + try: + return self.cell(coordinate = range_string, row = row, + column = column) + except CellCoordinatesException: + pass + + # named range + named_range = self._parent.get_named_range(range_string) + if named_range is None: + msg = '%s is not a valid range name' % range_string + raise NamedRangeException(msg) + + result = [] + for destination in named_range.destinations: + + worksheet, cells_range = destination + + if worksheet is not self: + msg = 'Range %s is not defined on worksheet %s' % \ + (cells_range, self.title) + raise NamedRangeException(msg) + + content = self.range(cells_range) + + if isinstance(content, tuple): + for cells in content: + result.extend(cells) + else: + result.append(content) + + if len(result) == 1: + return result[0] + else: + return tuple(result) + + def get_style(self, coordinate): + """Return the style object for the specified cell.""" + if not coordinate in self._styles: + self._styles[coordinate] = Style() + return self._styles[coordinate] + + def create_relationship(self, rel_type): + """Add a relationship for this sheet.""" + rel = Relationship(rel_type) + self.relationships.append(rel) + rel_id = self.relationships.index(rel) + rel.id = 'rId' + str(rel_id + 1) + return self.relationships[rel_id] + + def add_chart(self, chart): + """ Add a chart to the sheet """ + + chart._sheet = self + self._charts.append(chart) + + def append(self, list_or_dict): + """Appends a group of values at the bottom of the current sheet. + + * If it's a list: all values are added in order, starting from the first column + * If it's a dict: values are assigned to the columns indicated by the keys (numbers or letters) + + :param list_or_dict: list or dict containing values to append + :type list_or_dict: list/tuple or dict + + Usage: + + * append(['This is A1', 'This is B1', 'This is C1']) + * **or** append({'A' : 'This is A1', 'C' : 'This is C1'}) + * **or** append({0 : 'This is A1', 2 : 'This is C1'}) + + :raise: TypeError when list_or_dict is neither a list/tuple nor a dict + + """ + + row_idx = len(self.row_dimensions) + + if isinstance(list_or_dict, (list, tuple)): + + for col_idx, content in enumerate(list_or_dict): + + self.cell(row = row_idx, column = col_idx).value = content + + elif isinstance(list_or_dict, dict): + + for col_idx, content in list_or_dict.items(): + + if isinstance(col_idx, str): + col_idx = column_index_from_string(col_idx) - 1 + + self.cell(row = row_idx, column = col_idx).value = content + + else: + raise TypeError('list_or_dict must be a list or a dict') + + @property + def rows(self): + + return self.range(self.calculate_dimension()) + + @property + def columns(self): + + max_row = self.get_highest_row() + + cols = [] + + for col_idx in range(self.get_highest_column()): + col = get_column_letter(col_idx+1) + res = self.range('%s1:%s%d' % (col, col, max_row)) + cols.append(tuple([x[0] for x in res])) + + + return tuple(cols) + diff --git a/tablib/packages/openpyxl3/writer/__init__.py b/tablib/packages/openpyxl3/writer/__init__.py new file mode 100644 index 0000000..9eb0a21 --- /dev/null +++ b/tablib/packages/openpyxl3/writer/__init__.py @@ -0,0 +1,34 @@ +# file openpyxl/writer/__init__.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Imports for the openpyxl.writer namespace.""" + +# package imports +from . import excel +from . import strings +from . import styles +from . import theme +from . import workbook +from . import worksheet diff --git a/tablib/packages/openpyxl3/writer/charts.py b/tablib/packages/openpyxl3/writer/charts.py new file mode 100644 index 0000000..c0dfadb --- /dev/null +++ b/tablib/packages/openpyxl3/writer/charts.py @@ -0,0 +1,261 @@ +# coding=UTF-8 +''' +Copyright (c) 2010 openpyxl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license: http://www.opensource.org/licenses/mit-license.php +@author: Eric Gazoni +''' + +from ..shared.xmltools import Element, SubElement, get_document_content +from ..chart import Chart, ErrorBar + +class ChartWriter(object): + + def __init__(self, chart): + self.chart = chart + + def write(self): + """ write a chart """ + + root = Element('c:chartSpace', + {'xmlns:c':"http://schemas.openxmlformats.org/drawingml/2006/chart", + 'xmlns:a':"http://schemas.openxmlformats.org/drawingml/2006/main", + 'xmlns:r':"http://schemas.openxmlformats.org/officeDocument/2006/relationships"}) + + SubElement(root, 'c:lang', {'val':self.chart.lang}) + self._write_chart(root) + self._write_print_settings(root) + self._write_shapes(root) + + return get_document_content(root) + + def _write_chart(self, root): + + chart = self.chart + + ch = SubElement(root, 'c:chart') + self._write_title(ch) + plot_area = SubElement(ch, 'c:plotArea') + layout = SubElement(plot_area, 'c:layout') + mlayout = SubElement(layout, 'c:manualLayout') + SubElement(mlayout, 'c:layoutTarget', {'val':'inner'}) + SubElement(mlayout, 'c:xMode', {'val':'edge'}) + SubElement(mlayout, 'c:yMode', {'val':'edge'}) + SubElement(mlayout, 'c:x', {'val':str(chart._get_margin_left())}) + SubElement(mlayout, 'c:y', {'val':str(chart._get_margin_top())}) + SubElement(mlayout, 'c:w', {'val':str(chart.width)}) + SubElement(mlayout, 'c:h', {'val':str(chart.height)}) + + if chart.type == Chart.SCATTER_CHART: + subchart = SubElement(plot_area, 'c:scatterChart') + SubElement(subchart, 'c:scatterStyle', {'val':str('lineMarker')}) + else: + if chart.type == Chart.BAR_CHART: + subchart = SubElement(plot_area, 'c:barChart') + SubElement(subchart, 'c:barDir', {'val':'col'}) + else: + subchart = SubElement(plot_area, 'c:lineChart') + + SubElement(subchart, 'c:grouping', {'val':chart.grouping}) + + self._write_series(subchart) + + SubElement(subchart, 'c:marker', {'val':'1'}) + SubElement(subchart, 'c:axId', {'val':str(chart.x_axis.id)}) + SubElement(subchart, 'c:axId', {'val':str(chart.y_axis.id)}) + + if chart.type == Chart.SCATTER_CHART: + self._write_axis(plot_area, chart.x_axis, 'c:valAx') + else: + self._write_axis(plot_area, chart.x_axis, 'c:catAx') + self._write_axis(plot_area, chart.y_axis, 'c:valAx') + + self._write_legend(ch) + + SubElement(ch, 'c:plotVisOnly', {'val':'1'}) + + def _write_title(self, chart): + if self.chart.title != '': + title = SubElement(chart, 'c:title') + tx = SubElement(title, 'c:tx') + rich = SubElement(tx, 'c:rich') + SubElement(rich, 'a:bodyPr') + SubElement(rich, 'a:lstStyle') + p = SubElement(rich, 'a:p') + pPr = SubElement(p, 'a:pPr') + SubElement(pPr, 'a:defRPr') + r = SubElement(p, 'a:r') + SubElement(r, 'a:rPr', {'lang':self.chart.lang}) + t = SubElement(r, 'a:t').text = self.chart.title + SubElement(title, 'c:layout') + + def _write_axis(self, plot_area, axis, label): + + ax = SubElement(plot_area, label) + SubElement(ax, 'c:axId', {'val':str(axis.id)}) + + scaling = SubElement(ax, 'c:scaling') + SubElement(scaling, 'c:orientation', {'val':axis.orientation}) + if label == 'c:valAx': + SubElement(scaling, 'c:max', {'val':str(axis.max)}) + SubElement(scaling, 'c:min', {'val':str(axis.min)}) + + SubElement(ax, 'c:axPos', {'val':axis.position}) + if label == 'c:valAx': + SubElement(ax, 'c:majorGridlines') + SubElement(ax, 'c:numFmt', {'formatCode':"General", 'sourceLinked':'1'}) + SubElement(ax, 'c:tickLblPos', {'val':axis.tick_label_position}) + SubElement(ax, 'c:crossAx', {'val':str(axis.cross)}) + SubElement(ax, 'c:crosses', {'val':axis.crosses}) + if axis.auto: + SubElement(ax, 'c:auto', {'val':'1'}) + if axis.label_align: + SubElement(ax, 'c:lblAlgn', {'val':axis.label_align}) + if axis.label_offset: + SubElement(ax, 'c:lblOffset', {'val':str(axis.label_offset)}) + if label == 'c:valAx': + if self.chart.type == Chart.SCATTER_CHART: + SubElement(ax, 'c:crossBetween', {'val':'midCat'}) + else: + SubElement(ax, 'c:crossBetween', {'val':'between'}) + SubElement(ax, 'c:majorUnit', {'val':str(axis.unit)}) + + def _write_series(self, subchart): + + for i, serie in enumerate(self.chart._series): + ser = SubElement(subchart, 'c:ser') + SubElement(ser, 'c:idx', {'val':str(i)}) + SubElement(ser, 'c:order', {'val':str(i)}) + + if serie.legend: + tx = SubElement(ser, 'c:tx') + self._write_serial(tx, serie.legend) + + if serie.color: + sppr = SubElement(ser, 'c:spPr') + if self.chart.type == Chart.BAR_CHART: + # fill color + fillc = SubElement(sppr, 'a:solidFill') + SubElement(fillc, 'a:srgbClr', {'val':serie.color}) + # edge color + ln = SubElement(sppr, 'a:ln') + fill = SubElement(ln, 'a:solidFill') + SubElement(fill, 'a:srgbClr', {'val':serie.color}) + + if serie.error_bar: + self._write_error_bar(ser, serie) + + marker = SubElement(ser, 'c:marker') + SubElement(marker, 'c:symbol', {'val':serie.marker}) + + if serie.labels: + cat = SubElement(ser, 'c:cat') + self._write_serial(cat, serie.labels) + + if self.chart.type == Chart.SCATTER_CHART: + if serie.xvalues: + xval = SubElement(ser, 'c:xVal') + self._write_serial(xval, serie.xvalues) + + yval = SubElement(ser, 'c:yVal') + self._write_serial(yval, serie.values) + else: + val = SubElement(ser, 'c:val') + self._write_serial(val, serie.values) + + def _write_serial(self, node, serie, literal=False): + + cache = serie._get_cache() + if isinstance(cache[0], str): + typ = 'str' + else: + typ = 'num' + + if not literal: + if typ == 'num': + ref = SubElement(node, 'c:numRef') + else: + ref = SubElement(node, 'c:strRef') + SubElement(ref, 'c:f').text = serie._get_ref() + if typ == 'num': + data = SubElement(ref, 'c:numCache') + else: + data = SubElement(ref, 'c:strCache') + else: + data = SubElement(node, 'c:numLit') + + if typ == 'num': + SubElement(data, 'c:formatCode').text = 'General' + if literal: + values = (1,) + else: + values = cache + + SubElement(data, 'c:ptCount', {'val':str(len(values))}) + for j, val in enumerate(values): + point = SubElement(data, 'c:pt', {'idx':str(j)}) + SubElement(point, 'c:v').text = str(val) + + def _write_error_bar(self, node, serie): + + flag = {ErrorBar.PLUS_MINUS:'both', + ErrorBar.PLUS:'plus', + ErrorBar.MINUS:'minus'} + + eb = SubElement(node, 'c:errBars') + SubElement(eb, 'c:errBarType', {'val':flag[serie.error_bar.type]}) + SubElement(eb, 'c:errValType', {'val':'cust'}) + + plus = SubElement(eb, 'c:plus') + self._write_serial(plus, serie.error_bar.values, + literal=(serie.error_bar.type==ErrorBar.MINUS)) + + minus = SubElement(eb, 'c:minus') + self._write_serial(minus, serie.error_bar.values, + literal=(serie.error_bar.type==ErrorBar.PLUS)) + + def _write_legend(self, chart): + + legend = SubElement(chart, 'c:legend') + SubElement(legend, 'c:legendPos', {'val':self.chart.legend.position}) + SubElement(legend, 'c:layout') + + def _write_print_settings(self, root): + + settings = SubElement(root, 'c:printSettings') + SubElement(settings, 'c:headerFooter') + margins = dict([(k, str(v)) for (k,v) in self.chart.print_margins.items()]) + SubElement(settings, 'c:pageMargins', margins) + SubElement(settings, 'c:pageSetup') + + def _write_shapes(self, root): + + if self.chart._shapes: + SubElement(root, 'c:userShapes', {'r:id':'rId1'}) + + def write_rels(self, drawing_id): + + root = Element('Relationships', {'xmlns' : 'http://schemas.openxmlformats.org/package/2006/relationships'}) + attrs = {'Id' : 'rId1', + 'Type' : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUserShapes', + 'Target' : '../drawings/drawing%s.xml' % drawing_id } + SubElement(root, 'Relationship', attrs) + return get_document_content(root) diff --git a/tablib/packages/openpyxl3/writer/drawings.py b/tablib/packages/openpyxl3/writer/drawings.py new file mode 100644 index 0000000..8a6cce2 --- /dev/null +++ b/tablib/packages/openpyxl3/writer/drawings.py @@ -0,0 +1,192 @@ +# coding=UTF-8 +''' +Copyright (c) 2010 openpyxl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license: http://www.opensource.org/licenses/mit-license.php +@author: Eric Gazoni +''' + +from ..shared.xmltools import Element, SubElement, get_document_content + + +class DrawingWriter(object): + """ one main drawing file per sheet """ + + def __init__(self, sheet): + self._sheet = sheet + + def write(self): + """ write drawings for one sheet in one file """ + + root = Element('xdr:wsDr', + {'xmlns:xdr' : "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing", + 'xmlns:a' : "http://schemas.openxmlformats.org/drawingml/2006/main"}) + + for i, chart in enumerate(self._sheet._charts): + + drawing = chart.drawing + +# anchor = SubElement(root, 'xdr:twoCellAnchor') +# (start_row, start_col), (end_row, end_col) = drawing.coordinates +# # anchor coordinates +# _from = SubElement(anchor, 'xdr:from') +# x = SubElement(_from, 'xdr:col').text = str(start_col) +# x = SubElement(_from, 'xdr:colOff').text = '0' +# x = SubElement(_from, 'xdr:row').text = str(start_row) +# x = SubElement(_from, 'xdr:rowOff').text = '0' + +# _to = SubElement(anchor, 'xdr:to') +# x = SubElement(_to, 'xdr:col').text = str(end_col) +# x = SubElement(_to, 'xdr:colOff').text = '0' +# x = SubElement(_to, 'xdr:row').text = str(end_row) +# x = SubElement(_to, 'xdr:rowOff').text = '0' + + # we only support absolute anchor atm (TODO: oneCellAnchor, twoCellAnchor + x, y, w, h = drawing.get_emu_dimensions() + anchor = SubElement(root, 'xdr:absoluteAnchor') + SubElement(anchor, 'xdr:pos', {'x':str(x), 'y':str(y)}) + SubElement(anchor, 'xdr:ext', {'cx':str(w), 'cy':str(h)}) + + # graph frame + frame = SubElement(anchor, 'xdr:graphicFrame', {'macro':''}) + + name = SubElement(frame, 'xdr:nvGraphicFramePr') + SubElement(name, 'xdr:cNvPr', {'id':'%s' % i, 'name':'Graphique %s' % i}) + SubElement(name, 'xdr:cNvGraphicFramePr') + + frm = SubElement(frame, 'xdr:xfrm') + # no transformation + SubElement(frm, 'a:off', {'x':'0', 'y':'0'}) + SubElement(frm, 'a:ext', {'cx':'0', 'cy':'0'}) + + graph = SubElement(frame, 'a:graphic') + data = SubElement(graph, 'a:graphicData', + {'uri':'http://schemas.openxmlformats.org/drawingml/2006/chart'}) + SubElement(data, 'c:chart', + { 'xmlns:c':'http://schemas.openxmlformats.org/drawingml/2006/chart', + 'xmlns:r':'http://schemas.openxmlformats.org/officeDocument/2006/relationships', + 'r:id':'rId%s' % (i + 1)}) + + SubElement(anchor, 'xdr:clientData') + + return get_document_content(root) + + def write_rels(self, chart_id): + + root = Element('Relationships', + {'xmlns' : 'http://schemas.openxmlformats.org/package/2006/relationships'}) + for i, chart in enumerate(self._sheet._charts): + attrs = {'Id' : 'rId%s' % (i + 1), + 'Type' : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart', + 'Target' : '../charts/chart%s.xml' % (chart_id + i) } + SubElement(root, 'Relationship', attrs) + return get_document_content(root) + +class ShapeWriter(object): + """ one file per shape """ + + schema = "http://schemas.openxmlformats.org/drawingml/2006/main" + + def __init__(self, shapes): + + self._shapes = shapes + + def write(self, shape_id): + + root = Element('c:userShapes', {'xmlns:c' : 'http://schemas.openxmlformats.org/drawingml/2006/chart'}) + + for shape in self._shapes: + anchor = SubElement(root, 'cdr:relSizeAnchor', + {'xmlns:cdr' : "http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"}) + + xstart, ystart, xend, yend = shape.get_coordinates() + + _from = SubElement(anchor, 'cdr:from') + SubElement(_from, 'cdr:x').text = str(xstart) + SubElement(_from, 'cdr:y').text = str(ystart) + + _to = SubElement(anchor, 'cdr:to') + SubElement(_to, 'cdr:x').text = str(xend) + SubElement(_to, 'cdr:y').text = str(yend) + + sp = SubElement(anchor, 'cdr:sp', {'macro':'', 'textlink':''}) + nvspr = SubElement(sp, 'cdr:nvSpPr') + SubElement(nvspr, 'cdr:cNvPr', {'id':str(shape_id), 'name':'shape %s' % shape_id}) + SubElement(nvspr, 'cdr:cNvSpPr') + + sppr = SubElement(sp, 'cdr:spPr') + frm = SubElement(sppr, 'a:xfrm', {'xmlns:a':self.schema}) + # no transformation + SubElement(frm, 'a:off', {'x':'0', 'y':'0'}) + SubElement(frm, 'a:ext', {'cx':'0', 'cy':'0'}) + + prstgeom = SubElement(sppr, 'a:prstGeom', {'xmlns:a':self.schema, 'prst':str(shape.style)}) + SubElement(prstgeom, 'a:avLst') + + fill = SubElement(sppr, 'a:solidFill', {'xmlns:a':self.schema}) + SubElement(fill, 'a:srgbClr', {'val':shape.color}) + + border = SubElement(sppr, 'a:ln', {'xmlns:a':self.schema, 'w':str(shape._border_width)}) + sf = SubElement(border, 'a:solidFill') + SubElement(sf, 'a:srgbClr', {'val':shape.border_color}) + + self._write_style(sp) + self._write_text(sp, shape) + + shape_id += 1 + + return get_document_content(root) + + def _write_text(self, node, shape): + """ write text in the shape """ + + tx_body = SubElement(node, 'cdr:txBody') + SubElement(tx_body, 'a:bodyPr', {'xmlns:a':self.schema, 'vertOverflow':'clip'}) + SubElement(tx_body, 'a:lstStyle', + {'xmlns:a':self.schema}) + p = SubElement(tx_body, 'a:p', {'xmlns:a':self.schema}) + if shape.text: + r = SubElement(p, 'a:r') + rpr = SubElement(r, 'a:rPr', {'lang':'en-US'}) + fill = SubElement(rpr, 'a:solidFill') + SubElement(fill, 'a:srgbClr', {'val':shape.text_color}) + + SubElement(r, 'a:t').text = shape.text + else: + SubElement(p, 'a:endParaRPr', {'lang':'en-US'}) + + def _write_style(self, node): + """ write style theme """ + + style = SubElement(node, 'cdr:style') + + ln_ref = SubElement(style, 'a:lnRef', {'xmlns:a':self.schema, 'idx':'2'}) + scheme_clr = SubElement(ln_ref, 'a:schemeClr', {'val':'accent1'}) + SubElement(scheme_clr, 'a:shade', {'val':'50000'}) + + fill_ref = SubElement(style, 'a:fillRef', {'xmlns:a':self.schema, 'idx':'1'}) + SubElement(fill_ref, 'a:schemeClr', {'val':'accent1'}) + + effect_ref = SubElement(style, 'a:effectRef', {'xmlns:a':self.schema, 'idx':'0'}) + SubElement(effect_ref, 'a:schemeClr', {'val':'accent1'}) + + font_ref = SubElement(style, 'a:fontRef', {'xmlns:a':self.schema, 'idx':'minor'}) + SubElement(font_ref, 'a:schemeClr', {'val':'lt1'}) diff --git a/tablib/packages/openpyxl3/writer/dump_worksheet.py b/tablib/packages/openpyxl3/writer/dump_worksheet.py new file mode 100644 index 0000000..521cae0 --- /dev/null +++ b/tablib/packages/openpyxl3/writer/dump_worksheet.py @@ -0,0 +1,256 @@ +# file openpyxl/writer/straight_worksheet.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Write worksheets to xml representations in an optimized way""" + +import datetime +import os + +from ..cell import column_index_from_string, get_column_letter, Cell +from ..worksheet import Worksheet +from ..shared.xmltools import XMLGenerator, get_document_content, \ + start_tag, end_tag, tag +from ..shared.date_time import SharedDate +from ..shared.ooxml import MAX_COLUMN, MAX_ROW +from tempfile import NamedTemporaryFile +from ..writer.excel import ExcelWriter +from ..writer.strings import write_string_table +from ..writer.styles import StyleWriter +from ..style import Style, NumberFormat + +from ..shared.ooxml import ARC_SHARED_STRINGS, ARC_CONTENT_TYPES, \ + ARC_ROOT_RELS, ARC_WORKBOOK_RELS, ARC_APP, ARC_CORE, ARC_THEME, \ + ARC_STYLE, ARC_WORKBOOK, \ + PACKAGE_WORKSHEETS, PACKAGE_DRAWINGS, PACKAGE_CHARTS + +STYLES = {'datetime' : {'type':Cell.TYPE_NUMERIC, + 'style':'1'}, + 'string':{'type':Cell.TYPE_STRING, + 'style':'0'}, + 'numeric':{'type':Cell.TYPE_NUMERIC, + 'style':'0'}, + 'formula':{'type':Cell.TYPE_FORMULA, + 'style':'0'}, + 'boolean':{'type':Cell.TYPE_BOOL, + 'style':'0'}, + } + +DATETIME_STYLE = Style() +DATETIME_STYLE.number_format.format_code = NumberFormat.FORMAT_DATE_YYYYMMDD2 +BOUNDING_BOX_PLACEHOLDER = 'A1:%s%d' % (get_column_letter(MAX_COLUMN), MAX_ROW) + +class DumpWorksheet(Worksheet): + + """ + .. warning:: + + You shouldn't initialize this yourself, use :class:`openpyxl.workbook.Workbook` constructor instead, + with `optimized_write = True`. + """ + + def __init__(self, parent_workbook): + + Worksheet.__init__(self, parent_workbook) + + self._max_col = 0 + self._max_row = 0 + self._parent = parent_workbook + self._fileobj_header = NamedTemporaryFile(mode='r+', prefix='openpyxl.', suffix='.header', delete=False) + self._fileobj_content = NamedTemporaryFile(mode='r+', prefix='openpyxl.', suffix='.content', delete=False) + self._fileobj = NamedTemporaryFile(mode='w', prefix='openpyxl.', delete=False) + self.doc = XMLGenerator(self._fileobj_content, 'utf-8') + self.header = XMLGenerator(self._fileobj_header, 'utf-8') + self.title = 'Sheet' + + self._shared_date = SharedDate() + self._string_builder = self._parent.strings_table_builder + + @property + def filename(self): + return self._fileobj.name + + def write_header(self): + + doc = self.header + + start_tag(doc, 'worksheet', + {'xml:space': 'preserve', + 'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', + 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}) + start_tag(doc, 'sheetPr') + tag(doc, 'outlinePr', + {'summaryBelow': '1', + 'summaryRight': '1'}) + end_tag(doc, 'sheetPr') + tag(doc, 'dimension', {'ref': 'A1:%s' % (self.get_dimensions())}) + start_tag(doc, 'sheetViews') + start_tag(doc, 'sheetView', {'workbookViewId': '0'}) + tag(doc, 'selection', {'activeCell': 'A1', + 'sqref': 'A1'}) + end_tag(doc, 'sheetView') + end_tag(doc, 'sheetViews') + tag(doc, 'sheetFormatPr', {'defaultRowHeight': '15'}) + start_tag(doc, 'sheetData') + + def close(self): + + self._close_content() + self._close_header() + + self._write_fileobj(self._fileobj_header) + self._write_fileobj(self._fileobj_content) + + self._fileobj.close() + + def _write_fileobj(self, fobj): + + fobj.flush() + fobj.seek(0) + + while True: + chunk = fobj.read(4096) + if not chunk: + break + self._fileobj.write(chunk) + + fobj.close() + os.remove(fobj.name) + + self._fileobj.flush() + + def _close_header(self): + + doc = self.header + #doc.endDocument() + + def _close_content(self): + + doc = self.doc + end_tag(doc, 'sheetData') + + end_tag(doc, 'worksheet') + #doc.endDocument() + + def get_dimensions(self): + + if not self._max_col or not self._max_row: + return 'A1' + else: + return '%s%d' % (get_column_letter(self._max_col), (self._max_row)) + + def append(self, row): + + """ + :param row: iterable containing values to append + :type row: iterable + """ + + doc = self.doc + + self._max_row += 1 + span = len(row) + self._max_col = max(self._max_col, span) + + row_idx = self._max_row + + attrs = {'r': '%d' % row_idx, + 'spans': '1:%d' % span} + + start_tag(doc, 'row', attrs) + + for col_idx, cell in enumerate(row): + + if cell is None: + continue + + coordinate = '%s%d' % (get_column_letter(col_idx+1), row_idx) + attributes = {'r': coordinate} + + if isinstance(cell, bool): + dtype = 'boolean' + elif isinstance(cell, (int, float)): + dtype = 'numeric' + elif isinstance(cell, (datetime.datetime, datetime.date)): + dtype = 'datetime' + cell = self._shared_date.datetime_to_julian(cell) + attributes['s'] = STYLES[dtype]['style'] + elif cell and cell[0] == '=': + dtype = 'formula' + else: + dtype = 'string' + cell = self._string_builder.add(cell) + + attributes['t'] = STYLES[dtype]['type'] + + start_tag(doc, 'c', attributes) + + if dtype == 'formula': + tag(doc, 'f', body = '%s' % cell[1:]) + tag(doc, 'v') + else: + tag(doc, 'v', body = '%s' % cell) + + end_tag(doc, 'c') + + + end_tag(doc, 'row') + + +def save_dump(workbook, filename): + + writer = ExcelDumpWriter(workbook) + writer.save(filename) + return True + +class ExcelDumpWriter(ExcelWriter): + + def __init__(self, workbook): + + self.workbook = workbook + self.style_writer = StyleDumpWriter(workbook) + self.style_writer._style_list.append(DATETIME_STYLE) + + def _write_string_table(self, archive): + + shared_string_table = self.workbook.strings_table_builder.get_table() + archive.writestr(ARC_SHARED_STRINGS, + write_string_table(shared_string_table)) + + return shared_string_table + + def _write_worksheets(self, archive, shared_string_table, style_writer): + + for i, sheet in enumerate(self.workbook.worksheets): + sheet.write_header() + sheet.close() + archive.write(sheet.filename, PACKAGE_WORKSHEETS + '/sheet%d.xml' % (i + 1)) + os.remove(sheet.filename) + + +class StyleDumpWriter(StyleWriter): + + def _get_style_list(self, workbook): + return [] + diff --git a/tablib/packages/openpyxl3/writer/excel.py b/tablib/packages/openpyxl3/writer/excel.py new file mode 100644 index 0000000..f09746f --- /dev/null +++ b/tablib/packages/openpyxl3/writer/excel.py @@ -0,0 +1,156 @@ +# file openpyxl/writer/excel.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Write a .xlsx file.""" + +# Python stdlib imports +from zipfile import ZipFile, ZIP_DEFLATED +from io import BytesIO as StringIO + +# package imports +from ..shared.ooxml import ARC_SHARED_STRINGS, ARC_CONTENT_TYPES, \ + ARC_ROOT_RELS, ARC_WORKBOOK_RELS, ARC_APP, ARC_CORE, ARC_THEME, \ + ARC_STYLE, ARC_WORKBOOK, \ + PACKAGE_WORKSHEETS, PACKAGE_DRAWINGS, PACKAGE_CHARTS +from ..writer.strings import create_string_table, write_string_table +from ..writer.workbook import write_content_types, write_root_rels, \ + write_workbook_rels, write_properties_app, write_properties_core, \ + write_workbook +from ..writer.theme import write_theme +from ..writer.styles import StyleWriter +from ..writer.drawings import DrawingWriter, ShapeWriter +from ..writer.charts import ChartWriter +from ..writer.worksheet import write_worksheet, write_worksheet_rels + + +class ExcelWriter(object): + """Write a workbook object to an Excel file.""" + + def __init__(self, workbook): + self.workbook = workbook + self.style_writer = StyleWriter(self.workbook) + + def write_data(self, archive): + """Write the various xml files into the zip archive.""" + # cleanup all worksheets + shared_string_table = self._write_string_table(archive) + + archive.writestr(ARC_CONTENT_TYPES, write_content_types(self.workbook)) + archive.writestr(ARC_ROOT_RELS, write_root_rels(self.workbook)) + archive.writestr(ARC_WORKBOOK_RELS, write_workbook_rels(self.workbook)) + archive.writestr(ARC_APP, write_properties_app(self.workbook)) + archive.writestr(ARC_CORE, + write_properties_core(self.workbook.properties)) + archive.writestr(ARC_THEME, write_theme()) + archive.writestr(ARC_STYLE, self.style_writer.write_table()) + archive.writestr(ARC_WORKBOOK, write_workbook(self.workbook)) + + self._write_worksheets(archive, shared_string_table, self.style_writer) + + def _write_string_table(self, archive): + + for ws in self.workbook.worksheets: + ws.garbage_collect() + shared_string_table = create_string_table(self.workbook) + archive.writestr(ARC_SHARED_STRINGS, + write_string_table(shared_string_table)) + + return shared_string_table + + def _write_worksheets(self, archive, shared_string_table, style_writer): + + drawing_id = 1 + chart_id = 1 + shape_id = 1 + + for i, sheet in enumerate(self.workbook.worksheets): + archive.writestr(PACKAGE_WORKSHEETS + '/sheet%d.xml' % (i + 1), + write_worksheet(sheet, shared_string_table, + style_writer.get_style_by_hash())) + if sheet._charts or sheet.relationships: + archive.writestr(PACKAGE_WORKSHEETS + + '/_rels/sheet%d.xml.rels' % (i + 1), + write_worksheet_rels(sheet, drawing_id)) + if sheet._charts: + dw = DrawingWriter(sheet) + archive.writestr(PACKAGE_DRAWINGS + '/drawing%d.xml' % drawing_id, + dw.write()) + archive.writestr(PACKAGE_DRAWINGS + '/_rels/drawing%d.xml.rels' % drawing_id, + dw.write_rels(chart_id)) + drawing_id += 1 + + for chart in sheet._charts: + cw = ChartWriter(chart) + archive.writestr(PACKAGE_CHARTS + '/chart%d.xml' % chart_id, + cw.write()) + + if chart._shapes: + archive.writestr(PACKAGE_CHARTS + '/_rels/chart%d.xml.rels' % chart_id, + cw.write_rels(drawing_id)) + sw = ShapeWriter(chart._shapes) + archive.writestr(PACKAGE_DRAWINGS + '/drawing%d.xml' % drawing_id, + sw.write(shape_id)) + shape_id += len(chart._shapes) + drawing_id += 1 + + chart_id += 1 + + + def save(self, filename): + """Write data into the archive.""" + archive = ZipFile(filename, 'w', ZIP_DEFLATED) + self.write_data(archive) + archive.close() + + +def save_workbook(workbook, filename): + """Save the given workbook on the filesystem under the name filename. + + :param workbook: the workbook to save + :type workbook: :class:`openpyxl.workbook.Workbook` + + :param filename: the path to which save the workbook + :type filename: string + + :rtype: bool + + """ + writer = ExcelWriter(workbook) + writer.save(filename) + return True + + +def save_virtual_workbook(workbook): + """Return an in-memory workbook, suitable for a Django response.""" + writer = ExcelWriter(workbook) + temp_buffer = StringIO() + try: + archive = ZipFile(temp_buffer, 'w', ZIP_DEFLATED) + writer.write_data(archive) + finally: + archive.close() + virtual_workbook = temp_buffer.getvalue() + temp_buffer.close() + return virtual_workbook diff --git a/tablib/packages/openpyxl3/writer/strings.py b/tablib/packages/openpyxl3/writer/strings.py new file mode 100644 index 0000000..f3e882e --- /dev/null +++ b/tablib/packages/openpyxl3/writer/strings.py @@ -0,0 +1,86 @@ +# file openpyxl/writer/strings.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Write the shared string table.""" + +# Python stdlib imports +from io import BytesIO as StringIO + +# package imports +from ..shared.xmltools import start_tag, end_tag, tag, XMLGenerator + + +def create_string_table(workbook): + """Compile the string table for a workbook.""" + strings = set() + for sheet in workbook.worksheets: + for cell in sheet.get_cell_collection(): + if cell.data_type == cell.TYPE_STRING and cell._value is not None: + strings.add(cell.value) + return dict((key, i) for i, key in enumerate(strings)) + + +def write_string_table(string_table): + """Write the string table xml.""" + temp_buffer = StringIO() + doc = XMLGenerator(temp_buffer, 'utf-8') + start_tag(doc, 'sst', {'xmlns': + 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', + 'uniqueCount': '%d' % len(string_table)}) + strings_to_write = sorted(string_table.items(), + key=lambda pair: pair[1]) + for key in [pair[0] for pair in strings_to_write]: + start_tag(doc, 'si') + if key.strip() != key: + attr = {'xml:space': 'preserve'} + else: + attr = {} + tag(doc, 't', attr, key) + end_tag(doc, 'si') + end_tag(doc, 'sst') + string_table_xml = temp_buffer.getvalue() + temp_buffer.close() + return string_table_xml + +class StringTableBuilder(object): + + def __init__(self): + + self.counter = 0 + self.dct = {} + + def add(self, key): + + key = key.strip() + try: + return self.dct[key] + except KeyError: + res = self.dct[key] = self.counter + self.counter += 1 + return res + + def get_table(self): + + return self.dct diff --git a/tablib/packages/openpyxl3/writer/styles.py b/tablib/packages/openpyxl3/writer/styles.py new file mode 100644 index 0000000..7e3fb64 --- /dev/null +++ b/tablib/packages/openpyxl3/writer/styles.py @@ -0,0 +1,256 @@ +# file openpyxl/writer/styles.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Write the shared style table.""" + +# package imports +from ..shared.xmltools import Element, SubElement +from ..shared.xmltools import get_document_content +from .. import style + +class StyleWriter(object): + + def __init__(self, workbook): + self._style_list = self._get_style_list(workbook) + self._root = Element('styleSheet', + {'xmlns':'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}) + + def _get_style_list(self, workbook): + crc = {} + for worksheet in workbook.worksheets: + for style in worksheet._styles.values(): + crc[hash(style)] = style + self.style_table = dict([(style, i+1) \ + for i, style in enumerate(crc.values())]) + sorted_styles = sorted(self.style_table.items(), \ + key = lambda pair:pair[1]) + return [s[0] for s in sorted_styles] + + def get_style_by_hash(self): + return dict([(hash(style), id) \ + for style, id in self.style_table.items()]) + + def write_table(self): + number_format_table = self._write_number_formats() + fonts_table = self._write_fonts() + fills_table = self._write_fills() + borders_table = self._write_borders() + self._write_cell_style_xfs() + self._write_cell_xfs(number_format_table, fonts_table, fills_table, borders_table) + self._write_cell_style() + self._write_dxfs() + self._write_table_styles() + + return get_document_content(xml_node=self._root) + + def _write_fonts(self): + """ add fonts part to root + return {font.crc => index} + """ + + fonts = SubElement(self._root, 'fonts') + + # default + font_node = SubElement(fonts, 'font') + SubElement(font_node, 'sz', {'val':'11'}) + SubElement(font_node, 'color', {'theme':'1'}) + SubElement(font_node, 'name', {'val':'Calibri'}) + SubElement(font_node, 'family', {'val':'2'}) + SubElement(font_node, 'scheme', {'val':'minor'}) + + # others + table = {} + index = 1 + for st in self._style_list: + if hash(st.font) != hash(style.DEFAULTS.font) and hash(st.font) not in table: + table[hash(st.font)] = str(index) + font_node = SubElement(fonts, 'font') + SubElement(font_node, 'sz', {'val':str(st.font.size)}) + SubElement(font_node, 'color', {'rgb':str(st.font.color.index)}) + SubElement(font_node, 'name', {'val':st.font.name}) + SubElement(font_node, 'family', {'val':'2'}) + SubElement(font_node, 'scheme', {'val':'minor'}) + if st.font.bold: + SubElement(font_node, 'b') + if st.font.italic: + SubElement(font_node, 'i') + index += 1 + + fonts.attrib["count"] = str(index) + return table + + def _write_fills(self): + fills = SubElement(self._root, 'fills', {'count':'2'}) + fill = SubElement(fills, 'fill') + SubElement(fill, 'patternFill', {'patternType':'none'}) + fill = SubElement(fills, 'fill') + SubElement(fill, 'patternFill', {'patternType':'gray125'}) + + table = {} + index = 2 + for st in self._style_list: + if hash(st.fill) != hash(style.DEFAULTS.fill) and hash(st.fill) not in table: + table[hash(st.fill)] = str(index) + fill = SubElement(fills, 'fill') + if hash(st.fill.fill_type) != hash(style.DEFAULTS.fill.fill_type): + node = SubElement(fill,'patternFill', {'patternType':st.fill.fill_type}) + if hash(st.fill.start_color) != hash(style.DEFAULTS.fill.start_color): + + SubElement(node, 'fgColor', {'rgb':str(st.fill.start_color.index)}) + if hash(st.fill.end_color) != hash(style.DEFAULTS.fill.end_color): + SubElement(node, 'bgColor', {'rgb':str(st.fill.start_color.index)}) + index += 1 + + fills.attrib["count"] = str(index) + return table + + def _write_borders(self): + borders = SubElement(self._root, 'borders') + + # default + border = SubElement(borders, 'border') + SubElement(border, 'left') + SubElement(border, 'right') + SubElement(border, 'top') + SubElement(border, 'bottom') + SubElement(border, 'diagonal') + + # others + table = {} + index = 1 + for st in self._style_list: + if hash(st.borders) != hash(style.DEFAULTS.borders) and hash(st.borders) not in table: + table[hash(st.borders)] = str(index) + border = SubElement(borders, 'border') + # caution: respect this order + for side in ('left','right','top','bottom','diagonal'): + obj = getattr(st.borders, side) + node = SubElement(border, side, {'style':obj.border_style}) + SubElement(node, 'color', {'rgb':str(obj.color.index)}) + index += 1 + + borders.attrib["count"] = str(index) + return table + + def _write_cell_style_xfs(self): + cell_style_xfs = SubElement(self._root, 'cellStyleXfs', {'count':'1'}) + xf = SubElement(cell_style_xfs, 'xf', + {'numFmtId':"0", 'fontId':"0", 'fillId':"0", 'borderId':"0"}) + + def _write_cell_xfs(self, number_format_table, fonts_table, fills_table, borders_table): + """ write styles combinations based on ids found in tables """ + + # writing the cellXfs + cell_xfs = SubElement(self._root, 'cellXfs', + {'count':'%d' % (len(self._style_list) + 1)}) + + # default + def _get_default_vals(): + return dict(numFmtId='0', fontId='0', fillId='0', + xfId='0', borderId='0') + + SubElement(cell_xfs, 'xf', _get_default_vals()) + + for st in self._style_list: + vals = _get_default_vals() + + if hash(st.font) != hash(style.DEFAULTS.font): + vals['fontId'] = fonts_table[hash(st.font)] + vals['applyFont'] = '1' + + if hash(st.borders) != hash(style.DEFAULTS.borders): + vals['borderId'] = borders_table[hash(st.borders)] + vals['applyBorder'] = '1' + + if hash(st.fill) != hash(style.DEFAULTS.fill): + vals['fillId'] = fills_table[hash(st.fill)] + vals['applyFillId'] = '1' + + if st.number_format != style.DEFAULTS.number_format: + vals['numFmtId'] = '%d' % number_format_table[st.number_format] + vals['applyNumberFormat'] = '1' + + if hash(st.alignment) != hash(style.DEFAULTS.alignment): + vals['applyAlignment'] = '1' + + node = SubElement(cell_xfs, 'xf', vals) + + if hash(st.alignment) != hash(style.DEFAULTS.alignment): + alignments = {} + + for align_attr in ['horizontal','vertical']: + if hash(getattr(st.alignment, align_attr)) != hash(getattr(style.DEFAULTS.alignment, align_attr)): + alignments[align_attr] = getattr(st.alignment, align_attr) + + SubElement(node, 'alignment', alignments) + + + def _write_cell_style(self): + cell_styles = SubElement(self._root, 'cellStyles', {'count':'1'}) + cell_style = SubElement(cell_styles, 'cellStyle', + {'name':"Normal", 'xfId':"0", 'builtinId':"0"}) + + def _write_dxfs(self): + dxfs = SubElement(self._root, 'dxfs', {'count':'0'}) + + def _write_table_styles(self): + + table_styles = SubElement(self._root, 'tableStyles', + {'count':'0', 'defaultTableStyle':'TableStyleMedium9', + 'defaultPivotStyle':'PivotStyleLight16'}) + + def _write_number_formats(self): + + number_format_table = {} + + number_format_list = [] + exceptions_list = [] + num_fmt_id = 165 # start at a greatly higher value as any builtin can go + num_fmt_offset = 0 + + for style in self._style_list: + + if not style.number_format in number_format_list : + number_format_list.append(style.number_format) + + for number_format in number_format_list: + + if number_format.is_builtin(): + btin = number_format.builtin_format_id(number_format.format_code) + number_format_table[number_format] = btin + else: + number_format_table[number_format] = num_fmt_id + num_fmt_offset + num_fmt_offset += 1 + exceptions_list.append(number_format) + + num_fmts = SubElement(self._root, 'numFmts', + {'count':'%d' % len(exceptions_list)}) + + for number_format in exceptions_list : + SubElement(num_fmts, 'numFmt', + {'numFmtId':'%d' % number_format_table[number_format], + 'formatCode':'%s' % number_format.format_code}) + + return number_format_table diff --git a/tablib/packages/openpyxl3/writer/theme.py b/tablib/packages/openpyxl3/writer/theme.py new file mode 100644 index 0000000..80700f2 --- /dev/null +++ b/tablib/packages/openpyxl3/writer/theme.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +# file openpyxl/writer/theme.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Write the theme xml based on a fixed string.""" + +# package imports +from ..shared.xmltools import fromstring, get_document_content + + +def write_theme(): + """Write the theme xml.""" + xml_node = fromstring( + '\n' + + '' + '' + + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '') + return get_document_content(xml_node) diff --git a/tablib/packages/openpyxl3/writer/workbook.py b/tablib/packages/openpyxl3/writer/workbook.py new file mode 100644 index 0000000..e7b390c --- /dev/null +++ b/tablib/packages/openpyxl3/writer/workbook.py @@ -0,0 +1,204 @@ +# file openpyxl/writer/workbook.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Write the workbook global settings to the archive.""" + +# package imports +from ..shared.xmltools import Element, SubElement +from ..cell import absolute_coordinate +from ..shared.xmltools import get_document_content +from ..shared.ooxml import NAMESPACES, ARC_CORE, ARC_WORKBOOK, \ + ARC_APP, ARC_THEME, ARC_STYLE, ARC_SHARED_STRINGS +from ..shared.date_time import datetime_to_W3CDTF + + +def write_properties_core(properties): + """Write the core properties to xml.""" + root = Element('cp:coreProperties', {'xmlns:cp': NAMESPACES['cp'], + 'xmlns:xsi': NAMESPACES['xsi'], 'xmlns:dc': NAMESPACES['dc'], + 'xmlns:dcterms': NAMESPACES['dcterms'], + 'xmlns:dcmitype': NAMESPACES['dcmitype'], }) + SubElement(root, 'dc:creator').text = properties.creator + SubElement(root, 'cp:lastModifiedBy').text = properties.last_modified_by + SubElement(root, 'dcterms:created', \ + {'xsi:type': 'dcterms:W3CDTF'}).text = \ + datetime_to_W3CDTF(properties.created) + SubElement(root, 'dcterms:modified', + {'xsi:type': 'dcterms:W3CDTF'}).text = \ + datetime_to_W3CDTF(properties.modified) + return get_document_content(root) + + +def write_content_types(workbook): + """Write the content-types xml.""" + root = Element('Types', {'xmlns': 'http://schemas.openxmlformats.org/package/2006/content-types'}) + SubElement(root, 'Override', {'PartName': '/' + ARC_THEME, 'ContentType': 'application/vnd.openxmlformats-officedocument.theme+xml'}) + SubElement(root, 'Override', {'PartName': '/' + ARC_STYLE, 'ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml'}) + SubElement(root, 'Default', {'Extension': 'rels', 'ContentType': 'application/vnd.openxmlformats-package.relationships+xml'}) + SubElement(root, 'Default', {'Extension': 'xml', 'ContentType': 'application/xml'}) + SubElement(root, 'Override', {'PartName': '/' + ARC_WORKBOOK, 'ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml'}) + SubElement(root, 'Override', {'PartName': '/' + ARC_APP, 'ContentType': 'application/vnd.openxmlformats-officedocument.extended-properties+xml'}) + SubElement(root, 'Override', {'PartName': '/' + ARC_CORE, 'ContentType': 'application/vnd.openxmlformats-package.core-properties+xml'}) + SubElement(root, 'Override', {'PartName': '/' + ARC_SHARED_STRINGS, 'ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml'}) + + drawing_id = 1 + chart_id = 1 + + for sheet_id, sheet in enumerate(workbook.worksheets): + SubElement(root, 'Override', + {'PartName': '/xl/worksheets/sheet%d.xml' % (sheet_id + 1), + 'ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml'}) + if sheet._charts: + SubElement(root, 'Override', + {'PartName' : '/xl/drawings/drawing%d.xml' % (sheet_id + 1), + 'ContentType' : 'application/vnd.openxmlformats-officedocument.drawing+xml'}) + drawing_id += 1 + + for chart in sheet._charts: + SubElement(root, 'Override', + {'PartName' : '/xl/charts/chart%d.xml' % chart_id, + 'ContentType' : 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml'}) + chart_id += 1 + if chart._shapes: + SubElement(root, 'Override', + {'PartName' : '/xl/drawings/drawing%d.xml' % drawing_id, + 'ContentType' : 'application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml'}) + drawing_id += 1 + + return get_document_content(root) + + +def write_properties_app(workbook): + """Write the properties xml.""" + worksheets_count = len(workbook.worksheets) + root = Element('Properties', {'xmlns': 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties', + 'xmlns:vt': 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'}) + SubElement(root, 'Application').text = 'Microsoft Excel' + SubElement(root, 'DocSecurity').text = '0' + SubElement(root, 'ScaleCrop').text = 'false' + SubElement(root, 'Company') + SubElement(root, 'LinksUpToDate').text = 'false' + SubElement(root, 'SharedDoc').text = 'false' + SubElement(root, 'HyperlinksChanged').text = 'false' + SubElement(root, 'AppVersion').text = '12.0000' + + # heading pairs part + heading_pairs = SubElement(root, 'HeadingPairs') + vector = SubElement(heading_pairs, 'vt:vector', + {'size': '2', 'baseType': 'variant'}) + variant = SubElement(vector, 'vt:variant') + SubElement(variant, 'vt:lpstr').text = 'Worksheets' + variant = SubElement(vector, 'vt:variant') + SubElement(variant, 'vt:i4').text = '%d' % worksheets_count + + # title of parts + title_of_parts = SubElement(root, 'TitlesOfParts') + vector = SubElement(title_of_parts, 'vt:vector', + {'size': '%d' % worksheets_count, 'baseType': 'lpstr'}) + for ws in workbook.worksheets: + SubElement(vector, 'vt:lpstr').text = '%s' % ws.title + return get_document_content(root) + + +def write_root_rels(workbook): + """Write the relationships xml.""" + root = Element('Relationships', {'xmlns': + 'http://schemas.openxmlformats.org/package/2006/relationships'}) + SubElement(root, 'Relationship', {'Id': 'rId1', 'Target': ARC_WORKBOOK, + 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument'}) + SubElement(root, 'Relationship', {'Id': 'rId2', 'Target': ARC_CORE, + 'Type': 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties'}) + SubElement(root, 'Relationship', {'Id': 'rId3', 'Target': ARC_APP, + 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties'}) + return get_document_content(root) + + +def write_workbook(workbook): + """Write the core workbook xml.""" + root = Element('workbook', {'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', + 'xml:space': 'preserve', 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}) + SubElement(root, 'fileVersion', {'appName': 'xl', 'lastEdited': '4', + 'lowestEdited': '4', 'rupBuild': '4505'}) + SubElement(root, 'workbookPr', {'defaultThemeVersion': '124226', + 'codeName': 'ThisWorkbook'}) + book_views = SubElement(root, 'bookViews') + SubElement(book_views, 'workbookView', {'activeTab': '%d' % workbook.get_index(workbook.get_active_sheet()), + 'autoFilterDateGrouping': '1', 'firstSheet': '0', 'minimized': '0', + 'showHorizontalScroll': '1', 'showSheetTabs': '1', + 'showVerticalScroll': '1', 'tabRatio': '600', + 'visibility': 'visible'}) + # worksheets + sheets = SubElement(root, 'sheets') + for i, sheet in enumerate(workbook.worksheets): + sheet_node = SubElement(sheets, 'sheet', {'name': sheet.title, + 'sheetId': '%d' % (i + 1), 'r:id': 'rId%d' % (i + 1)}) + if not sheet.sheet_state == sheet.SHEETSTATE_VISIBLE: + sheet_node.set('state', sheet.sheet_state) + # named ranges + defined_names = SubElement(root, 'definedNames') + for named_range in workbook.get_named_ranges(): + name = SubElement(defined_names, 'definedName', + {'name': named_range.name}) + + # as there can be many cells in one range, generate the list of ranges + dest_cells = [] + cell_ids = [] + for worksheet, range_name in named_range.destinations: + cell_ids.append(workbook.get_index(worksheet)) + dest_cells.append("'%s'!%s" % (worksheet.title.replace("'", "''"), + absolute_coordinate(range_name))) + + # for local ranges, we must check all the cells belong to the same sheet + base_id = cell_ids[0] + if named_range.local_only and all([x == base_id for x in cell_ids]): + name.set('localSheetId', '%s' % base_id) + + # finally write the cells list + name.text = ','.join(dest_cells) + + SubElement(root, 'calcPr', {'calcId': '124519', 'calcMode': 'auto', + 'fullCalcOnLoad': '1'}) + return get_document_content(root) + + +def write_workbook_rels(workbook): + """Write the workbook relationships xml.""" + root = Element('Relationships', {'xmlns': + 'http://schemas.openxmlformats.org/package/2006/relationships'}) + for i in range(len(workbook.worksheets)): + SubElement(root, 'Relationship', {'Id': 'rId%d' % (i + 1), + 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet', + 'Target': 'worksheets/sheet%s.xml' % (i + 1)}) + rid = len(workbook.worksheets) + 1 + SubElement(root, 'Relationship', + {'Id': 'rId%d' % rid, 'Target': 'sharedStrings.xml', + 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings'}) + SubElement(root, 'Relationship', + {'Id': 'rId%d' % (rid + 1), 'Target': 'styles.xml', + 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles'}) + SubElement(root, 'Relationship', + {'Id': 'rId%d' % (rid + 2), 'Target': 'theme/theme1.xml', + 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme'}) + return get_document_content(root) diff --git a/tablib/packages/openpyxl3/writer/worksheet.py b/tablib/packages/openpyxl3/writer/worksheet.py new file mode 100644 index 0000000..b7633f8 --- /dev/null +++ b/tablib/packages/openpyxl3/writer/worksheet.py @@ -0,0 +1,209 @@ +# file openpyxl/writer/worksheet.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +"""Write worksheets to xml representations.""" + +# Python stdlib imports +from io import BytesIO as StringIO # cStringIO doesn't handle unicode + +# package imports +from ..cell import coordinate_from_string, column_index_from_string +from ..shared.xmltools import Element, SubElement, XMLGenerator, \ + get_document_content, start_tag, end_tag, tag + + +def row_sort(cell): + """Translate column names for sorting.""" + return column_index_from_string(cell.column) + + +def write_worksheet(worksheet, string_table, style_table): + """Write a worksheet to an xml file.""" + xml_file = StringIO() + doc = XMLGenerator(xml_file, 'utf-8') + start_tag(doc, 'worksheet', + {'xml:space': 'preserve', + 'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', + 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}) + start_tag(doc, 'sheetPr') + tag(doc, 'outlinePr', + {'summaryBelow': '%d' % (worksheet.show_summary_below), + 'summaryRight': '%d' % (worksheet.show_summary_right)}) + end_tag(doc, 'sheetPr') + tag(doc, 'dimension', {'ref': '%s' % worksheet.calculate_dimension()}) + write_worksheet_sheetviews(doc, worksheet) + tag(doc, 'sheetFormatPr', {'defaultRowHeight': '15'}) + write_worksheet_cols(doc, worksheet) + write_worksheet_data(doc, worksheet, string_table, style_table) + if worksheet.auto_filter: + tag(doc, 'autoFilter', {'ref': worksheet.auto_filter}) + write_worksheet_hyperlinks(doc, worksheet) + if worksheet._charts: + tag(doc, 'drawing', {'r:id':'rId1'}) + end_tag(doc, 'worksheet') + doc.endDocument() + xml_string = xml_file.getvalue() + xml_file.close() + return xml_string + +def write_worksheet_sheetviews(doc, worksheet): + start_tag(doc, 'sheetViews') + start_tag(doc, 'sheetView', {'workbookViewId': '0'}) + selectionAttrs = {} + topLeftCell = worksheet.freeze_panes + if topLeftCell: + colName, row = coordinate_from_string(topLeftCell) + column = column_index_from_string(colName) + pane = 'topRight' + paneAttrs = {} + if column > 1: + paneAttrs['xSplit'] = str(column - 1) + if row > 1: + paneAttrs['ySplit'] = str(row - 1) + pane = 'bottomLeft' + if column > 1: + pane = 'bottomRight' + paneAttrs.update(dict(topLeftCell=topLeftCell, + activePane=pane, + state='frozen')) + tag(doc, 'pane', paneAttrs) + selectionAttrs['pane'] = pane + if row > 1 and column > 1: + tag(doc, 'selection', {'pane': 'topRight'}) + tag(doc, 'selection', {'pane': 'bottomLeft'}) + + selectionAttrs.update({'activeCell': worksheet.active_cell, + 'sqref': worksheet.selected_cell}) + + tag(doc, 'selection', selectionAttrs) + end_tag(doc, 'sheetView') + end_tag(doc, 'sheetViews') + + +def write_worksheet_cols(doc, worksheet): + """Write worksheet columns to xml.""" + if worksheet.column_dimensions: + start_tag(doc, 'cols') + for column_string, columndimension in \ + worksheet.column_dimensions.items(): + col_index = column_index_from_string(column_string) + col_def = {} + col_def['collapsed'] = str(columndimension.style_index) + col_def['min'] = str(col_index) + col_def['max'] = str(col_index) + if columndimension.width != \ + worksheet.default_column_dimension.width: + col_def['customWidth'] = 'true' + if not columndimension.visible: + col_def['hidden'] = 'true' + if columndimension.outline_level > 0: + col_def['outlineLevel'] = str(columndimension.outline_level) + if columndimension.collapsed: + col_def['collapsed'] = 'true' + if columndimension.auto_size: + col_def['bestFit'] = 'true' + if columndimension.width > 0: + col_def['width'] = str(columndimension.width) + else: + col_def['width'] = '9.10' + tag(doc, 'col', col_def) + end_tag(doc, 'cols') + + +def write_worksheet_data(doc, worksheet, string_table, style_table): + """Write worksheet data to xml.""" + start_tag(doc, 'sheetData') + max_column = worksheet.get_highest_column() + style_id_by_hash = style_table + cells_by_row = {} + for cell in worksheet.get_cell_collection(): + cells_by_row.setdefault(cell.row, []).append(cell) + for row_idx in sorted(cells_by_row): + row_dimension = worksheet.row_dimensions[row_idx] + attrs = {'r': '%d' % row_idx, + 'spans': '1:%d' % max_column} + if row_dimension.height > 0: + attrs['ht'] = str(row_dimension.height) + attrs['customHeight'] = '1' + start_tag(doc, 'row', attrs) + row_cells = cells_by_row[row_idx] + sorted_cells = sorted(row_cells, key = row_sort) + for cell in sorted_cells: + value = cell._value + coordinate = cell.get_coordinate() + attributes = {'r': coordinate} + attributes['t'] = cell.data_type + if coordinate in worksheet._styles: + attributes['s'] = '%d' % style_id_by_hash[ + hash(worksheet._styles[coordinate])] + start_tag(doc, 'c', attributes) + if value is None: + tag(doc, 'v', body='') + elif cell.data_type == cell.TYPE_STRING: + tag(doc, 'v', body = '%s' % string_table[value]) + elif cell.data_type == cell.TYPE_FORMULA: + tag(doc, 'f', body = '%s' % value[1:]) + tag(doc, 'v') + elif cell.data_type == cell.TYPE_NUMERIC: + tag(doc, 'v', body = '%s' % value) + else: + tag(doc, 'v', body = '%s' % value) + end_tag(doc, 'c') + end_tag(doc, 'row') + end_tag(doc, 'sheetData') + + +def write_worksheet_hyperlinks(doc, worksheet): + """Write worksheet hyperlinks to xml.""" + write_hyperlinks = False + for cell in worksheet.get_cell_collection(): + if cell.hyperlink_rel_id is not None: + write_hyperlinks = True + break + if write_hyperlinks: + start_tag(doc, 'hyperlinks') + for cell in worksheet.get_cell_collection(): + if cell.hyperlink_rel_id is not None: + attrs = {'display': cell.hyperlink, + 'ref': cell.get_coordinate(), + 'r:id': cell.hyperlink_rel_id} + tag(doc, 'hyperlink', attrs) + end_tag(doc, 'hyperlinks') + + +def write_worksheet_rels(worksheet, idx): + """Write relationships for the worksheet to xml.""" + root = Element('Relationships', {'xmlns': 'http://schemas.openxmlformats.org/package/2006/relationships'}) + for rel in worksheet.relationships: + attrs = {'Id': rel.id, 'Type': rel.type, 'Target': rel.target} + if rel.target_mode: + attrs['TargetMode'] = rel.target_mode + SubElement(root, 'Relationship', attrs) + if worksheet._charts: + attrs = {'Id' : 'rId1', + 'Type' : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing', + 'Target' : '../drawings/drawing%s.xml' % idx } + SubElement(root, 'Relationship', attrs) + return get_document_content(root) From 2b36d71554b50e4123bc2a6eb5065689b8860272 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 12 May 2011 19:16:37 -0400 Subject: [PATCH 14/22] additional compat mappings --- tablib/compat.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tablib/compat.py b/tablib/compat.py index 09a56b6..82946f7 100644 --- a/tablib/compat.py +++ b/tablib/compat.py @@ -25,9 +25,23 @@ if is_py3: import tablib.packages.xlwt3 as xlwt from tablib.packages import markup3 as markup + # py3 mappings + ifilter = filter + xrange = range + unicode = str + bytes = bytes + basestring = str + else: from cStringIO import StringIO as BytesIO import tablib.packages.xlwt as xlwt from tablib.packages import markup + from itertools import ifilter + + # py2 mappings + xrange = xrange + unicode = unicode + bytes = str + basestring = basestring From a60e2f132e67ebdfab6a4220953c8b15d6e97378 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 13 May 2011 00:28:50 -0400 Subject: [PATCH 15/22] Finally! :sparkles:Python 3:sparkles: port of openpyxl --- tablib/packages/openpyxl3/cell.py | 4 +- tablib/packages/openpyxl3/chart.py | 680 +++++++-------- tablib/packages/openpyxl3/drawing.py | 804 +++++++++--------- tablib/packages/openpyxl3/reader/__init__.py | 10 +- tablib/packages/openpyxl3/reader/excel.py | 14 +- .../openpyxl3/reader/iter_worksheet.py | 686 +++++++-------- tablib/packages/openpyxl3/reader/worksheet.py | 13 +- tablib/packages/openpyxl3/shared/__init__.py | 12 +- tablib/packages/openpyxl3/shared/date_time.py | 4 +- tablib/packages/openpyxl3/style.py | 2 +- tablib/packages/openpyxl3/tests/helper.py | 4 +- tablib/packages/openpyxl3/tests/test_cell.py | 2 +- tablib/packages/openpyxl3/tests/test_chart.py | 262 +++--- tablib/packages/openpyxl3/tests/test_iter.py | 224 ++--- tablib/packages/openpyxl3/tests/test_meta.py | 2 +- .../openpyxl3/tests/test_named_range.py | 2 +- .../openpyxl3/tests/test_number_format.py | 6 +- tablib/packages/openpyxl3/tests/test_props.py | Bin 9060 -> 4494 bytes tablib/packages/openpyxl3/tests/test_read.py | 2 +- .../packages/openpyxl3/tests/test_strings.py | 2 +- tablib/packages/openpyxl3/tests/test_style.py | 2 +- tablib/packages/openpyxl3/tests/test_write.py | 4 +- tablib/packages/openpyxl3/worksheet.py | 8 +- tablib/packages/openpyxl3/writer/charts.py | 523 ++++++------ .../openpyxl3/writer/dump_worksheet.py | 8 +- tablib/packages/openpyxl3/writer/excel.py | 16 +- tablib/packages/openpyxl3/writer/strings.py | 4 +- tablib/packages/openpyxl3/writer/styles.py | 6 +- tablib/packages/openpyxl3/writer/worksheet.py | 2 +- 29 files changed, 1657 insertions(+), 1651 deletions(-) diff --git a/tablib/packages/openpyxl3/cell.py b/tablib/packages/openpyxl3/cell.py index 300f0ed..1171fde 100644 --- a/tablib/packages/openpyxl3/cell.py +++ b/tablib/packages/openpyxl3/cell.py @@ -335,7 +335,7 @@ class Cell(object): @property def style(self): - """Returns the :class:`openpyxl.style.Style` object for this cell""" + """Returns the :class:`.style.Style` object for this cell""" return self.parent.get_style(self.get_coordinate()) @property @@ -367,7 +367,7 @@ class Cell(object): :param column: number of columns to offset :type column: int - :rtype: :class:`openpyxl.cell.Cell` + :rtype: :class:`.cell.Cell` """ offset_column = get_column_letter(column_index_from_string( column = self.column) + column) diff --git a/tablib/packages/openpyxl3/chart.py b/tablib/packages/openpyxl3/chart.py index 19ba3b9..265ccaf 100644 --- a/tablib/packages/openpyxl3/chart.py +++ b/tablib/packages/openpyxl3/chart.py @@ -1,340 +1,340 @@ -''' -Copyright (c) 2010 openpyxl - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -@license: http://www.opensource.org/licenses/mit-license.php -@author: Eric Gazoni -''' - -import math - -from .style import NumberFormat -from .drawing import Drawing, Shape -from .shared.units import pixels_to_EMU, short_color -from .cell import get_column_letter - -class Axis(object): - - POSITION_BOTTOM = 'b' - POSITION_LEFT = 'l' - - ORIENTATION_MIN_MAX = "minMax" - - def __init__(self): - - self.orientation = self.ORIENTATION_MIN_MAX - self.number_format = NumberFormat() - for attr in ('position','tick_label_position','crosses', - 'auto','label_align','label_offset','cross_between'): - setattr(self, attr, None) - self.min = 0 - self.max = None - self.unit = None - - @classmethod - def default_category(cls): - """ default values for category axes """ - - ax = Axis() - ax.id = 60871424 - ax.cross = 60873344 - ax.position = Axis.POSITION_BOTTOM - ax.tick_label_position = 'nextTo' - ax.crosses = "autoZero" - ax.auto = True - ax.label_align = 'ctr' - ax.label_offset = 100 - return ax - - @classmethod - def default_value(cls): - """ default values for value axes """ - - ax = Axis() - ax.id = 60873344 - ax.cross = 60871424 - ax.position = Axis.POSITION_LEFT - ax.major_gridlines = None - ax.tick_label_position = 'nextTo' - ax.crosses = 'autoZero' - ax.auto = False - ax.cross_between = 'between' - return ax - -class Reference(object): - """ a simple wrapper around a serie of reference data """ - - def __init__(self, sheet, pos1, pos2=None): - - self.sheet = sheet - self.pos1 = pos1 - self.pos2 = pos2 - - def get_type(self): - - if isinstance(self.cache[0], str): - return 'str' - else: - return 'num' - - def _get_ref(self): - """ format excel reference notation """ - - if self.pos2: - return '%s!$%s$%s:$%s$%s' % (self.sheet.title, - get_column_letter(self.pos1[1]+1), self.pos1[0]+1, - get_column_letter(self.pos2[1]+1), self.pos2[0]+1) - else: - return '%s!$%s$%s' % (self.sheet.title, - get_column_letter(self.pos1[1]+1), self.pos1[0]+1) - - - def _get_cache(self): - """ read data in sheet - to be used at writing time """ - - cache = [] - if self.pos2: - for row in range(self.pos1[0], self.pos2[0]+1): - for col in range(self.pos1[1], self.pos2[1]+1): - cache.append(self.sheet.cell(row=row, column=col).value) - else: - cell = self.sheet.cell(row=self.pos1[0], column=self.pos1[1]) - cache.append(cell.value) - return cache - - -class Serie(object): - """ a serie of data and possibly associated labels """ - - MARKER_NONE = 'none' - - def __init__(self, values, labels=None, legend=None, color=None, xvalues=None): - - self.marker = Serie.MARKER_NONE - self.values = values - self.xvalues = xvalues - self.labels = labels - self.legend = legend - self.error_bar = None - self._color = color - - def _get_color(self): - return self._color - - def _set_color(self, color): - self._color = short_color(color) - - color = property(_get_color, _set_color) - - def get_min_max(self): - - if self.error_bar: - err_cache = self.error_bar.values._get_cache() - vals = [v + err_cache[i] \ - for i,v in enumerate(self.values._get_cache())] - else: - vals = self.values._get_cache() - return min(vals), max(vals) - - def __len__(self): - - return len(self.values.cache) - -class Legend(object): - - def __init__(self): - - self.position = 'r' - self.layout = None - -class ErrorBar(object): - - PLUS = 1 - MINUS = 2 - PLUS_MINUS = 3 - - def __init__(self, _type, values): - - self.type = _type - self.values = values - -class Chart(object): - """ raw chart class """ - - GROUPING_CLUSTERED = 'clustered' - GROUPING_STANDARD = 'standard' - - BAR_CHART = 1 - LINE_CHART = 2 - SCATTER_CHART = 3 - - def __init__(self, _type, grouping): - - self._series = [] - - # public api - self.type = _type - self.grouping = grouping - self.x_axis = Axis.default_category() - self.y_axis = Axis.default_value() - self.legend = Legend() - self.lang = 'fr-FR' - self.title = '' - self.print_margins = dict(b=.75, l=.7, r=.7, t=.75, header=0.3, footer=.3) - - # the containing drawing - self.drawing = Drawing() - - # the offset for the plot part in percentage of the drawing size - self.width = .6 - self.height = .6 - self.margin_top = self._get_max_margin_top() - self.margin_left = 0 - - # the user defined shapes - self._shapes = [] - - def add_serie(self, serie): - - serie.id = len(self._series) - self._series.append(serie) - self._compute_min_max() - if not None in [s.xvalues for s in self._series]: - self._compute_xmin_xmax() - - def add_shape(self, shape): - - shape._chart = self - self._shapes.append(shape) - - def get_x_units(self): - """ calculate one unit for x axis in EMU """ - - return max([len(s.values._get_cache()) for s in self._series]) - - def get_y_units(self): - """ calculate one unit for y axis in EMU """ - - dh = pixels_to_EMU(self.drawing.height) - return (dh * self.height) / self.y_axis.max - - def get_y_chars(self): - """ estimate nb of chars for y axis """ - - _max = max([max([x for x in s.values._get_cache() if x is not None]) for s in self._series]) - return len(str(int(_max))) - - def _compute_min_max(self): - """ compute y axis limits and units """ - - maxi = max([max([x for x in s.values._get_cache() if x is not None]) for s in self._series]) - - mul = None - if maxi < 1: - s = str(maxi).split('.')[1] - mul = 10 - for x in s: - if x == '0': - mul *= 10 - else: - break - maxi = maxi * mul - - maxi = math.ceil(maxi * 1.1) - sz = len(str(int(maxi))) - 1 - unit = math.ceil(math.ceil(maxi / pow(10, sz)) * pow(10, sz-1)) - maxi = math.ceil(maxi/unit) * unit - - if mul is not None: - maxi = maxi/mul - unit = unit/mul - - if maxi / unit > 9: - # no more that 10 ticks - unit *= 2 - - self.y_axis.max = maxi - self.y_axis.unit = unit - - def _compute_xmin_xmax(self): - """ compute x axis limits and units """ - - maxi = max([max([x for x in s.xvalues._get_cache() if x is not None]) for s in self._series]) - - mul = None - if maxi < 1: - s = str(maxi).split('.')[1] - mul = 10 - for x in s: - if x == '0': - mul *= 10 - else: - break - maxi = maxi * mul - - maxi = math.ceil(maxi * 1.1) - sz = len(str(int(maxi))) - 1 - unit = math.ceil(math.ceil(maxi / pow(10, sz)) * pow(10, sz-1)) - maxi = math.ceil(maxi/unit) * unit - - if mul is not None: - maxi = maxi/mul - unit = unit/mul - - if maxi / unit > 9: - # no more that 10 ticks - unit *= 2 - - self.x_axis.max = maxi - self.x_axis.unit = unit - - def _get_max_margin_top(self): - - mb = Shape.FONT_HEIGHT + Shape.MARGIN_BOTTOM - plot_height = self.drawing.height * self.height - return float(self.drawing.height - plot_height - mb)/self.drawing.height - - def _get_min_margin_left(self): - - ml = (self.get_y_chars() * Shape.FONT_WIDTH) + Shape.MARGIN_LEFT - return float(ml)/self.drawing.width - - def _get_margin_top(self): - """ get margin in percent """ - - return min(self.margin_top, self._get_max_margin_top()) - - def _get_margin_left(self): - - return max(self._get_min_margin_left(), self.margin_left) - -class BarChart(Chart): - def __init__(self): - super(BarChart, self).__init__(Chart.BAR_CHART, Chart.GROUPING_CLUSTERED) - -class LineChart(Chart): - def __init__(self): - super(LineChart, self).__init__(Chart.LINE_CHART, Chart.GROUPING_STANDARD) - -class ScatterChart(Chart): - def __init__(self): - super(ScatterChart, self).__init__(Chart.SCATTER_CHART, Chart.GROUPING_STANDARD) - - +''' +Copyright (c) 2010 openpyxl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license: http://www.opensource.org/licenses/mit-license.php +@author: Eric Gazoni +''' + +import math + +from .style import NumberFormat +from .drawing import Drawing, Shape +from .shared.units import pixels_to_EMU, short_color +from .cell import get_column_letter + +class Axis(object): + + POSITION_BOTTOM = 'b' + POSITION_LEFT = 'l' + + ORIENTATION_MIN_MAX = "minMax" + + def __init__(self): + + self.orientation = self.ORIENTATION_MIN_MAX + self.number_format = NumberFormat() + for attr in ('position','tick_label_position','crosses', + 'auto','label_align','label_offset','cross_between'): + setattr(self, attr, None) + self.min = 0 + self.max = None + self.unit = None + + @classmethod + def default_category(cls): + """ default values for category axes """ + + ax = Axis() + ax.id = 60871424 + ax.cross = 60873344 + ax.position = Axis.POSITION_BOTTOM + ax.tick_label_position = 'nextTo' + ax.crosses = "autoZero" + ax.auto = True + ax.label_align = 'ctr' + ax.label_offset = 100 + return ax + + @classmethod + def default_value(cls): + """ default values for value axes """ + + ax = Axis() + ax.id = 60873344 + ax.cross = 60871424 + ax.position = Axis.POSITION_LEFT + ax.major_gridlines = None + ax.tick_label_position = 'nextTo' + ax.crosses = 'autoZero' + ax.auto = False + ax.cross_between = 'between' + return ax + +class Reference(object): + """ a simple wrapper around a serie of reference data """ + + def __init__(self, sheet, pos1, pos2=None): + + self.sheet = sheet + self.pos1 = pos1 + self.pos2 = pos2 + + def get_type(self): + + if isinstance(self.cache[0], str): + return 'str' + else: + return 'num' + + def _get_ref(self): + """ format excel reference notation """ + + if self.pos2: + return '%s!$%s$%s:$%s$%s' % (self.sheet.title, + get_column_letter(self.pos1[1]+1), self.pos1[0]+1, + get_column_letter(self.pos2[1]+1), self.pos2[0]+1) + else: + return '%s!$%s$%s' % (self.sheet.title, + get_column_letter(self.pos1[1]+1), self.pos1[0]+1) + + + def _get_cache(self): + """ read data in sheet - to be used at writing time """ + + cache = [] + if self.pos2: + for row in range(self.pos1[0], self.pos2[0]+1): + for col in range(self.pos1[1], self.pos2[1]+1): + cache.append(self.sheet.cell(row=row, column=col).value) + else: + cell = self.sheet.cell(row=self.pos1[0], column=self.pos1[1]) + cache.append(cell.value) + return cache + + +class Serie(object): + """ a serie of data and possibly associated labels """ + + MARKER_NONE = 'none' + + def __init__(self, values, labels=None, legend=None, color=None, xvalues=None): + + self.marker = Serie.MARKER_NONE + self.values = values + self.xvalues = xvalues + self.labels = labels + self.legend = legend + self.error_bar = None + self._color = color + + def _get_color(self): + return self._color + + def _set_color(self, color): + self._color = short_color(color) + + color = property(_get_color, _set_color) + + def get_min_max(self): + + if self.error_bar: + err_cache = self.error_bar.values._get_cache() + vals = [v + err_cache[i] \ + for i,v in enumerate(self.values._get_cache())] + else: + vals = self.values._get_cache() + return min(vals), max(vals) + + def __len__(self): + + return len(self.values.cache) + +class Legend(object): + + def __init__(self): + + self.position = 'r' + self.layout = None + +class ErrorBar(object): + + PLUS = 1 + MINUS = 2 + PLUS_MINUS = 3 + + def __init__(self, _type, values): + + self.type = _type + self.values = values + +class Chart(object): + """ raw chart class """ + + GROUPING_CLUSTERED = 'clustered' + GROUPING_STANDARD = 'standard' + + BAR_CHART = 1 + LINE_CHART = 2 + SCATTER_CHART = 3 + + def __init__(self, _type, grouping): + + self._series = [] + + # public api + self.type = _type + self.grouping = grouping + self.x_axis = Axis.default_category() + self.y_axis = Axis.default_value() + self.legend = Legend() + self.lang = 'fr-FR' + self.title = '' + self.print_margins = dict(b=.75, l=.7, r=.7, t=.75, header=0.3, footer=.3) + + # the containing drawing + self.drawing = Drawing() + + # the offset for the plot part in percentage of the drawing size + self.width = .6 + self.height = .6 + self.margin_top = self._get_max_margin_top() + self.margin_left = 0 + + # the user defined shapes + self._shapes = [] + + def add_serie(self, serie): + + serie.id = len(self._series) + self._series.append(serie) + self._compute_min_max() + if not None in [s.xvalues for s in self._series]: + self._compute_xmin_xmax() + + def add_shape(self, shape): + + shape._chart = self + self._shapes.append(shape) + + def get_x_units(self): + """ calculate one unit for x axis in EMU """ + + return max([len(s.values._get_cache()) for s in self._series]) + + def get_y_units(self): + """ calculate one unit for y axis in EMU """ + + dh = pixels_to_EMU(self.drawing.height) + return (dh * self.height) / self.y_axis.max + + def get_y_chars(self): + """ estimate nb of chars for y axis """ + + _max = max([max(s.values._get_cache()) for s in self._series]) + return len(str(int(_max))) + + def _compute_min_max(self): + """ compute y axis limits and units """ + + maxi = max([max(s.values._get_cache()) for s in self._series]) + + mul = None + if maxi < 1: + s = str(maxi).split('.')[1] + mul = 10 + for x in s: + if x == '0': + mul *= 10 + else: + break + maxi = maxi * mul + + maxi = math.ceil(maxi * 1.1) + sz = len(str(int(maxi))) - 1 + unit = math.ceil(math.ceil(maxi / pow(10, sz)) * pow(10, sz-1)) + maxi = math.ceil(maxi/unit) * unit + + if mul is not None: + maxi = maxi/mul + unit = unit/mul + + if maxi / unit > 9: + # no more that 10 ticks + unit *= 2 + + self.y_axis.max = maxi + self.y_axis.unit = unit + + def _compute_xmin_xmax(self): + """ compute x axis limits and units """ + + maxi = max([max(s.xvalues._get_cache()) for s in self._series]) + + mul = None + if maxi < 1: + s = str(maxi).split('.')[1] + mul = 10 + for x in s: + if x == '0': + mul *= 10 + else: + break + maxi = maxi * mul + + maxi = math.ceil(maxi * 1.1) + sz = len(str(int(maxi))) - 1 + unit = math.ceil(math.ceil(maxi / pow(10, sz)) * pow(10, sz-1)) + maxi = math.ceil(maxi/unit) * unit + + if mul is not None: + maxi = maxi/mul + unit = unit/mul + + if maxi / unit > 9: + # no more that 10 ticks + unit *= 2 + + self.x_axis.max = maxi + self.x_axis.unit = unit + + def _get_max_margin_top(self): + + mb = Shape.FONT_HEIGHT + Shape.MARGIN_BOTTOM + plot_height = self.drawing.height * self.height + return float(self.drawing.height - plot_height - mb)/self.drawing.height + + def _get_min_margin_left(self): + + ml = (self.get_y_chars() * Shape.FONT_WIDTH) + Shape.MARGIN_LEFT + return float(ml)/self.drawing.width + + def _get_margin_top(self): + """ get margin in percent """ + + return min(self.margin_top, self._get_max_margin_top()) + + def _get_margin_left(self): + + return max(self._get_min_margin_left(), self.margin_left) + +class BarChart(Chart): + def __init__(self): + super(BarChart, self).__init__(Chart.BAR_CHART, Chart.GROUPING_CLUSTERED) + +class LineChart(Chart): + def __init__(self): + super(LineChart, self).__init__(Chart.LINE_CHART, Chart.GROUPING_STANDARD) + +class ScatterChart(Chart): + def __init__(self): + super(ScatterChart, self).__init__(Chart.SCATTER_CHART, Chart.GROUPING_STANDARD) + + diff --git a/tablib/packages/openpyxl3/drawing.py b/tablib/packages/openpyxl3/drawing.py index 6b3c76d..0007569 100644 --- a/tablib/packages/openpyxl3/drawing.py +++ b/tablib/packages/openpyxl3/drawing.py @@ -1,402 +1,402 @@ -''' -Copyright (c) 2010 openpyxl - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -@license: http://www.opensource.org/licenses/mit-license.php -@author: Eric Gazoni -''' - -import math -from .style import Color -from .shared.units import pixels_to_EMU, EMU_to_pixels, short_color - -class Shadow(object): - - SHADOW_BOTTOM = 'b' - SHADOW_BOTTOM_LEFT = 'bl' - SHADOW_BOTTOM_RIGHT = 'br' - SHADOW_CENTER = 'ctr' - SHADOW_LEFT = 'l' - SHADOW_TOP = 't' - SHADOW_TOP_LEFT = 'tl' - SHADOW_TOP_RIGHT = 'tr' - - def __init__(self): - self.visible = False - self.blurRadius = 6 - self.distance = 2 - self.direction = 0 - self.alignment = self.SHADOW_BOTTOM_RIGHT - self.color = Color(Color.BLACK) - self.alpha = 50 - -class Drawing(object): - """ a drawing object - eg container for shapes or charts - we assume user specifies dimensions in pixels; units are - converted to EMU in the drawing part - """ - - count = 0 - - def __init__(self): - - self.name = '' - self.description = '' - self.coordinates = ((1,2), (16,8)) - self.left = 0 - self.top = 0 - self._width = EMU_to_pixels(200000) - self._height = EMU_to_pixels(1828800) - self.resize_proportional = False - self.rotation = 0 -# self.shadow = Shadow() - - def _set_width(self, w): - - if self.resize_proportional and w: - ratio = self._height / self._width - self._height = round(ratio * w) - self._width = w - - def _get_width(self): - - return self._width - - width = property(_get_width, _set_width) - - def _set_height(self, h): - - if self.resize_proportional and h: - ratio = self._width / self._height - self._width = round(ratio * h) - self._height = h - - def _get_height(self): - - return self._height - - height = property(_get_height, _set_height) - - def set_dimension(self, w=0, h=0): - - xratio = w / self._width - yratio = h / self._height - - if self.resize_proportional and w and h: - if (xratio * self._height) < h: - self._height = math.ceil(xratio * self._height) - self._width = width - else: - self._width = math.ceil(yratio * self._width) - self._height = height - - def get_emu_dimensions(self): - """ return (x, y, w, h) in EMU """ - - return (pixels_to_EMU(self.left), pixels_to_EMU(self.top), - pixels_to_EMU(self._width), pixels_to_EMU(self._height)) - - -class Shape(object): - """ a drawing inside a chart - coordiantes are specified by the user in the axis units - """ - - MARGIN_LEFT = 6 + 13 + 1 - MARGIN_BOTTOM = 17 + 11 - - FONT_WIDTH = 7 - FONT_HEIGHT = 8 - - ROUND_RECT = 'roundRect' - RECT = 'rect' - - # other shapes to define : - ''' - "line" - "lineInv" - "triangle" - "rtTriangle" - "diamond" - "parallelogram" - "trapezoid" - "nonIsoscelesTrapezoid" - "pentagon" - "hexagon" - "heptagon" - "octagon" - "decagon" - "dodecagon" - "star4" - "star5" - "star6" - "star7" - "star8" - "star10" - "star12" - "star16" - "star24" - "star32" - "roundRect" - "round1Rect" - "round2SameRect" - "round2DiagRect" - "snipRoundRect" - "snip1Rect" - "snip2SameRect" - "snip2DiagRect" - "plaque" - "ellipse" - "teardrop" - "homePlate" - "chevron" - "pieWedge" - "pie" - "blockArc" - "donut" - "noSmoking" - "rightArrow" - "leftArrow" - "upArrow" - "downArrow" - "stripedRightArrow" - "notchedRightArrow" - "bentUpArrow" - "leftRightArrow" - "upDownArrow" - "leftUpArrow" - "leftRightUpArrow" - "quadArrow" - "leftArrowCallout" - "rightArrowCallout" - "upArrowCallout" - "downArrowCallout" - "leftRightArrowCallout" - "upDownArrowCallout" - "quadArrowCallout" - "bentArrow" - "uturnArrow" - "circularArrow" - "leftCircularArrow" - "leftRightCircularArrow" - "curvedRightArrow" - "curvedLeftArrow" - "curvedUpArrow" - "curvedDownArrow" - "swooshArrow" - "cube" - "can" - "lightningBolt" - "heart" - "sun" - "moon" - "smileyFace" - "irregularSeal1" - "irregularSeal2" - "foldedCorner" - "bevel" - "frame" - "halfFrame" - "corner" - "diagStripe" - "chord" - "arc" - "leftBracket" - "rightBracket" - "leftBrace" - "rightBrace" - "bracketPair" - "bracePair" - "straightConnector1" - "bentConnector2" - "bentConnector3" - "bentConnector4" - "bentConnector5" - "curvedConnector2" - "curvedConnector3" - "curvedConnector4" - "curvedConnector5" - "callout1" - "callout2" - "callout3" - "accentCallout1" - "accentCallout2" - "accentCallout3" - "borderCallout1" - "borderCallout2" - "borderCallout3" - "accentBorderCallout1" - "accentBorderCallout2" - "accentBorderCallout3" - "wedgeRectCallout" - "wedgeRoundRectCallout" - "wedgeEllipseCallout" - "cloudCallout" - "cloud" - "ribbon" - "ribbon2" - "ellipseRibbon" - "ellipseRibbon2" - "leftRightRibbon" - "verticalScroll" - "horizontalScroll" - "wave" - "doubleWave" - "plus" - "flowChartProcess" - "flowChartDecision" - "flowChartInputOutput" - "flowChartPredefinedProcess" - "flowChartInternalStorage" - "flowChartDocument" - "flowChartMultidocument" - "flowChartTerminator" - "flowChartPreparation" - "flowChartManualInput" - "flowChartManualOperation" - "flowChartConnector" - "flowChartPunchedCard" - "flowChartPunchedTape" - "flowChartSummingJunction" - "flowChartOr" - "flowChartCollate" - "flowChartSort" - "flowChartExtract" - "flowChartMerge" - "flowChartOfflineStorage" - "flowChartOnlineStorage" - "flowChartMagneticTape" - "flowChartMagneticDisk" - "flowChartMagneticDrum" - "flowChartDisplay" - "flowChartDelay" - "flowChartAlternateProcess" - "flowChartOffpageConnector" - "actionButtonBlank" - "actionButtonHome" - "actionButtonHelp" - "actionButtonInformation" - "actionButtonForwardNext" - "actionButtonBackPrevious" - "actionButtonEnd" - "actionButtonBeginning" - "actionButtonReturn" - "actionButtonDocument" - "actionButtonSound" - "actionButtonMovie" - "gear6" - "gear9" - "funnel" - "mathPlus" - "mathMinus" - "mathMultiply" - "mathDivide" - "mathEqual" - "mathNotEqual" - "cornerTabs" - "squareTabs" - "plaqueTabs" - "chartX" - "chartStar" - "chartPlus" - ''' - - def __init__(self, coordinates=((0,0), (1,1)), text=None, scheme="accent1"): - - self.coordinates = coordinates # in axis unit - self.text = text - self.scheme = scheme - self.style = Shape.RECT - self._border_width = 3175 # in EMU - self._border_color = Color.BLACK[2:] #"F3B3C5" - self._color = Color.WHITE[2:] - self._text_color = Color.BLACK[2:] - - def _get_border_color(self): - return self._border_color - - def _set_border_color(self, color): - self._border_color = short_color(color) - - border_color = property(_get_border_color, _set_border_color) - - def _get_color(self): - return self._color - - def _set_color(self, color): - self._color = short_color(color) - - color = property(_get_color, _set_color) - - def _get_text_color(self): - return self._text_color - - def _set_text_color(self, color): - self._text_color = short_color(color) - - text_color = property(_get_text_color, _set_text_color) - - def _get_border_width(self): - - return EMU_to_pixels(self._border_width) - - def _set_border_width(self, w): - - self._border_width = pixels_to_EMU(w) - print(self._border_width) - - border_width = property(_get_border_width, _set_border_width) - - def get_coordinates(self): - """ return shape coordinates in percentages (left, top, right, bottom) """ - - (x1, y1), (x2, y2) = self.coordinates - - drawing_width = pixels_to_EMU(self._chart.drawing.width) - drawing_height = pixels_to_EMU(self._chart.drawing.height) - plot_width = drawing_width * self._chart.width - plot_height = drawing_height * self._chart.height - - margin_left = self._chart._get_margin_left() * drawing_width - xunit = plot_width / self._chart.get_x_units() - - margin_top = self._chart._get_margin_top() * drawing_height - yunit = self._chart.get_y_units() - - x_start = (margin_left + (float(x1) * xunit)) / drawing_width - y_start = (margin_top + plot_height - (float(y1) * yunit)) / drawing_height - - x_end = (margin_left + (float(x2) * xunit)) / drawing_width - y_end = (margin_top + plot_height - (float(y2) * yunit)) / drawing_height - - def _norm_pct(pct): - """ force shapes to appear by truncating too large sizes """ - if pct>1: pct = 1 - elif pct<0: pct = 0 - return pct - - # allow user to specify y's in whatever order - # excel expect y_end to be lower - if y_end < y_start: - y_end, y_start = y_start, y_end - - return (_norm_pct(x_start), _norm_pct(y_start), - _norm_pct(x_end), _norm_pct(y_end)) - +''' +Copyright (c) 2010 openpyxl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license: http://www.opensource.org/licenses/mit-license.php +@author: Eric Gazoni +''' + +import math +from .style import Color +from .shared.units import pixels_to_EMU, EMU_to_pixels, short_color + +class Shadow(object): + + SHADOW_BOTTOM = 'b' + SHADOW_BOTTOM_LEFT = 'bl' + SHADOW_BOTTOM_RIGHT = 'br' + SHADOW_CENTER = 'ctr' + SHADOW_LEFT = 'l' + SHADOW_TOP = 't' + SHADOW_TOP_LEFT = 'tl' + SHADOW_TOP_RIGHT = 'tr' + + def __init__(self): + self.visible = False + self.blurRadius = 6 + self.distance = 2 + self.direction = 0 + self.alignment = self.SHADOW_BOTTOM_RIGHT + self.color = Color(Color.BLACK) + self.alpha = 50 + +class Drawing(object): + """ a drawing object - eg container for shapes or charts + we assume user specifies dimensions in pixels; units are + converted to EMU in the drawing part + """ + + count = 0 + + def __init__(self): + + self.name = '' + self.description = '' + self.coordinates = ((1,2), (16,8)) + self.left = 0 + self.top = 0 + self._width = EMU_to_pixels(200000) + self._height = EMU_to_pixels(1828800) + self.resize_proportional = False + self.rotation = 0 +# self.shadow = Shadow() + + def _set_width(self, w): + + if self.resize_proportional and w: + ratio = self._height / self._width + self._height = round(ratio * w) + self._width = w + + def _get_width(self): + + return self._width + + width = property(_get_width, _set_width) + + def _set_height(self, h): + + if self.resize_proportional and h: + ratio = self._width / self._height + self._width = round(ratio * h) + self._height = h + + def _get_height(self): + + return self._height + + height = property(_get_height, _set_height) + + def set_dimension(self, w=0, h=0): + + xratio = w / self._width + yratio = h / self._height + + if self.resize_proportional and w and h: + if (xratio * self._height) < h: + self._height = math.ceil(xratio * self._height) + self._width = width + else: + self._width = math.ceil(yratio * self._width) + self._height = height + + def get_emu_dimensions(self): + """ return (x, y, w, h) in EMU """ + + return (pixels_to_EMU(self.left), pixels_to_EMU(self.top), + pixels_to_EMU(self._width), pixels_to_EMU(self._height)) + + +class Shape(object): + """ a drawing inside a chart + coordiantes are specified by the user in the axis units + """ + + MARGIN_LEFT = 6 + 13 + 1 + MARGIN_BOTTOM = 17 + 11 + + FONT_WIDTH = 7 + FONT_HEIGHT = 8 + + ROUND_RECT = 'roundRect' + RECT = 'rect' + + # other shapes to define : + ''' + "line" + "lineInv" + "triangle" + "rtTriangle" + "diamond" + "parallelogram" + "trapezoid" + "nonIsoscelesTrapezoid" + "pentagon" + "hexagon" + "heptagon" + "octagon" + "decagon" + "dodecagon" + "star4" + "star5" + "star6" + "star7" + "star8" + "star10" + "star12" + "star16" + "star24" + "star32" + "roundRect" + "round1Rect" + "round2SameRect" + "round2DiagRect" + "snipRoundRect" + "snip1Rect" + "snip2SameRect" + "snip2DiagRect" + "plaque" + "ellipse" + "teardrop" + "homePlate" + "chevron" + "pieWedge" + "pie" + "blockArc" + "donut" + "noSmoking" + "rightArrow" + "leftArrow" + "upArrow" + "downArrow" + "stripedRightArrow" + "notchedRightArrow" + "bentUpArrow" + "leftRightArrow" + "upDownArrow" + "leftUpArrow" + "leftRightUpArrow" + "quadArrow" + "leftArrowCallout" + "rightArrowCallout" + "upArrowCallout" + "downArrowCallout" + "leftRightArrowCallout" + "upDownArrowCallout" + "quadArrowCallout" + "bentArrow" + "uturnArrow" + "circularArrow" + "leftCircularArrow" + "leftRightCircularArrow" + "curvedRightArrow" + "curvedLeftArrow" + "curvedUpArrow" + "curvedDownArrow" + "swooshArrow" + "cube" + "can" + "lightningBolt" + "heart" + "sun" + "moon" + "smileyFace" + "irregularSeal1" + "irregularSeal2" + "foldedCorner" + "bevel" + "frame" + "halfFrame" + "corner" + "diagStripe" + "chord" + "arc" + "leftBracket" + "rightBracket" + "leftBrace" + "rightBrace" + "bracketPair" + "bracePair" + "straightConnector1" + "bentConnector2" + "bentConnector3" + "bentConnector4" + "bentConnector5" + "curvedConnector2" + "curvedConnector3" + "curvedConnector4" + "curvedConnector5" + "callout1" + "callout2" + "callout3" + "accentCallout1" + "accentCallout2" + "accentCallout3" + "borderCallout1" + "borderCallout2" + "borderCallout3" + "accentBorderCallout1" + "accentBorderCallout2" + "accentBorderCallout3" + "wedgeRectCallout" + "wedgeRoundRectCallout" + "wedgeEllipseCallout" + "cloudCallout" + "cloud" + "ribbon" + "ribbon2" + "ellipseRibbon" + "ellipseRibbon2" + "leftRightRibbon" + "verticalScroll" + "horizontalScroll" + "wave" + "doubleWave" + "plus" + "flowChartProcess" + "flowChartDecision" + "flowChartInputOutput" + "flowChartPredefinedProcess" + "flowChartInternalStorage" + "flowChartDocument" + "flowChartMultidocument" + "flowChartTerminator" + "flowChartPreparation" + "flowChartManualInput" + "flowChartManualOperation" + "flowChartConnector" + "flowChartPunchedCard" + "flowChartPunchedTape" + "flowChartSummingJunction" + "flowChartOr" + "flowChartCollate" + "flowChartSort" + "flowChartExtract" + "flowChartMerge" + "flowChartOfflineStorage" + "flowChartOnlineStorage" + "flowChartMagneticTape" + "flowChartMagneticDisk" + "flowChartMagneticDrum" + "flowChartDisplay" + "flowChartDelay" + "flowChartAlternateProcess" + "flowChartOffpageConnector" + "actionButtonBlank" + "actionButtonHome" + "actionButtonHelp" + "actionButtonInformation" + "actionButtonForwardNext" + "actionButtonBackPrevious" + "actionButtonEnd" + "actionButtonBeginning" + "actionButtonReturn" + "actionButtonDocument" + "actionButtonSound" + "actionButtonMovie" + "gear6" + "gear9" + "funnel" + "mathPlus" + "mathMinus" + "mathMultiply" + "mathDivide" + "mathEqual" + "mathNotEqual" + "cornerTabs" + "squareTabs" + "plaqueTabs" + "chartX" + "chartStar" + "chartPlus" + ''' + + def __init__(self, coordinates=((0,0), (1,1)), text=None, scheme="accent1"): + + self.coordinates = coordinates # in axis unit + self.text = text + self.scheme = scheme + self.style = Shape.RECT + self._border_width = 3175 # in EMU + self._border_color = Color.BLACK[2:] #"F3B3C5" + self._color = Color.WHITE[2:] + self._text_color = Color.BLACK[2:] + + def _get_border_color(self): + return self._border_color + + def _set_border_color(self, color): + self._border_color = short_color(color) + + border_color = property(_get_border_color, _set_border_color) + + def _get_color(self): + return self._color + + def _set_color(self, color): + self._color = short_color(color) + + color = property(_get_color, _set_color) + + def _get_text_color(self): + return self._text_color + + def _set_text_color(self, color): + self._text_color = short_color(color) + + text_color = property(_get_text_color, _set_text_color) + + def _get_border_width(self): + + return EMU_to_pixels(self._border_width) + + def _set_border_width(self, w): + + self._border_width = pixels_to_EMU(w) + print(self._border_width) + + border_width = property(_get_border_width, _set_border_width) + + def get_coordinates(self): + """ return shape coordinates in percentages (left, top, right, bottom) """ + + (x1, y1), (x2, y2) = self.coordinates + + drawing_width = pixels_to_EMU(self._chart.drawing.width) + drawing_height = pixels_to_EMU(self._chart.drawing.height) + plot_width = drawing_width * self._chart.width + plot_height = drawing_height * self._chart.height + + margin_left = self._chart._get_margin_left() * drawing_width + xunit = plot_width / self._chart.get_x_units() + + margin_top = self._chart._get_margin_top() * drawing_height + yunit = self._chart.get_y_units() + + x_start = (margin_left + (float(x1) * xunit)) / drawing_width + y_start = (margin_top + plot_height - (float(y1) * yunit)) / drawing_height + + x_end = (margin_left + (float(x2) * xunit)) / drawing_width + y_end = (margin_top + plot_height - (float(y2) * yunit)) / drawing_height + + def _norm_pct(pct): + """ force shapes to appear by truncating too large sizes """ + if pct>1: pct = 1 + elif pct<0: pct = 0 + return pct + + # allow user to specify y's in whatever order + # excel expect y_end to be lower + if y_end < y_start: + y_end, y_start = y_start, y_end + + return (_norm_pct(x_start), _norm_pct(y_start), + _norm_pct(x_end), _norm_pct(y_end)) + \ No newline at end of file diff --git a/tablib/packages/openpyxl3/reader/__init__.py b/tablib/packages/openpyxl3/reader/__init__.py index 9b0ee2f..76f10f8 100644 --- a/tablib/packages/openpyxl3/reader/__init__.py +++ b/tablib/packages/openpyxl3/reader/__init__.py @@ -26,8 +26,8 @@ """Imports for the openpyxl.reader namespace.""" # package imports -from ..reader import excel -from ..reader import strings -from ..reader import style -from ..reader import workbook -from ..reader import worksheet +from . import excel +from . import strings +from . import style +from . import workbook +from . import worksheet diff --git a/tablib/packages/openpyxl3/reader/excel.py b/tablib/packages/openpyxl3/reader/excel.py index 698d292..3fee695 100644 --- a/tablib/packages/openpyxl3/reader/excel.py +++ b/tablib/packages/openpyxl3/reader/excel.py @@ -33,12 +33,12 @@ from ..shared.exc import OpenModeError, InvalidFileException from ..shared.ooxml import ARC_SHARED_STRINGS, ARC_CORE, ARC_APP, \ ARC_WORKBOOK, PACKAGE_WORKSHEETS, ARC_STYLE from ..workbook import Workbook -from ..reader.strings import read_string_table -from ..reader.style import read_style_table -from ..reader.workbook import read_sheets_titles, read_named_ranges, \ +from .strings import read_string_table +from .style import read_style_table +from .workbook import read_sheets_titles, read_named_ranges, \ read_properties_core, get_sheet_ids -from ..reader.worksheet import read_worksheet -from ..reader.iter_worksheet import unpack_worksheet +from .worksheet import read_worksheet +from .iter_worksheet import unpack_worksheet def load_workbook(filename, use_iterators = False): """Open the given filename and return the workbook @@ -49,11 +49,11 @@ def load_workbook(filename, use_iterators = False): :param use_iterators: use lazy load for cells :type use_iterators: bool - :rtype: :class:`openpyxl.workbook.Workbook` + :rtype: :class:`..workbook.Workbook` .. note:: - When using lazy load, all worksheets will be :class:`openpyxl.reader.iter_worksheet.IterableWorksheet` + When using lazy load, all worksheets will be :class:`.iter_worksheet.IterableWorksheet` and the returned workbook will be read-only. """ diff --git a/tablib/packages/openpyxl3/reader/iter_worksheet.py b/tablib/packages/openpyxl3/reader/iter_worksheet.py index 7bc9ed4..670e6b1 100644 --- a/tablib/packages/openpyxl3/reader/iter_worksheet.py +++ b/tablib/packages/openpyxl3/reader/iter_worksheet.py @@ -1,343 +1,343 @@ -# file openpyxl/reader/iter_worksheet.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -""" Iterators-based worksheet reader -*Still very raw* -""" - -from io import BytesIO as StringIO -import warnings -import operator -from functools import partial -from itertools import groupby -from ..worksheet import Worksheet -from ..cell import coordinate_from_string, get_column_letter, Cell -from ..reader.excel import get_sheet_ids -from ..reader.strings import read_string_table -from ..reader.style import read_style_table, NumberFormat -from ..shared.date_time import SharedDate -from ..reader.worksheet import read_dimension -from ..shared.ooxml import (MIN_COLUMN, MAX_COLUMN, PACKAGE_WORKSHEETS, - MAX_ROW, MIN_ROW, ARC_SHARED_STRINGS, ARC_APP, ARC_STYLE) -from xml.etree.cElementTree import iterparse -from zipfile import ZipFile -from .. import cell -import re -import tempfile -import zlib -import zipfile -import struct - -TYPE_NULL = Cell.TYPE_NULL -MISSING_VALUE = None - -RE_COORDINATE = re.compile('^([A-Z]+)([0-9]+)$') - -SHARED_DATE = SharedDate() - -_COL_CONVERSION_CACHE = dict((get_column_letter(i), i) for i in range(1, 18279)) -def column_index_from_string(str_col, _col_conversion_cache=_COL_CONVERSION_CACHE): - # we use a function argument to get indexed name lookup - return _col_conversion_cache[str_col] -del _COL_CONVERSION_CACHE - -RAW_ATTRIBUTES = ['row', 'column', 'coordinate', 'internal_value', 'data_type', 'style_id', 'number_format'] - -try: - from collections import namedtuple - BaseRawCell = namedtuple('RawCell', RAW_ATTRIBUTES) -except ImportError: - - warnings.warn("""Unable to import 'namedtuple' module, this may cause memory issues when using optimized reader. Please upgrade your Python installation to 2.6+""") - - class BaseRawCell(object): - - def __init__(self, *args): - assert len(args)==len(RAW_ATTRIBUTES) - - for attr, val in zip(RAW_ATTRIBUTES, args): - setattr(self, attr, val) - - def _replace(self, **kwargs): - - self.__dict__.update(kwargs) - - return self - - -class RawCell(BaseRawCell): - """Optimized version of the :class:`openpyxl.cell.Cell`, using named tuples. - - Useful attributes are: - - * row - * column - * coordinate - * internal_value - - You can also access if needed: - - * data_type - * number_format - - """ - - @property - def is_date(self): - res = (self.data_type == Cell.TYPE_NUMERIC - and self.number_format is not None - and ('d' in self.number_format - or 'm' in self.number_format - or 'y' in self.number_format - or 'h' in self.number_format - or 's' in self.number_format - )) - - return res - -def iter_rows(workbook_name, sheet_name, xml_source, range_string = '', row_offset = 0, column_offset = 0): - - archive = get_archive_file(workbook_name) - - source = xml_source - - if range_string: - min_col, min_row, max_col, max_row = get_range_boundaries(range_string, row_offset, column_offset) - else: - min_col, min_row, max_col, max_row = read_dimension(xml_source = source) - min_col = column_index_from_string(min_col) - max_col = column_index_from_string(max_col) + 1 - max_row += 6 - - try: - string_table = read_string_table(archive.read(ARC_SHARED_STRINGS)) - except KeyError: - string_table = {} - - style_table = read_style_table(archive.read(ARC_STYLE)) - - source.seek(0) - p = iterparse(source) - - return get_squared_range(p, min_col, min_row, max_col, max_row, string_table, style_table) - - -def get_rows(p, min_column = MIN_COLUMN, min_row = MIN_ROW, max_column = MAX_COLUMN, max_row = MAX_ROW): - - return groupby(get_cells(p, min_row, min_column, max_row, max_column), operator.attrgetter('row')) - -def get_cells(p, min_row, min_col, max_row, max_col, _re_coordinate=RE_COORDINATE): - - for _event, element in p: - - if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c': - coord = element.get('r') - column_str, row = _re_coordinate.match(coord).groups() - - row = int(row) - column = column_index_from_string(column_str) - - if min_col <= column <= max_col and min_row <= row <= max_row: - data_type = element.get('t', 'n') - style_id = element.get('s') - value = element.findtext('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v') - yield RawCell(row, column_str, coord, value, data_type, style_id, None) - - if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v': - continue - element.clear() - - - -def get_range_boundaries(range_string, row = 0, column = 0): - - if ':' in range_string: - min_range, max_range = range_string.split(':') - min_col, min_row = coordinate_from_string(min_range) - max_col, max_row = coordinate_from_string(max_range) - - min_col = column_index_from_string(min_col) + column - max_col = column_index_from_string(max_col) + column - min_row += row - max_row += row - - else: - min_col, min_row = coordinate_from_string(range_string) - min_col = column_index_from_string(min_col) - max_col = min_col + 1 - max_row = min_row - - return (min_col, min_row, max_col, max_row) - -def get_archive_file(archive_name): - - return ZipFile(archive_name, 'r') - -def get_xml_source(archive_file, sheet_name): - - return archive_file.read('%s/%s' % (PACKAGE_WORKSHEETS, sheet_name)) - -def get_missing_cells(row, columns): - - return dict([(column, RawCell(row, column, '%s%s' % (column, row), MISSING_VALUE, TYPE_NULL, None, None)) for column in columns]) - -def get_squared_range(p, min_col, min_row, max_col, max_row, string_table, style_table): - - expected_columns = [get_column_letter(ci) for ci in range(min_col, max_col)] - - current_row = min_row - for row, cells in get_rows(p, min_row = min_row, max_row = max_row, min_column = min_col, max_column = max_col): - full_row = [] - if current_row < row: - - for gap_row in range(current_row, row): - - dummy_cells = get_missing_cells(gap_row, expected_columns) - - yield tuple([dummy_cells[column] for column in expected_columns]) - - current_row = row - - temp_cells = list(cells) - - retrieved_columns = dict([(c.column, c) for c in temp_cells]) - - missing_columns = list(set(expected_columns) - set(retrieved_columns.keys())) - - replacement_columns = get_missing_cells(row, missing_columns) - - for column in expected_columns: - - if column in retrieved_columns: - cell = retrieved_columns[column] - - if cell.style_id is not None: - style = style_table[int(cell.style_id)] - cell = cell._replace(number_format = style.number_format.format_code) #pylint: disable-msg=W0212 - if cell.internal_value is not None: - if cell.data_type == Cell.TYPE_STRING: - cell = cell._replace(internal_value = string_table[int(cell.internal_value)]) #pylint: disable-msg=W0212 - elif cell.data_type == Cell.TYPE_BOOL: - cell = cell._replace(internal_value = cell.internal_value == 'True') - elif cell.is_date: - cell = cell._replace(internal_value = SHARED_DATE.from_julian(float(cell.internal_value))) - elif cell.data_type == Cell.TYPE_NUMERIC: - cell = cell._replace(internal_value = float(cell.internal_value)) - full_row.append(cell) - - else: - full_row.append(replacement_columns[column]) - - current_row = row + 1 - - yield tuple(full_row) - -#------------------------------------------------------------------------------ - -class IterableWorksheet(Worksheet): - - def __init__(self, parent_workbook, title, workbook_name, - sheet_codename, xml_source): - - Worksheet.__init__(self, parent_workbook, title) - self._workbook_name = workbook_name - self._sheet_codename = sheet_codename - self._xml_source = xml_source - - def iter_rows(self, range_string = '', row_offset = 0, column_offset = 0): - """ Returns a squared range based on the `range_string` parameter, - using generators. - - :param range_string: range of cells (e.g. 'A1:C4') - :type range_string: string - - :param row: row index of the cell (e.g. 4) - :type row: int - - :param column: column index of the cell (e.g. 3) - :type column: int - - :rtype: generator - - """ - - return iter_rows(workbook_name = self._workbook_name, - sheet_name = self._sheet_codename, - xml_source = self._xml_source, - range_string = range_string, - row_offset = row_offset, - column_offset = column_offset) - - def cell(self, *args, **kwargs): - - raise NotImplementedError("use 'iter_rows()' instead") - - def range(self, *args, **kwargs): - - raise NotImplementedError("use 'iter_rows()' instead") - -def unpack_worksheet(archive, filename): - - temp_file = tempfile.TemporaryFile(mode='r+', prefix='openpyxl.', suffix='.unpack.temp') - - zinfo = archive.getinfo(filename) - - if zinfo.compress_type == zipfile.ZIP_STORED: - decoder = None - elif zinfo.compress_type == zipfile.ZIP_DEFLATED: - decoder = zlib.decompressobj(-zlib.MAX_WBITS) - else: - raise zipfile.BadZipFile("Unrecognized compression method") - - archive.fp.seek(_get_file_offset(archive, zinfo)) - bytes_to_read = zinfo.compress_size - - while True: - buff = archive.fp.read(min(bytes_to_read, 102400)) - if not buff: - break - bytes_to_read -= len(buff) - if decoder: - buff = decoder.decompress(buff) - temp_file.write(buff) - - if decoder: - temp_file.write(decoder.decompress('Z')) - - return temp_file - -def _get_file_offset(archive, zinfo): - - try: - return zinfo.file_offset - except AttributeError: - # From http://stackoverflow.com/questions/3781261/how-to-simulate-zipfile-open-in-python-2-5 - - # Seek over the fixed size fields to the "file name length" field in - # the file header (26 bytes). Unpack this and the "extra field length" - # field ourselves as info.extra doesn't seem to be the correct length. - archive.fp.seek(zinfo.header_offset + 26) - file_name_len, extra_len = struct.unpack("TITLE') - - def test_write_xaxis(self): - - self.cw._write_axis(self.root, self.chart.x_axis, 'c:catAx') - eq_(get_xml(self.root), '') - - def test_write_yaxis(self): - - self.cw._write_axis(self.root, self.chart.y_axis, 'c:valAx') - eq_(get_xml(self.root), '') - - def test_write_series(self): - - self.cw._write_series(self.root) - eq_(get_xml(self.root), 'data!$A$1:$A$11General0123456789None') - - def test_write_legend(self): - - self.cw._write_legend(self.root) - eq_(get_xml(self.root), '') - - def test_write_print_settings(self): - - self.cw._write_print_settings(self.root) - eq_(get_xml(self.root), '') - - def test_write_chart(self): - - self.cw._write_chart(self.root) - eq_(get_xml(self.root), 'TITLEdata!$A$1:$A$11General0123456789None') - - -class TestScatterChartWriter(object): - - def setUp(self): - - wb = Workbook() - ws = wb.get_active_sheet() - ws.title = 'data' - for i in range(10): - ws.cell(row = i, column = 0).value = i - ws.cell(row = i, column = 1).value = i - self.scatterchart = ScatterChart() - self.scatterchart.add_serie(Serie(Reference(ws, (0, 0), (10, 0)), - xvalues = Reference(ws, (0, 1), (10, 1)))) - self.cw = ChartWriter(self.scatterchart) - self.root = Element('test') - - def test_write_xaxis(self): - - self.cw._write_axis(self.root, self.scatterchart.x_axis, 'c:valAx') - eq_(get_xml(self.root), '') - - - def test_write_yaxis(self): - - self.cw._write_axis(self.root, self.scatterchart.y_axis, 'c:valAx') - eq_(get_xml(self.root), '') - - def test_write_series(self): - - self.cw._write_series(self.root) - eq_(get_xml(self.root), 'data!$B$1:$B$11General0123456789Nonedata!$A$1:$A$11General0123456789None') - - def test_write_legend(self): - - self.cw._write_legend(self.root) - eq_(get_xml(self.root), '') - - def test_write_print_settings(self): - - self.cw._write_print_settings(self.root) - eq_(get_xml(self.root), '') - - def test_write_chart(self): - - self.cw._write_chart(self.root) - eq_(get_xml(self.root), 'data!$B$1:$B$11General0123456789Nonedata!$A$1:$A$11General0123456789None') +# file openpyxl/tests/test_chart.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +from nose.tools import eq_ + +from openpyxl.tests.helper import get_xml +from openpyxl.shared.xmltools import Element +from openpyxl.writer.charts import ChartWriter +from openpyxl.workbook import Workbook +from openpyxl.chart import BarChart, ScatterChart, Serie, Reference +from openpyxl.style import Color + +class TestChartWriter(object): + + def setUp(self): + + wb = Workbook() + ws = wb.get_active_sheet() + ws.title = 'data' + for i in range(10): + ws.cell(row = i, column = 0).value = i + self.chart = BarChart() + self.chart.title = 'TITLE' + self.chart.add_serie(Serie(Reference(ws, (0, 0), (10, 0)))) + self.chart._series[-1].color = Color.GREEN + self.cw = ChartWriter(self.chart) + self.root = Element('test') + + def test_write_title(self): + self.cw._write_title(self.root) + eq_(get_xml(self.root), 'TITLE') + + def test_write_xaxis(self): + + self.cw._write_axis(self.root, self.chart.x_axis, 'c:catAx') + eq_(get_xml(self.root), '') + + def test_write_yaxis(self): + + self.cw._write_axis(self.root, self.chart.y_axis, 'c:valAx') + eq_(get_xml(self.root), '') + + def test_write_series(self): + + self.cw._write_series(self.root) + eq_(get_xml(self.root), 'data!$A$1:$A$11General0123456789None') + + def test_write_legend(self): + + self.cw._write_legend(self.root) + eq_(get_xml(self.root), '') + + def test_write_print_settings(self): + + self.cw._write_print_settings(self.root) + eq_(get_xml(self.root), '') + + def test_write_chart(self): + + self.cw._write_chart(self.root) + eq_(get_xml(self.root), 'TITLEdata!$A$1:$A$11General0123456789None') + + +class TestScatterChartWriter(object): + + def setUp(self): + + wb = Workbook() + ws = wb.get_active_sheet() + ws.title = 'data' + for i in range(10): + ws.cell(row = i, column = 0).value = i + ws.cell(row = i, column = 1).value = i + self.scatterchart = ScatterChart() + self.scatterchart.add_serie(Serie(Reference(ws, (0, 0), (10, 0)), + xvalues = Reference(ws, (0, 1), (10, 1)))) + self.cw = ChartWriter(self.scatterchart) + self.root = Element('test') + + def test_write_xaxis(self): + + self.cw._write_axis(self.root, self.scatterchart.x_axis, 'c:valAx') + eq_(get_xml(self.root), '') + + + def test_write_yaxis(self): + + self.cw._write_axis(self.root, self.scatterchart.y_axis, 'c:valAx') + eq_(get_xml(self.root), '') + + def test_write_series(self): + + self.cw._write_series(self.root) + eq_(get_xml(self.root), 'data!$B$1:$B$11General0123456789Nonedata!$A$1:$A$11General0123456789None') + + def test_write_legend(self): + + self.cw._write_legend(self.root) + eq_(get_xml(self.root), '') + + def test_write_print_settings(self): + + self.cw._write_print_settings(self.root) + eq_(get_xml(self.root), '') + + def test_write_chart(self): + + self.cw._write_chart(self.root) + eq_(get_xml(self.root), 'data!$B$1:$B$11General0123456789Nonedata!$A$1:$A$11General0123456789None') diff --git a/tablib/packages/openpyxl3/tests/test_iter.py b/tablib/packages/openpyxl3/tests/test_iter.py index b34ab95..899f8d7 100644 --- a/tablib/packages/openpyxl3/tests/test_iter.py +++ b/tablib/packages/openpyxl3/tests/test_iter.py @@ -1,112 +1,112 @@ -# file openpyxl/tests/test_iter.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -from nose.tools import eq_, raises, assert_raises -import os.path as osp -from openpyxl.tests.helper import DATADIR -from openpyxl.reader.iter_worksheet import get_range_boundaries -from openpyxl.reader.excel import load_workbook -import datetime - -class TestWorksheet(object): - - workbook_name = osp.join(DATADIR, 'genuine', 'empty.xlsx') - -class TestText(TestWorksheet): - sheet_name = 'Sheet1 - Text' - - expected = [['This is cell A1 in Sheet 1', None, None, None, None, None, None], - [None, None, None, None, None, None, None], - [None, None, None, None, None, None, None], - [None, None, None, None, None, None, None], - [None, None, None, None, None, None, 'This is cell G5'], ] - - def test_read_fast_integrated(self): - - wb = load_workbook(filename = self.workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = self.sheet_name) - - for row, expected_row in zip(ws.iter_rows(), self.expected): - - row_values = [x.internal_value for x in row] - - eq_(row_values, expected_row) - - - def test_get_boundaries_range(self): - - eq_(get_range_boundaries('C1:C4'), (3, 1, 3, 4)) - - def test_get_boundaries_one(self): - - - eq_(get_range_boundaries('C1'), (3, 1, 4, 1)) - - def test_read_single_cell_range(self): - - wb = load_workbook(filename = self.workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = self.sheet_name) - - eq_('This is cell A1 in Sheet 1', list(ws.iter_rows('A1'))[0][0].internal_value) - -class TestIntegers(TestWorksheet): - - sheet_name = 'Sheet2 - Numbers' - - expected = [[x + 1] for x in range(30)] - - query_range = 'D1:E30' - - def test_read_fast_integrated(self): - - wb = load_workbook(filename = self.workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = self.sheet_name) - - for row, expected_row in zip(ws.iter_rows(self.query_range), self.expected): - - row_values = [x.internal_value for x in row] - - eq_(row_values, expected_row) - -class TestFloats(TestWorksheet): - - sheet_name = 'Sheet2 - Numbers' - query_range = 'K1:L30' - expected = expected = [[(x + 1) / 100.0] for x in range(30)] - -class TestDates(TestWorksheet): - - sheet_name = 'Sheet4 - Dates' - - def test_read_single_cell_date(self): - - wb = load_workbook(filename = self.workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = self.sheet_name) - - eq_(datetime.datetime(1973, 5, 20), list(ws.iter_rows('A1'))[0][0].internal_value) - eq_(datetime.datetime(1973, 5, 20, 9, 15, 2), list(ws.iter_rows('C1'))[0][0].internal_value) - - - +# file openpyxl/tests/test_iter.py + +# Copyright (c) 2010 openpyxl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# @license: http://www.opensource.org/licenses/mit-license.php +# @author: Eric Gazoni + +from nose.tools import eq_, raises, assert_raises +import os.path as osp +from openpyxl.tests.helper import DATADIR +from openpyxl.reader.iter_worksheet import get_range_boundaries +from openpyxl.reader.excel import load_workbook +import datetime + +class TestWorksheet(object): + + workbook_name = osp.join(DATADIR, 'genuine', 'empty.xlsx') + +class TestText(TestWorksheet): + sheet_name = 'Sheet1 - Text' + + expected = [['This is cell A1 in Sheet 1', None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, 'This is cell G5'], ] + + def test_read_fast_integrated(self): + + wb = load_workbook(filename = self.workbook_name, use_iterators = True) + ws = wb.get_sheet_by_name(name = self.sheet_name) + + for row, expected_row in zip(ws.iter_rows(), self.expected): + + row_values = [x.internal_value for x in row] + + eq_(row_values, expected_row) + + + def test_get_boundaries_range(self): + + eq_(get_range_boundaries('C1:C4'), (3, 1, 3, 4)) + + def test_get_boundaries_one(self): + + + eq_(get_range_boundaries('C1'), (3, 1, 4, 1)) + + def test_read_single_cell_range(self): + + wb = load_workbook(filename = self.workbook_name, use_iterators = True) + ws = wb.get_sheet_by_name(name = self.sheet_name) + + eq_('This is cell A1 in Sheet 1', list(ws.iter_rows('A1'))[0][0].internal_value) + +class TestIntegers(TestWorksheet): + + sheet_name = 'Sheet2 - Numbers' + + expected = [[x + 1] for x in range(30)] + + query_range = 'D1:E30' + + def test_read_fast_integrated(self): + + wb = load_workbook(filename = self.workbook_name, use_iterators = True) + ws = wb.get_sheet_by_name(name = self.sheet_name) + + for row, expected_row in zip(ws.iter_rows(self.query_range), self.expected): + + row_values = [x.internal_value for x in row] + + eq_(row_values, expected_row) + +class TestFloats(TestWorksheet): + + sheet_name = 'Sheet2 - Numbers' + query_range = 'K1:L30' + expected = expected = [[(x + 1) / 100.0] for x in range(30)] + +class TestDates(TestWorksheet): + + sheet_name = 'Sheet4 - Dates' + + def test_read_single_cell_date(self): + + wb = load_workbook(filename = self.workbook_name, use_iterators = True) + ws = wb.get_sheet_by_name(name = self.sheet_name) + + eq_(datetime.datetime(1973, 5, 20), list(ws.iter_rows('A1'))[0][0].internal_value) + eq_(datetime.datetime(1973, 5, 20, 9, 15, 2), list(ws.iter_rows('C1'))[0][0].internal_value) + + + diff --git a/tablib/packages/openpyxl3/tests/test_meta.py b/tablib/packages/openpyxl3/tests/test_meta.py index 3aefdcf..06051ad 100644 --- a/tablib/packages/openpyxl3/tests/test_meta.py +++ b/tablib/packages/openpyxl3/tests/test_meta.py @@ -24,7 +24,7 @@ # @author: Eric Gazoni # Python stdlib imports -from __future__ import with_statement + import os.path # package imports diff --git a/tablib/packages/openpyxl3/tests/test_named_range.py b/tablib/packages/openpyxl3/tests/test_named_range.py index a56ed86..c6d9d58 100644 --- a/tablib/packages/openpyxl3/tests/test_named_range.py +++ b/tablib/packages/openpyxl3/tests/test_named_range.py @@ -24,7 +24,7 @@ # @author: Eric Gazoni # Python stdlib imports -from __future__ import with_statement + import os.path # 3rd-party imports diff --git a/tablib/packages/openpyxl3/tests/test_number_format.py b/tablib/packages/openpyxl3/tests/test_number_format.py index 2f5c9da..54b0b88 100644 --- a/tablib/packages/openpyxl3/tests/test_number_format.py +++ b/tablib/packages/openpyxl3/tests/test_number_format.py @@ -24,7 +24,7 @@ # @author: Eric Gazoni # Python stdlib imports -from __future__ import with_statement + from datetime import datetime, date # 3rd party imports @@ -57,7 +57,7 @@ class TestNumberFormat(object): date_pairs= ( (40167, datetime(2009, 12, 20)), - (21980, datetime(1960, 03, 05)), + (21980, datetime(1960, 0o3, 0o5)), ) for count, dt in date_pairs: @@ -128,7 +128,7 @@ class TestNumberFormat(object): def check_bad_date(year, month, day): assert_raises(ValueError, self.sd.to_julian, year, month, day) - bad_dates = ((1776, 07, 04), (1899, 12, 31), ) + bad_dates = ((1776, 0o7, 0o4), (1899, 12, 31), ) for year, month, day in bad_dates: yield check_bad_date, year, month, day diff --git a/tablib/packages/openpyxl3/tests/test_props.py b/tablib/packages/openpyxl3/tests/test_props.py index 1c5adb64a9f03e67c98e58fbe7fb1c606cce0ec4..07d447c9234f743e7a1c02871547bf57bcdaf170 100644 GIT binary patch literal 4494 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 E1ogd)IRF3v diff --git a/tablib/packages/openpyxl3/tests/test_read.py b/tablib/packages/openpyxl3/tests/test_read.py index eb6ab07..1240164 100644 --- a/tablib/packages/openpyxl3/tests/test_read.py +++ b/tablib/packages/openpyxl3/tests/test_read.py @@ -24,7 +24,7 @@ # @author: Eric Gazoni # Python stdlib imports -from __future__ import with_statement + import os.path # 3rd party imports diff --git a/tablib/packages/openpyxl3/tests/test_strings.py b/tablib/packages/openpyxl3/tests/test_strings.py index bf3cc57..7dbf17a 100644 --- a/tablib/packages/openpyxl3/tests/test_strings.py +++ b/tablib/packages/openpyxl3/tests/test_strings.py @@ -24,7 +24,7 @@ # @author: Eric Gazoni # Python stdlib imports -from __future__ import with_statement + import os.path # 3rd party imports diff --git a/tablib/packages/openpyxl3/tests/test_style.py b/tablib/packages/openpyxl3/tests/test_style.py index a11ea43..4c8962c 100644 --- a/tablib/packages/openpyxl3/tests/test_style.py +++ b/tablib/packages/openpyxl3/tests/test_style.py @@ -24,7 +24,7 @@ # @author: Eric Gazoni # Python stdlib imports -from __future__ import with_statement + import os.path import datetime diff --git a/tablib/packages/openpyxl3/tests/test_write.py b/tablib/packages/openpyxl3/tests/test_write.py index 51e3c15..74363cb 100644 --- a/tablib/packages/openpyxl3/tests/test_write.py +++ b/tablib/packages/openpyxl3/tests/test_write.py @@ -24,8 +24,8 @@ # @author: Eric Gazoni # Python stdlib imports -from __future__ import with_statement -from io import BytesIO as StringIO + +from io import StringIO import os.path # 3rd party imports diff --git a/tablib/packages/openpyxl3/worksheet.py b/tablib/packages/openpyxl3/worksheet.py index 2eb6499..6cdda6b 100644 --- a/tablib/packages/openpyxl3/worksheet.py +++ b/tablib/packages/openpyxl3/worksheet.py @@ -187,7 +187,7 @@ class Worksheet(object): """Represents a worksheet. Do not create worksheets yourself, - use :func:`openpyxl.workbook.Workbook.create_sheet` instead + use :func:`.workbook.Workbook.create_sheet` instead """ BREAK_NONE = 0 @@ -241,7 +241,7 @@ class Worksheet(object): def get_cell_collection(self): """Return an unordered list of the cells in this worksheet.""" - return self._cells.values() + return list(self._cells.values()) def _set_title(self, value): """Set a sheet title, ensuring it is valid.""" @@ -325,7 +325,7 @@ class Worksheet(object): :raise: InsufficientCoordinatesException when coordinate or (row and column) are not given - :rtype: :class:`openpyxl.cell.Cell` + :rtype: :class:`.cell.Cell` """ if not coordinate: @@ -390,7 +390,7 @@ class Worksheet(object): :param column: number of columns to offset :type column: int - :rtype: tuples of tuples of :class:`openpyxl.cell.Cell` + :rtype: tuples of tuples of :class:`.cell.Cell` """ if ':' in range_string: diff --git a/tablib/packages/openpyxl3/writer/charts.py b/tablib/packages/openpyxl3/writer/charts.py index c0dfadb..420328d 100644 --- a/tablib/packages/openpyxl3/writer/charts.py +++ b/tablib/packages/openpyxl3/writer/charts.py @@ -1,261 +1,262 @@ -# coding=UTF-8 -''' -Copyright (c) 2010 openpyxl - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -@license: http://www.opensource.org/licenses/mit-license.php -@author: Eric Gazoni -''' - -from ..shared.xmltools import Element, SubElement, get_document_content -from ..chart import Chart, ErrorBar - -class ChartWriter(object): - - def __init__(self, chart): - self.chart = chart - - def write(self): - """ write a chart """ - - root = Element('c:chartSpace', - {'xmlns:c':"http://schemas.openxmlformats.org/drawingml/2006/chart", - 'xmlns:a':"http://schemas.openxmlformats.org/drawingml/2006/main", - 'xmlns:r':"http://schemas.openxmlformats.org/officeDocument/2006/relationships"}) - - SubElement(root, 'c:lang', {'val':self.chart.lang}) - self._write_chart(root) - self._write_print_settings(root) - self._write_shapes(root) - - return get_document_content(root) - - def _write_chart(self, root): - - chart = self.chart - - ch = SubElement(root, 'c:chart') - self._write_title(ch) - plot_area = SubElement(ch, 'c:plotArea') - layout = SubElement(plot_area, 'c:layout') - mlayout = SubElement(layout, 'c:manualLayout') - SubElement(mlayout, 'c:layoutTarget', {'val':'inner'}) - SubElement(mlayout, 'c:xMode', {'val':'edge'}) - SubElement(mlayout, 'c:yMode', {'val':'edge'}) - SubElement(mlayout, 'c:x', {'val':str(chart._get_margin_left())}) - SubElement(mlayout, 'c:y', {'val':str(chart._get_margin_top())}) - SubElement(mlayout, 'c:w', {'val':str(chart.width)}) - SubElement(mlayout, 'c:h', {'val':str(chart.height)}) - - if chart.type == Chart.SCATTER_CHART: - subchart = SubElement(plot_area, 'c:scatterChart') - SubElement(subchart, 'c:scatterStyle', {'val':str('lineMarker')}) - else: - if chart.type == Chart.BAR_CHART: - subchart = SubElement(plot_area, 'c:barChart') - SubElement(subchart, 'c:barDir', {'val':'col'}) - else: - subchart = SubElement(plot_area, 'c:lineChart') - - SubElement(subchart, 'c:grouping', {'val':chart.grouping}) - - self._write_series(subchart) - - SubElement(subchart, 'c:marker', {'val':'1'}) - SubElement(subchart, 'c:axId', {'val':str(chart.x_axis.id)}) - SubElement(subchart, 'c:axId', {'val':str(chart.y_axis.id)}) - - if chart.type == Chart.SCATTER_CHART: - self._write_axis(plot_area, chart.x_axis, 'c:valAx') - else: - self._write_axis(plot_area, chart.x_axis, 'c:catAx') - self._write_axis(plot_area, chart.y_axis, 'c:valAx') - - self._write_legend(ch) - - SubElement(ch, 'c:plotVisOnly', {'val':'1'}) - - def _write_title(self, chart): - if self.chart.title != '': - title = SubElement(chart, 'c:title') - tx = SubElement(title, 'c:tx') - rich = SubElement(tx, 'c:rich') - SubElement(rich, 'a:bodyPr') - SubElement(rich, 'a:lstStyle') - p = SubElement(rich, 'a:p') - pPr = SubElement(p, 'a:pPr') - SubElement(pPr, 'a:defRPr') - r = SubElement(p, 'a:r') - SubElement(r, 'a:rPr', {'lang':self.chart.lang}) - t = SubElement(r, 'a:t').text = self.chart.title - SubElement(title, 'c:layout') - - def _write_axis(self, plot_area, axis, label): - - ax = SubElement(plot_area, label) - SubElement(ax, 'c:axId', {'val':str(axis.id)}) - - scaling = SubElement(ax, 'c:scaling') - SubElement(scaling, 'c:orientation', {'val':axis.orientation}) - if label == 'c:valAx': - SubElement(scaling, 'c:max', {'val':str(axis.max)}) - SubElement(scaling, 'c:min', {'val':str(axis.min)}) - - SubElement(ax, 'c:axPos', {'val':axis.position}) - if label == 'c:valAx': - SubElement(ax, 'c:majorGridlines') - SubElement(ax, 'c:numFmt', {'formatCode':"General", 'sourceLinked':'1'}) - SubElement(ax, 'c:tickLblPos', {'val':axis.tick_label_position}) - SubElement(ax, 'c:crossAx', {'val':str(axis.cross)}) - SubElement(ax, 'c:crosses', {'val':axis.crosses}) - if axis.auto: - SubElement(ax, 'c:auto', {'val':'1'}) - if axis.label_align: - SubElement(ax, 'c:lblAlgn', {'val':axis.label_align}) - if axis.label_offset: - SubElement(ax, 'c:lblOffset', {'val':str(axis.label_offset)}) - if label == 'c:valAx': - if self.chart.type == Chart.SCATTER_CHART: - SubElement(ax, 'c:crossBetween', {'val':'midCat'}) - else: - SubElement(ax, 'c:crossBetween', {'val':'between'}) - SubElement(ax, 'c:majorUnit', {'val':str(axis.unit)}) - - def _write_series(self, subchart): - - for i, serie in enumerate(self.chart._series): - ser = SubElement(subchart, 'c:ser') - SubElement(ser, 'c:idx', {'val':str(i)}) - SubElement(ser, 'c:order', {'val':str(i)}) - - if serie.legend: - tx = SubElement(ser, 'c:tx') - self._write_serial(tx, serie.legend) - - if serie.color: - sppr = SubElement(ser, 'c:spPr') - if self.chart.type == Chart.BAR_CHART: - # fill color - fillc = SubElement(sppr, 'a:solidFill') - SubElement(fillc, 'a:srgbClr', {'val':serie.color}) - # edge color - ln = SubElement(sppr, 'a:ln') - fill = SubElement(ln, 'a:solidFill') - SubElement(fill, 'a:srgbClr', {'val':serie.color}) - - if serie.error_bar: - self._write_error_bar(ser, serie) - - marker = SubElement(ser, 'c:marker') - SubElement(marker, 'c:symbol', {'val':serie.marker}) - - if serie.labels: - cat = SubElement(ser, 'c:cat') - self._write_serial(cat, serie.labels) - - if self.chart.type == Chart.SCATTER_CHART: - if serie.xvalues: - xval = SubElement(ser, 'c:xVal') - self._write_serial(xval, serie.xvalues) - - yval = SubElement(ser, 'c:yVal') - self._write_serial(yval, serie.values) - else: - val = SubElement(ser, 'c:val') - self._write_serial(val, serie.values) - - def _write_serial(self, node, serie, literal=False): - - cache = serie._get_cache() - if isinstance(cache[0], str): - typ = 'str' - else: - typ = 'num' - - if not literal: - if typ == 'num': - ref = SubElement(node, 'c:numRef') - else: - ref = SubElement(node, 'c:strRef') - SubElement(ref, 'c:f').text = serie._get_ref() - if typ == 'num': - data = SubElement(ref, 'c:numCache') - else: - data = SubElement(ref, 'c:strCache') - else: - data = SubElement(node, 'c:numLit') - - if typ == 'num': - SubElement(data, 'c:formatCode').text = 'General' - if literal: - values = (1,) - else: - values = cache - - SubElement(data, 'c:ptCount', {'val':str(len(values))}) - for j, val in enumerate(values): - point = SubElement(data, 'c:pt', {'idx':str(j)}) - SubElement(point, 'c:v').text = str(val) - - def _write_error_bar(self, node, serie): - - flag = {ErrorBar.PLUS_MINUS:'both', - ErrorBar.PLUS:'plus', - ErrorBar.MINUS:'minus'} - - eb = SubElement(node, 'c:errBars') - SubElement(eb, 'c:errBarType', {'val':flag[serie.error_bar.type]}) - SubElement(eb, 'c:errValType', {'val':'cust'}) - - plus = SubElement(eb, 'c:plus') - self._write_serial(plus, serie.error_bar.values, - literal=(serie.error_bar.type==ErrorBar.MINUS)) - - minus = SubElement(eb, 'c:minus') - self._write_serial(minus, serie.error_bar.values, - literal=(serie.error_bar.type==ErrorBar.PLUS)) - - def _write_legend(self, chart): - - legend = SubElement(chart, 'c:legend') - SubElement(legend, 'c:legendPos', {'val':self.chart.legend.position}) - SubElement(legend, 'c:layout') - - def _write_print_settings(self, root): - - settings = SubElement(root, 'c:printSettings') - SubElement(settings, 'c:headerFooter') - margins = dict([(k, str(v)) for (k,v) in self.chart.print_margins.items()]) - SubElement(settings, 'c:pageMargins', margins) - SubElement(settings, 'c:pageSetup') - - def _write_shapes(self, root): - - if self.chart._shapes: - SubElement(root, 'c:userShapes', {'r:id':'rId1'}) - - def write_rels(self, drawing_id): - - root = Element('Relationships', {'xmlns' : 'http://schemas.openxmlformats.org/package/2006/relationships'}) - attrs = {'Id' : 'rId1', - 'Type' : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUserShapes', - 'Target' : '../drawings/drawing%s.xml' % drawing_id } - SubElement(root, 'Relationship', attrs) - return get_document_content(root) +# coding=UTF-8 +''' +Copyright (c) 2010 openpyxl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license: http://www.opensource.org/licenses/mit-license.php +@author: Eric Gazoni +''' + +from ..shared.xmltools import Element, SubElement, get_document_content +from ..chart import Chart, ErrorBar + + +class ChartWriter(object): + + def __init__(self, chart): + self.chart = chart + + def write(self): + """ write a chart """ + + root = Element('c:chartSpace', + {'xmlns:c':"http://schemas.openxmlformats.org/drawingml/2006/chart", + 'xmlns:a':"http://schemas.openxmlformats.org/drawingml/2006/main", + 'xmlns:r':"http://schemas.openxmlformats.org/officeDocument/2006/relationships"}) + + SubElement(root, 'c:lang', {'val':self.chart.lang}) + self._write_chart(root) + self._write_print_settings(root) + self._write_shapes(root) + + return get_document_content(root) + + def _write_chart(self, root): + + chart = self.chart + + ch = SubElement(root, 'c:chart') + self._write_title(ch) + plot_area = SubElement(ch, 'c:plotArea') + layout = SubElement(plot_area, 'c:layout') + mlayout = SubElement(layout, 'c:manualLayout') + SubElement(mlayout, 'c:layoutTarget', {'val':'inner'}) + SubElement(mlayout, 'c:xMode', {'val':'edge'}) + SubElement(mlayout, 'c:yMode', {'val':'edge'}) + SubElement(mlayout, 'c:x', {'val':str(chart._get_margin_left())}) + SubElement(mlayout, 'c:y', {'val':str(chart._get_margin_top())}) + SubElement(mlayout, 'c:w', {'val':str(chart.width)}) + SubElement(mlayout, 'c:h', {'val':str(chart.height)}) + + if chart.type == Chart.SCATTER_CHART: + subchart = SubElement(plot_area, 'c:scatterChart') + SubElement(subchart, 'c:scatterStyle', {'val':str('lineMarker')}) + else: + if chart.type == Chart.BAR_CHART: + subchart = SubElement(plot_area, 'c:barChart') + SubElement(subchart, 'c:barDir', {'val':'col'}) + else: + subchart = SubElement(plot_area, 'c:lineChart') + + SubElement(subchart, 'c:grouping', {'val':chart.grouping}) + + self._write_series(subchart) + + SubElement(subchart, 'c:marker', {'val':'1'}) + SubElement(subchart, 'c:axId', {'val':str(chart.x_axis.id)}) + SubElement(subchart, 'c:axId', {'val':str(chart.y_axis.id)}) + + if chart.type == Chart.SCATTER_CHART: + self._write_axis(plot_area, chart.x_axis, 'c:valAx') + else: + self._write_axis(plot_area, chart.x_axis, 'c:catAx') + self._write_axis(plot_area, chart.y_axis, 'c:valAx') + + self._write_legend(ch) + + SubElement(ch, 'c:plotVisOnly', {'val':'1'}) + + def _write_title(self, chart): + if self.chart.title != '': + title = SubElement(chart, 'c:title') + tx = SubElement(title, 'c:tx') + rich = SubElement(tx, 'c:rich') + SubElement(rich, 'a:bodyPr') + SubElement(rich, 'a:lstStyle') + p = SubElement(rich, 'a:p') + pPr = SubElement(p, 'a:pPr') + SubElement(pPr, 'a:defRPr') + r = SubElement(p, 'a:r') + SubElement(r, 'a:rPr', {'lang':self.chart.lang}) + t = SubElement(r, 'a:t').text = self.chart.title + SubElement(title, 'c:layout') + + def _write_axis(self, plot_area, axis, label): + + ax = SubElement(plot_area, label) + SubElement(ax, 'c:axId', {'val':str(axis.id)}) + + scaling = SubElement(ax, 'c:scaling') + SubElement(scaling, 'c:orientation', {'val':axis.orientation}) + if label == 'c:valAx': + SubElement(scaling, 'c:max', {'val':str(axis.max)}) + SubElement(scaling, 'c:min', {'val':str(axis.min)}) + + SubElement(ax, 'c:axPos', {'val':axis.position}) + if label == 'c:valAx': + SubElement(ax, 'c:majorGridlines') + SubElement(ax, 'c:numFmt', {'formatCode':"General", 'sourceLinked':'1'}) + SubElement(ax, 'c:tickLblPos', {'val':axis.tick_label_position}) + SubElement(ax, 'c:crossAx', {'val':str(axis.cross)}) + SubElement(ax, 'c:crosses', {'val':axis.crosses}) + if axis.auto: + SubElement(ax, 'c:auto', {'val':'1'}) + if axis.label_align: + SubElement(ax, 'c:lblAlgn', {'val':axis.label_align}) + if axis.label_offset: + SubElement(ax, 'c:lblOffset', {'val':str(axis.label_offset)}) + if label == 'c:valAx': + if self.chart.type == Chart.SCATTER_CHART: + SubElement(ax, 'c:crossBetween', {'val':'midCat'}) + else: + SubElement(ax, 'c:crossBetween', {'val':'between'}) + SubElement(ax, 'c:majorUnit', {'val':str(axis.unit)}) + + def _write_series(self, subchart): + + for i, serie in enumerate(self.chart._series): + ser = SubElement(subchart, 'c:ser') + SubElement(ser, 'c:idx', {'val':str(i)}) + SubElement(ser, 'c:order', {'val':str(i)}) + + if serie.legend: + tx = SubElement(ser, 'c:tx') + self._write_serial(tx, serie.legend) + + if serie.color: + sppr = SubElement(ser, 'c:spPr') + if self.chart.type == Chart.BAR_CHART: + # fill color + fillc = SubElement(sppr, 'a:solidFill') + SubElement(fillc, 'a:srgbClr', {'val':serie.color}) + # edge color + ln = SubElement(sppr, 'a:ln') + fill = SubElement(ln, 'a:solidFill') + SubElement(fill, 'a:srgbClr', {'val':serie.color}) + + if serie.error_bar: + self._write_error_bar(ser, serie) + + marker = SubElement(ser, 'c:marker') + SubElement(marker, 'c:symbol', {'val':serie.marker}) + + if serie.labels: + cat = SubElement(ser, 'c:cat') + self._write_serial(cat, serie.labels) + + if self.chart.type == Chart.SCATTER_CHART: + if serie.xvalues: + xval = SubElement(ser, 'c:xVal') + self._write_serial(xval, serie.xvalues) + + yval = SubElement(ser, 'c:yVal') + self._write_serial(yval, serie.values) + else: + val = SubElement(ser, 'c:val') + self._write_serial(val, serie.values) + + def _write_serial(self, node, serie, literal=False): + + cache = serie._get_cache() + if isinstance(cache[0], str): + typ = 'str' + else: + typ = 'num' + + if not literal: + if typ == 'num': + ref = SubElement(node, 'c:numRef') + else: + ref = SubElement(node, 'c:strRef') + SubElement(ref, 'c:f').text = serie._get_ref() + if typ == 'num': + data = SubElement(ref, 'c:numCache') + else: + data = SubElement(ref, 'c:strCache') + else: + data = SubElement(node, 'c:numLit') + + if typ == 'num': + SubElement(data, 'c:formatCode').text = 'General' + if literal: + values = (1,) + else: + values = cache + + SubElement(data, 'c:ptCount', {'val':str(len(values))}) + for j, val in enumerate(values): + point = SubElement(data, 'c:pt', {'idx':str(j)}) + SubElement(point, 'c:v').text = str(val) + + def _write_error_bar(self, node, serie): + + flag = {ErrorBar.PLUS_MINUS:'both', + ErrorBar.PLUS:'plus', + ErrorBar.MINUS:'minus'} + + eb = SubElement(node, 'c:errBars') + SubElement(eb, 'c:errBarType', {'val':flag[serie.error_bar.type]}) + SubElement(eb, 'c:errValType', {'val':'cust'}) + + plus = SubElement(eb, 'c:plus') + self._write_serial(plus, serie.error_bar.values, + literal=(serie.error_bar.type==ErrorBar.MINUS)) + + minus = SubElement(eb, 'c:minus') + self._write_serial(minus, serie.error_bar.values, + literal=(serie.error_bar.type==ErrorBar.PLUS)) + + def _write_legend(self, chart): + + legend = SubElement(chart, 'c:legend') + SubElement(legend, 'c:legendPos', {'val':self.chart.legend.position}) + SubElement(legend, 'c:layout') + + def _write_print_settings(self, root): + + settings = SubElement(root, 'c:printSettings') + SubElement(settings, 'c:headerFooter') + margins = dict([(k, str(v)) for (k,v) in self.chart.print_margins.items()]) + SubElement(settings, 'c:pageMargins', margins) + SubElement(settings, 'c:pageSetup') + + def _write_shapes(self, root): + + if self.chart._shapes: + SubElement(root, 'c:userShapes', {'r:id':'rId1'}) + + def write_rels(self, drawing_id): + + root = Element('Relationships', {'xmlns' : 'http://schemas.openxmlformats.org/package/2006/relationships'}) + attrs = {'Id' : 'rId1', + 'Type' : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUserShapes', + 'Target' : '../drawings/drawing%s.xml' % drawing_id } + SubElement(root, 'Relationship', attrs) + return get_document_content(root) diff --git a/tablib/packages/openpyxl3/writer/dump_worksheet.py b/tablib/packages/openpyxl3/writer/dump_worksheet.py index 521cae0..36d68d4 100644 --- a/tablib/packages/openpyxl3/writer/dump_worksheet.py +++ b/tablib/packages/openpyxl3/writer/dump_worksheet.py @@ -66,7 +66,7 @@ class DumpWorksheet(Worksheet): """ .. warning:: - You shouldn't initialize this yourself, use :class:`openpyxl.workbook.Workbook` constructor instead, + You shouldn't initialize this yourself, use :class:`..workbook.Workbook` constructor instead, with `optimized_write = True`. """ @@ -77,9 +77,9 @@ class DumpWorksheet(Worksheet): self._max_col = 0 self._max_row = 0 self._parent = parent_workbook - self._fileobj_header = NamedTemporaryFile(mode='r+', prefix='openpyxl.', suffix='.header', delete=False) - self._fileobj_content = NamedTemporaryFile(mode='r+', prefix='openpyxl.', suffix='.content', delete=False) - self._fileobj = NamedTemporaryFile(mode='w', prefix='openpyxl.', delete=False) + self._fileobj_header = NamedTemporaryFile(mode='r+', prefix='..', suffix='.header', delete=False) + self._fileobj_content = NamedTemporaryFile(mode='r+', prefix='..', suffix='.content', delete=False) + self._fileobj = NamedTemporaryFile(mode='w', prefix='..', delete=False) self.doc = XMLGenerator(self._fileobj_content, 'utf-8') self.header = XMLGenerator(self._fileobj_header, 'utf-8') self.title = 'Sheet' diff --git a/tablib/packages/openpyxl3/writer/excel.py b/tablib/packages/openpyxl3/writer/excel.py index f09746f..a19666d 100644 --- a/tablib/packages/openpyxl3/writer/excel.py +++ b/tablib/packages/openpyxl3/writer/excel.py @@ -27,22 +27,22 @@ # Python stdlib imports from zipfile import ZipFile, ZIP_DEFLATED -from io import BytesIO as StringIO +from io import StringIO # package imports from ..shared.ooxml import ARC_SHARED_STRINGS, ARC_CONTENT_TYPES, \ ARC_ROOT_RELS, ARC_WORKBOOK_RELS, ARC_APP, ARC_CORE, ARC_THEME, \ ARC_STYLE, ARC_WORKBOOK, \ PACKAGE_WORKSHEETS, PACKAGE_DRAWINGS, PACKAGE_CHARTS -from ..writer.strings import create_string_table, write_string_table -from ..writer.workbook import write_content_types, write_root_rels, \ +from .strings import create_string_table, write_string_table +from .workbook import write_content_types, write_root_rels, \ write_workbook_rels, write_properties_app, write_properties_core, \ write_workbook -from ..writer.theme import write_theme -from ..writer.styles import StyleWriter -from ..writer.drawings import DrawingWriter, ShapeWriter -from ..writer.charts import ChartWriter -from ..writer.worksheet import write_worksheet, write_worksheet_rels +from .theme import write_theme +from .styles import StyleWriter +from .drawings import DrawingWriter, ShapeWriter +from .charts import ChartWriter +from .worksheet import write_worksheet, write_worksheet_rels class ExcelWriter(object): diff --git a/tablib/packages/openpyxl3/writer/strings.py b/tablib/packages/openpyxl3/writer/strings.py index f3e882e..706c2b6 100644 --- a/tablib/packages/openpyxl3/writer/strings.py +++ b/tablib/packages/openpyxl3/writer/strings.py @@ -26,7 +26,7 @@ """Write the shared string table.""" # Python stdlib imports -from io import BytesIO as StringIO +from io import StringIO # package imports from ..shared.xmltools import start_tag, end_tag, tag, XMLGenerator @@ -49,7 +49,7 @@ def write_string_table(string_table): start_tag(doc, 'sst', {'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', 'uniqueCount': '%d' % len(string_table)}) - strings_to_write = sorted(string_table.items(), + strings_to_write = sorted(iter(string_table.items()), key=lambda pair: pair[1]) for key in [pair[0] for pair in strings_to_write]: start_tag(doc, 'si') diff --git a/tablib/packages/openpyxl3/writer/styles.py b/tablib/packages/openpyxl3/writer/styles.py index 7e3fb64..3d73382 100644 --- a/tablib/packages/openpyxl3/writer/styles.py +++ b/tablib/packages/openpyxl3/writer/styles.py @@ -40,11 +40,11 @@ class StyleWriter(object): def _get_style_list(self, workbook): crc = {} for worksheet in workbook.worksheets: - for style in worksheet._styles.values(): + for style in list(worksheet._styles.values()): crc[hash(style)] = style self.style_table = dict([(style, i+1) \ - for i, style in enumerate(crc.values())]) - sorted_styles = sorted(self.style_table.items(), \ + for i, style in enumerate(list(crc.values()))]) + sorted_styles = sorted(iter(self.style_table.items()), \ key = lambda pair:pair[1]) return [s[0] for s in sorted_styles] diff --git a/tablib/packages/openpyxl3/writer/worksheet.py b/tablib/packages/openpyxl3/writer/worksheet.py index b7633f8..21d9e9b 100644 --- a/tablib/packages/openpyxl3/writer/worksheet.py +++ b/tablib/packages/openpyxl3/writer/worksheet.py @@ -26,7 +26,7 @@ """Write worksheets to xml representations.""" # Python stdlib imports -from io import BytesIO as StringIO # cStringIO doesn't handle unicode +from io import StringIO # cStringIO doesn't handle unicode # package imports from ..cell import coordinate_from_string, column_index_from_string From f30e760657bc73a16559b948c0de53916fc63aa9 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 13 May 2011 00:29:51 -0400 Subject: [PATCH 16/22] less compat reliance --- tablib/packages/openpyxl/cell.py | 1 - tablib/packages/openpyxl/reader/iter_worksheet.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tablib/packages/openpyxl/cell.py b/tablib/packages/openpyxl/cell.py index ec5e74e..757a834 100644 --- a/tablib/packages/openpyxl/cell.py +++ b/tablib/packages/openpyxl/cell.py @@ -42,7 +42,6 @@ from .shared.date_time import SharedDate from .shared.exc import CellCoordinatesException, \ ColumnStringIndexException, DataTypeException from .style import NumberFormat -from ...compat import basestring, unicode # constants COORD_RE = re.compile('^[$]?([A-Z]+)[$]?(\d+)$') diff --git a/tablib/packages/openpyxl/reader/iter_worksheet.py b/tablib/packages/openpyxl/reader/iter_worksheet.py index 781c0ca..7b3bc04 100644 --- a/tablib/packages/openpyxl/reader/iter_worksheet.py +++ b/tablib/packages/openpyxl/reader/iter_worksheet.py @@ -31,8 +31,7 @@ from ....compat import BytesIO as StringIO import warnings import operator from functools import partial -from ....compat import ifilter, xrange -from itertools import groupby +from itertools import groupby, ifilter from ..worksheet import Worksheet from ..cell import coordinate_from_string, get_column_letter, Cell from ..reader.excel import get_sheet_ids From cbdaa09e8324767926e49e814170a325758c072b Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 13 May 2011 00:30:03 -0400 Subject: [PATCH 17/22] success!! --- tablib/compat.py | 2 ++ tablib/formats/_xlsx.py | 20 ++++++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/tablib/compat.py b/tablib/compat.py index 82946f7..48e0081 100644 --- a/tablib/compat.py +++ b/tablib/compat.py @@ -24,6 +24,7 @@ if is_py3: from io import BytesIO import tablib.packages.xlwt3 as xlwt from tablib.packages import markup3 as markup + from tablib.packages import openpyxl3 as openpyxl # py3 mappings ifilter = filter @@ -37,6 +38,7 @@ else: import tablib.packages.xlwt as xlwt from tablib.packages import markup from itertools import ifilter + from tablib.packages import openpyxl # py2 mappings xrange = xrange diff --git a/tablib/formats/_xlsx.py b/tablib/formats/_xlsx.py index 1a0713b..9cd63b5 100644 --- a/tablib/formats/_xlsx.py +++ b/tablib/formats/_xlsx.py @@ -7,14 +7,18 @@ import sys if sys.version_info[0] > 2: - from io import BytesIO + from io import BytesIO else: from cStringIO import StringIO as BytesIO -from tablib.packages.openpyxl.workbook import Workbook -from tablib.packages.openpyxl.writer.excel import ExcelWriter +from tablib.compat import openpyxl + +Workbook = openpyxl.workbook.Workbook +ExcelWriter = openpyxl.writer.excel.ExcelWriter +get_column_letter = openpyxl.cell.get_column_letter + +from tablib.compat import unicode -from tablib.packages.openpyxl.cell import get_column_letter title = 'xlsx' extentions = ('xlsx',) @@ -65,8 +69,9 @@ def dset_sheet(dataset, ws): # bold headers if (row_number == 1) and dataset.headers: - ws.cell('%s%s'%(col_idx, row_number)).value = unicode( - '%s' % col, errors='ignore') + # ws.cell('%s%s'%(col_idx, row_number)).value = unicode( + # '%s' % col, errors='ignore') + ws.cell('%s%s'%(col_idx, row_number)).value = unicode(col) style = ws.get_style('%s%s' % (col_idx, row_number)) style.font.bold = True ws.freeze_panes = '%s%s' % (col_idx, row_number) @@ -91,7 +96,6 @@ def dset_sheet(dataset, ws): ws.cell('%s%s'%(col_idx, row_number)).value = unicode( '%s' % col, errors='ignore') except TypeError: - ws.cell('%s%s'%(col_idx, row_number)).value = unicode( - '%s' % col, errors='ignore') + ws.cell('%s%s'%(col_idx, row_number)).value = unicode(col) From 9399bf2fe7cdb32b593dfa0012b6fc16f877a9ec Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 13 May 2011 00:32:02 -0400 Subject: [PATCH 18/22] no vendored tests --- tablib/packages/openpyxl/tests/__init__.py | 24 - tablib/packages/openpyxl/tests/helper.py | 94 ---- tablib/packages/openpyxl/tests/test_cell.py | 200 ------- tablib/packages/openpyxl/tests/test_chart.py | 131 ----- tablib/packages/openpyxl/tests/test_dump.py | 109 ---- tablib/packages/openpyxl/tests/test_iter.py | 112 ---- tablib/packages/openpyxl/tests/test_meta.py | 50 -- .../openpyxl/tests/test_named_range.py | 102 ---- .../openpyxl/tests/test_number_format.py | 142 ----- .../openpyxl/tests/test_password_hash.py | 41 -- tablib/packages/openpyxl/tests/test_props.py | 124 ----- tablib/packages/openpyxl/tests/test_read.py | 134 ----- .../packages/openpyxl/tests/test_strings.py | 68 --- tablib/packages/openpyxl/tests/test_style.py | 181 ------ tablib/packages/openpyxl/tests/test_theme.py | 37 -- .../packages/openpyxl/tests/test_workbook.py | 148 ----- .../packages/openpyxl/tests/test_worksheet.py | 258 --------- tablib/packages/openpyxl/tests/test_write.py | 203 ------- tablib/packages/openpyxl3/tests/__init__.py | 24 - tablib/packages/openpyxl3/tests/helper.py | 94 ---- tablib/packages/openpyxl3/tests/test_cell.py | 200 ------- tablib/packages/openpyxl3/tests/test_chart.py | 131 ----- .../test_data/genuine/empty-no-string.xlsx | Bin 7842 -> 0 bytes .../test_data/genuine/empty-with-styles.xlsx | Bin 7511 -> 0 bytes .../tests/test_data/genuine/empty.xlsx | Bin 11005 -> 0 bytes .../tests/test_data/genuine/merge_range.xlsx | Bin 8742 -> 0 bytes .../test_data/reader/app-multi-titles.xml | 43 -- .../reader/empty-workbook-styles.xml | 53 -- .../tests/test_data/reader/merged-ranges.xml | 2 - .../tests/test_data/reader/null_archive.xlsx | Bin 126 -> 0 bytes .../tests/test_data/reader/null_file.xlsx | 0 .../test_data/reader/shared-strings-rich.xml | 33 -- .../reader/sharedStrings-emptystring.xml | 3 - .../tests/test_data/reader/sharedStrings.xml | 2 - .../tests/test_data/reader/sheet2.xml | 2 - .../tests/test_data/reader/simple-styles.xml | 46 -- .../tests/test_data/reader/workbook.xml | 2 - .../tests/test_data/writer/expected/.rels | 2 - .../writer/expected/[Content_Types].xml | 31 -- .../tests/test_data/writer/expected/app.xml | 2 - .../tests/test_data/writer/expected/core.xml | 2 - .../tests/test_data/writer/expected/font.xml | 10 - .../writer/expected/sharedStrings.xml | 2 - .../test_data/writer/expected/sheet1.xml | 23 - .../writer/expected/sheet1_auto_filter.xml | 1 - .../writer/expected/sheet1_formula.xml | 33 -- .../expected/sheet1_freeze_panes_both.xml | 26 - .../expected/sheet1_freeze_panes_horiz.xml | 24 - .../expected/sheet1_freeze_panes_vert.xml | 24 - .../writer/expected/sheet1_height.xml | 23 - .../writer/expected/sheet1_hyperlink.xml | 27 - .../writer/expected/sheet1_hyperlink.xml.rels | 6 - .../writer/expected/sheet1_style.xml | 1 - .../writer/expected/simple-styles.xml | 46 -- .../test_data/writer/expected/styles.xml | 2 - .../test_data/writer/expected/theme1.xml | 2 - .../test_data/writer/expected/workbook.xml | 2 - .../writer/expected/workbook.xml.rels | 2 - tablib/packages/openpyxl3/tests/test_dump.py | 109 ---- tablib/packages/openpyxl3/tests/test_iter.py | 112 ---- tablib/packages/openpyxl3/tests/test_meta.py | 50 -- .../openpyxl3/tests/test_named_range.py | 102 ---- .../openpyxl3/tests/test_number_format.py | 142 ----- .../openpyxl3/tests/test_password_hash.py | 41 -- tablib/packages/openpyxl3/tests/test_props.py | 124 ----- tablib/packages/openpyxl3/tests/test_read.py | 134 ----- .../packages/openpyxl3/tests/test_strings.py | 68 --- tablib/packages/openpyxl3/tests/test_style.py | 181 ------ tablib/packages/openpyxl3/tests/test_theme.py | 37 -- .../packages/openpyxl3/tests/test_workbook.py | 148 ----- .../openpyxl3/tests/test_worksheet.py | 258 --------- tablib/packages/openpyxl3/tests/test_write.py | 203 ------- test.xlsx | Bin 0 -> 5377 bytes test_tablib.py.orig | 522 ++++++++++++++++++ toy.py | 9 + 75 files changed, 531 insertions(+), 4793 deletions(-) delete mode 100644 tablib/packages/openpyxl/tests/__init__.py delete mode 100644 tablib/packages/openpyxl/tests/helper.py delete mode 100644 tablib/packages/openpyxl/tests/test_cell.py delete mode 100644 tablib/packages/openpyxl/tests/test_chart.py delete mode 100644 tablib/packages/openpyxl/tests/test_dump.py delete mode 100644 tablib/packages/openpyxl/tests/test_iter.py delete mode 100644 tablib/packages/openpyxl/tests/test_meta.py delete mode 100644 tablib/packages/openpyxl/tests/test_named_range.py delete mode 100644 tablib/packages/openpyxl/tests/test_number_format.py delete mode 100644 tablib/packages/openpyxl/tests/test_password_hash.py delete mode 100644 tablib/packages/openpyxl/tests/test_props.py delete mode 100644 tablib/packages/openpyxl/tests/test_read.py delete mode 100644 tablib/packages/openpyxl/tests/test_strings.py delete mode 100644 tablib/packages/openpyxl/tests/test_style.py delete mode 100644 tablib/packages/openpyxl/tests/test_theme.py delete mode 100644 tablib/packages/openpyxl/tests/test_workbook.py delete mode 100644 tablib/packages/openpyxl/tests/test_worksheet.py delete mode 100644 tablib/packages/openpyxl/tests/test_write.py delete mode 100644 tablib/packages/openpyxl3/tests/__init__.py delete mode 100644 tablib/packages/openpyxl3/tests/helper.py delete mode 100644 tablib/packages/openpyxl3/tests/test_cell.py delete mode 100644 tablib/packages/openpyxl3/tests/test_chart.py delete mode 100644 tablib/packages/openpyxl3/tests/test_data/genuine/empty-no-string.xlsx delete mode 100644 tablib/packages/openpyxl3/tests/test_data/genuine/empty-with-styles.xlsx delete mode 100644 tablib/packages/openpyxl3/tests/test_data/genuine/empty.xlsx delete mode 100644 tablib/packages/openpyxl3/tests/test_data/genuine/merge_range.xlsx delete mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/app-multi-titles.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/empty-workbook-styles.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/merged-ranges.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/null_archive.xlsx delete mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/null_file.xlsx delete mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/shared-strings-rich.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings-emptystring.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/sheet2.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/simple-styles.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/reader/workbook.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/.rels delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/[Content_Types].xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/app.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/core.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/font.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sharedStrings.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_auto_filter.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_formula.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_both.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_horiz.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_vert.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_height.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml.rels delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_style.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/simple-styles.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/styles.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/theme1.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml delete mode 100644 tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml.rels delete mode 100644 tablib/packages/openpyxl3/tests/test_dump.py delete mode 100644 tablib/packages/openpyxl3/tests/test_iter.py delete mode 100644 tablib/packages/openpyxl3/tests/test_meta.py delete mode 100644 tablib/packages/openpyxl3/tests/test_named_range.py delete mode 100644 tablib/packages/openpyxl3/tests/test_number_format.py delete mode 100644 tablib/packages/openpyxl3/tests/test_password_hash.py delete mode 100644 tablib/packages/openpyxl3/tests/test_props.py delete mode 100644 tablib/packages/openpyxl3/tests/test_read.py delete mode 100644 tablib/packages/openpyxl3/tests/test_strings.py delete mode 100644 tablib/packages/openpyxl3/tests/test_style.py delete mode 100644 tablib/packages/openpyxl3/tests/test_theme.py delete mode 100644 tablib/packages/openpyxl3/tests/test_workbook.py delete mode 100644 tablib/packages/openpyxl3/tests/test_worksheet.py delete mode 100644 tablib/packages/openpyxl3/tests/test_write.py create mode 100644 test.xlsx create mode 100755 test_tablib.py.orig create mode 100644 toy.py diff --git a/tablib/packages/openpyxl/tests/__init__.py b/tablib/packages/openpyxl/tests/__init__.py deleted file mode 100644 index de89032..0000000 --- a/tablib/packages/openpyxl/tests/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# file openpyxl/tests/__init__.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni diff --git a/tablib/packages/openpyxl/tests/helper.py b/tablib/packages/openpyxl/tests/helper.py deleted file mode 100644 index ae9c8b4..0000000 --- a/tablib/packages/openpyxl/tests/helper.py +++ /dev/null @@ -1,94 +0,0 @@ -# file openpyxl/tests/helper.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -from __future__ import with_statement -import os -import os.path -import shutil -import difflib -from StringIO import StringIO -from pprint import pprint -from tempfile import gettempdir - -# package imports -from openpyxl.shared.xmltools import fromstring, ElementTree -from openpyxl.shared.xmltools import pretty_indent - -# constants -DATADIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test_data')) -TMPDIR = os.path.join(gettempdir(), 'openpyxl_test_temp') - - -def make_tmpdir(): - try: - os.makedirs(TMPDIR) - except OSError: - pass - - -def clean_tmpdir(): - if os.path.isdir(TMPDIR): - shutil.rmtree(TMPDIR, ignore_errors = True) - - -def assert_equals_file_content(reference_file, fixture, filetype = 'xml'): - if os.path.isfile(fixture): - with open(fixture) as fixture_file: - fixture_content = fixture_file.read() - else: - fixture_content = fixture - - with open(reference_file) as expected_file: - expected_content = expected_file.read() - - if filetype == 'xml': - fixture_content = fromstring(fixture_content) - pretty_indent(fixture_content) - temp = StringIO() - ElementTree(fixture_content).write(temp) - fixture_content = temp.getvalue() - - expected_content = fromstring(expected_content) - pretty_indent(expected_content) - temp = StringIO() - ElementTree(expected_content).write(temp) - expected_content = temp.getvalue() - - fixture_lines = fixture_content.split('\n') - expected_lines = expected_content.split('\n') - differences = list(difflib.unified_diff(expected_lines, fixture_lines)) - if differences: - temp = StringIO() - pprint(differences, stream = temp) - assert False, 'Differences found : %s' % temp.getvalue() - -def get_xml(xml_node): - - io = StringIO() - ElementTree(xml_node).write(io, encoding = 'UTF-8') - ret = io.getvalue() - io.close() - return ret.replace('\n', '') diff --git a/tablib/packages/openpyxl/tests/test_cell.py b/tablib/packages/openpyxl/tests/test_cell.py deleted file mode 100644 index edc35c0..0000000 --- a/tablib/packages/openpyxl/tests/test_cell.py +++ /dev/null @@ -1,200 +0,0 @@ -# file openpyxl/tests/test_cell.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -from datetime import time, datetime - -# 3rd party imports -from nose.tools import eq_, raises, assert_raises - -# package imports -from openpyxl.worksheet import Worksheet -from openpyxl.workbook import Workbook -from openpyxl.shared.exc import ColumnStringIndexException, \ - CellCoordinatesException, DataTypeException -from openpyxl.cell import column_index_from_string, \ - coordinate_from_string, get_column_letter, Cell, absolute_coordinate - - -def test_coordinates(): - column, row = coordinate_from_string('ZF46') - eq_("ZF", column) - eq_(46, row) - - -@raises(CellCoordinatesException) -def test_invalid_coordinate(): - coordinate_from_string('AAA') - - -def test_absolute(): - eq_('$ZF$51', absolute_coordinate('ZF51')) - -def test_absolute_multiple(): - - eq_('$ZF$51:$ZF$53', absolute_coordinate('ZF51:ZF$53')) - - -def test_column_index(): - eq_(10, column_index_from_string('J')) - eq_(270, column_index_from_string('jJ')) - eq_(7030, column_index_from_string('jjj')) - - -def test_bad_column_index(): - - @raises(ColumnStringIndexException) - def _check(bad_string): - column_index_from_string(bad_string) - - bad_strings = ('JJJJ', '', '$', '1',) - for bad_string in bad_strings: - yield _check, bad_string - - -def test_column_letter_boundries(): - assert_raises(ColumnStringIndexException, get_column_letter, 0) - assert_raises(ColumnStringIndexException, get_column_letter, 18279) - - -def test_column_letter(): - eq_('ZZZ', get_column_letter(18278)) - eq_('JJJ', get_column_letter(7030)) - eq_('AB', get_column_letter(28)) - eq_('AA', get_column_letter(27)) - eq_('Z', get_column_letter(26)) - - -def test_initial_value(): - cell = Cell(None, 'A', 1, value = '17.5') - eq_(cell.TYPE_NUMERIC, cell.data_type) - - -class TestCellValueTypes(): - - @classmethod - def setup_class(cls): - cls.cell = Cell(None, 'A', 1) - - def test_1st(self): - eq_(self.cell.TYPE_NULL, self.cell.data_type) - - def test_null(self): - self.cell.value = None - eq_(self.cell.TYPE_NULL, self.cell.data_type) - - def test_numeric(self): - - def check_numeric(value): - self.cell.value = value - eq_(self.cell.TYPE_NUMERIC, self.cell.data_type) - - values = (42, '4.2', '-42.000', '0', 0, 0.0001, '0.9999', '99E-02') - for value in values: - yield check_numeric, value - - def test_string(self): - self.cell.value = 'hello' - eq_(self.cell.TYPE_STRING, self.cell.data_type) - - def test_formula(self): - self.cell.value = '=42' - eq_(self.cell.TYPE_FORMULA, self.cell.data_type) - - def test_boolean(self): - self.cell.value = True - eq_(self.cell.TYPE_BOOL, self.cell.data_type) - self.cell.value = False - eq_(self.cell.TYPE_BOOL, self.cell.data_type) - - def test_error_codes(self): - - def check_error(): - eq_(self.cell.TYPE_ERROR, self.cell.data_type) - - for error_string in self.cell.ERROR_CODES.keys(): - self.cell.value = error_string - yield check_error - - -def test_data_type_check(): - cell = Cell(None, 'A', 1) - cell.bind_value(None) - eq_(Cell.TYPE_NULL, cell._data_type) - - cell.bind_value('.0e000') - eq_(Cell.TYPE_NUMERIC, cell._data_type) - - cell.bind_value('-0.e-0') - eq_(Cell.TYPE_NUMERIC, cell._data_type) - - cell.bind_value('1E') - eq_(Cell.TYPE_STRING, cell._data_type) - -@raises(DataTypeException) -def test_set_bad_type(): - cell = Cell(None, 'A', 1) - cell.set_value_explicit(1, 'q') - - -def test_time(): - - def check_time(raw_value, coerced_value): - cell.value = raw_value - eq_(cell.value, coerced_value) - eq_(cell.TYPE_NUMERIC, cell.data_type) - - wb = Workbook() - ws = Worksheet(wb) - cell = Cell(ws, 'A', 1) - values = (('03:40:16', time(3, 40, 16)), ('03:40', time(3, 40)),) - for raw_value, coerced_value in values: - yield check_time, raw_value, coerced_value - - -def test_date_format_on_non_date(): - wb = Workbook() - ws = Worksheet(wb) - cell = Cell(ws, 'A', 1) - cell.value = datetime.now() - cell.value = 'testme' - eq_('testme', cell.value) - - -def test_repr(): - wb = Workbook() - ws = Worksheet(wb) - cell = Cell(ws, 'A', 1) - eq_(repr(cell), '', 'Got bad repr: %s' % repr(cell)) - -def test_is_date(): - wb = Workbook() - ws = Worksheet(wb) - cell = Cell(ws, 'A', 1) - cell.value = datetime.now() - eq_(cell.is_date(), True) - cell.value = 'testme' - eq_('testme', cell.value) - eq_(cell.is_date(), False) diff --git a/tablib/packages/openpyxl/tests/test_chart.py b/tablib/packages/openpyxl/tests/test_chart.py deleted file mode 100644 index 2605a2b..0000000 --- a/tablib/packages/openpyxl/tests/test_chart.py +++ /dev/null @@ -1,131 +0,0 @@ -# file openpyxl/tests/test_chart.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -from nose.tools import eq_ - -from openpyxl.tests.helper import get_xml -from openpyxl.shared.xmltools import Element -from openpyxl.writer.charts import ChartWriter -from openpyxl.workbook import Workbook -from openpyxl.chart import BarChart, ScatterChart, Serie, Reference -from openpyxl.style import Color - -class TestChartWriter(object): - - def setUp(self): - - wb = Workbook() - ws = wb.get_active_sheet() - ws.title = u'data' - for i in range(10): - ws.cell(row = i, column = 0).value = i - self.chart = BarChart() - self.chart.title = 'TITLE' - self.chart.add_serie(Serie(Reference(ws, (0, 0), (10, 0)))) - self.chart._series[-1].color = Color.GREEN - self.cw = ChartWriter(self.chart) - self.root = Element('test') - - def test_write_title(self): - self.cw._write_title(self.root) - eq_(get_xml(self.root), 'TITLE') - - def test_write_xaxis(self): - - self.cw._write_axis(self.root, self.chart.x_axis, 'c:catAx') - eq_(get_xml(self.root), '') - - def test_write_yaxis(self): - - self.cw._write_axis(self.root, self.chart.y_axis, 'c:valAx') - eq_(get_xml(self.root), '') - - def test_write_series(self): - - self.cw._write_series(self.root) - eq_(get_xml(self.root), 'data!$A$1:$A$11General0123456789None') - - def test_write_legend(self): - - self.cw._write_legend(self.root) - eq_(get_xml(self.root), '') - - def test_write_print_settings(self): - - self.cw._write_print_settings(self.root) - eq_(get_xml(self.root), '') - - def test_write_chart(self): - - self.cw._write_chart(self.root) - eq_(get_xml(self.root), 'TITLEdata!$A$1:$A$11General0123456789None') - - -class TestScatterChartWriter(object): - - def setUp(self): - - wb = Workbook() - ws = wb.get_active_sheet() - ws.title = u'data' - for i in range(10): - ws.cell(row = i, column = 0).value = i - ws.cell(row = i, column = 1).value = i - self.scatterchart = ScatterChart() - self.scatterchart.add_serie(Serie(Reference(ws, (0, 0), (10, 0)), - xvalues = Reference(ws, (0, 1), (10, 1)))) - self.cw = ChartWriter(self.scatterchart) - self.root = Element('test') - - def test_write_xaxis(self): - - self.cw._write_axis(self.root, self.scatterchart.x_axis, 'c:valAx') - eq_(get_xml(self.root), '') - - - def test_write_yaxis(self): - - self.cw._write_axis(self.root, self.scatterchart.y_axis, 'c:valAx') - eq_(get_xml(self.root), '') - - def test_write_series(self): - - self.cw._write_series(self.root) - eq_(get_xml(self.root), 'data!$B$1:$B$11General0123456789Nonedata!$A$1:$A$11General0123456789None') - - def test_write_legend(self): - - self.cw._write_legend(self.root) - eq_(get_xml(self.root), '') - - def test_write_print_settings(self): - - self.cw._write_print_settings(self.root) - eq_(get_xml(self.root), '') - - def test_write_chart(self): - - self.cw._write_chart(self.root) - eq_(get_xml(self.root), 'data!$B$1:$B$11General0123456789Nonedata!$A$1:$A$11General0123456789None') diff --git a/tablib/packages/openpyxl/tests/test_dump.py b/tablib/packages/openpyxl/tests/test_dump.py deleted file mode 100644 index 1e18ace..0000000 --- a/tablib/packages/openpyxl/tests/test_dump.py +++ /dev/null @@ -1,109 +0,0 @@ - -# file openpyxl/tests/test_dump.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -from datetime import time, datetime - -# 3rd party imports -from nose.tools import eq_, raises, assert_raises - -from openpyxl.workbook import Workbook -from openpyxl.cell import get_column_letter - -from openpyxl.reader.excel import load_workbook - -from openpyxl.writer.strings import StringTableBuilder - -from tempfile import NamedTemporaryFile -import os -import shutil - -def test_dump_sheet(): - - test_file = NamedTemporaryFile(prefix='openpyxl.', suffix='.xlsx', delete=False) - test_file.close() - test_filename = test_file.name - - wb = Workbook(optimized_write = True) - - ws = wb.create_sheet() - - letters = [get_column_letter(x+1) for x in xrange(20)] - - expected_rows = [] - - for row in xrange(20): - - expected_rows.append(['%s%d' % (letter, row+1) for letter in letters]) - - for row in xrange(20): - - expected_rows.append([(row+1) for letter in letters]) - - for row in xrange(10): - - expected_rows.append([datetime(2010, ((x % 12)+1), row+1) for x in range(len(letters))]) - - for row in xrange(20): - - expected_rows.append(['=%s%d' % (letter, row+1) for letter in letters]) - - for row in expected_rows: - - ws.append(row) - - wb.save(test_filename) - - wb2 = load_workbook(test_filename, True) - - ws = wb2.worksheets[0] - - - for ex_row, ws_row in zip(expected_rows[:-20], ws.iter_rows()): - - for ex_cell, ws_cell in zip(ex_row, ws_row): - - eq_(ex_cell, ws_cell.internal_value) - - os.remove(test_filename) - - -def test_table_builder(): - - sb = StringTableBuilder() - - result = {'a':0, 'b':1, 'c':2, 'd':3} - - for letter in sorted(result.keys()): - - for x in range(5): - - sb.add(letter) - - table = dict(sb.get_table()) - - for key,idx in result.items(): - eq_(idx, table[key]) diff --git a/tablib/packages/openpyxl/tests/test_iter.py b/tablib/packages/openpyxl/tests/test_iter.py deleted file mode 100644 index a68237d..0000000 --- a/tablib/packages/openpyxl/tests/test_iter.py +++ /dev/null @@ -1,112 +0,0 @@ -# file openpyxl/tests/test_iter.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -from nose.tools import eq_, raises, assert_raises -import os.path as osp -from openpyxl.tests.helper import DATADIR -from openpyxl.reader.iter_worksheet import get_range_boundaries -from openpyxl.reader.excel import load_workbook -import datetime - -class TestWorksheet(object): - - workbook_name = osp.join(DATADIR, 'genuine', 'empty.xlsx') - -class TestText(TestWorksheet): - sheet_name = 'Sheet1 - Text' - - expected = [['This is cell A1 in Sheet 1', None, None, None, None, None, None], - [None, None, None, None, None, None, None], - [None, None, None, None, None, None, None], - [None, None, None, None, None, None, None], - [None, None, None, None, None, None, 'This is cell G5'], ] - - def test_read_fast_integrated(self): - - wb = load_workbook(filename = self.workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = self.sheet_name) - - for row, expected_row in zip(ws.iter_rows(), self.expected): - - row_values = [x.internal_value for x in row] - - eq_(row_values, expected_row) - - - def test_get_boundaries_range(self): - - eq_(get_range_boundaries('C1:C4'), (3, 1, 3, 4)) - - def test_get_boundaries_one(self): - - - eq_(get_range_boundaries('C1'), (3, 1, 4, 1)) - - def test_read_single_cell_range(self): - - wb = load_workbook(filename = self.workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = self.sheet_name) - - eq_('This is cell A1 in Sheet 1', list(ws.iter_rows('A1'))[0][0].internal_value) - -class TestIntegers(TestWorksheet): - - sheet_name = 'Sheet2 - Numbers' - - expected = [[x + 1] for x in xrange(30)] - - query_range = 'D1:E30' - - def test_read_fast_integrated(self): - - wb = load_workbook(filename = self.workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = self.sheet_name) - - for row, expected_row in zip(ws.iter_rows(self.query_range), self.expected): - - row_values = [x.internal_value for x in row] - - eq_(row_values, expected_row) - -class TestFloats(TestWorksheet): - - sheet_name = 'Sheet2 - Numbers' - query_range = 'K1:L30' - expected = expected = [[(x + 1) / 100.0] for x in xrange(30)] - -class TestDates(TestWorksheet): - - sheet_name = 'Sheet4 - Dates' - - def test_read_single_cell_date(self): - - wb = load_workbook(filename = self.workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = self.sheet_name) - - eq_(datetime.datetime(1973, 5, 20), list(ws.iter_rows('A1'))[0][0].internal_value) - eq_(datetime.datetime(1973, 5, 20, 9, 15, 2), list(ws.iter_rows('C1'))[0][0].internal_value) - - - diff --git a/tablib/packages/openpyxl/tests/test_meta.py b/tablib/packages/openpyxl/tests/test_meta.py deleted file mode 100644 index 3aefdcf..0000000 --- a/tablib/packages/openpyxl/tests/test_meta.py +++ /dev/null @@ -1,50 +0,0 @@ -# file openpyxl/tests/test_meta.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -from __future__ import with_statement -import os.path - -# package imports -from openpyxl.tests.helper import DATADIR, assert_equals_file_content -from openpyxl.writer.workbook import write_content_types, write_root_rels -from openpyxl.workbook import Workbook - - -def test_write_content_types(): - wb = Workbook() - wb.create_sheet() - wb.create_sheet() - content = write_content_types(wb) - reference_file = os.path.join(DATADIR, 'writer', 'expected', - '[Content_Types].xml') - assert_equals_file_content(reference_file, content) - - -def test_write_root_rels(): - wb = Workbook() - content = write_root_rels(wb) - reference_file = os.path.join(DATADIR, 'writer', 'expected', '.rels') - assert_equals_file_content(reference_file, content) diff --git a/tablib/packages/openpyxl/tests/test_named_range.py b/tablib/packages/openpyxl/tests/test_named_range.py deleted file mode 100644 index a56ed86..0000000 --- a/tablib/packages/openpyxl/tests/test_named_range.py +++ /dev/null @@ -1,102 +0,0 @@ -# file openpyxl/tests/test_named_range.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -from __future__ import with_statement -import os.path - -# 3rd-party imports -from nose.tools import eq_, assert_raises - -# package imports -from openpyxl.tests.helper import DATADIR -from openpyxl.namedrange import split_named_range -from openpyxl.reader.workbook import read_named_ranges -from openpyxl.shared.exc import NamedRangeException -from openpyxl.reader.excel import load_workbook - - -def test_split(): - eq_([('My Sheet', '$D$8'), ], split_named_range("'My Sheet'!$D$8")) - - -def test_split_no_quotes(): - eq_([('HYPOTHESES', '$B$3:$L$3'), ], split_named_range('HYPOTHESES!$B$3:$L$3')) - - -def test_bad_range_name(): - assert_raises(NamedRangeException, split_named_range, 'HYPOTHESES$B$3') - - -def test_read_named_ranges(): - - class DummyWs(object): - title = 'My Sheeet' - - def __str__(self): - return self.title - - class DummyWB(object): - - def get_sheet_by_name(self, name): - return DummyWs() - - with open(os.path.join(DATADIR, 'reader', 'workbook.xml')) as handle: - content = handle.read() - named_ranges = read_named_ranges(content, DummyWB()) - eq_(["My Sheeet!$D$8"], [str(range) for range in named_ranges]) - -def test_oddly_shaped_named_ranges(): - - ranges_counts = ((4, 'TEST_RANGE'), - (3, 'TRAP_1'), - (13, 'TRAP_2')) - - def check_ranges(ws, count, range_name): - - eq_(count, len(ws.range(range_name))) - - wb = load_workbook(os.path.join(DATADIR, 'genuine', 'merge_range.xlsx'), - use_iterators = False) - - ws = wb.worksheets[0] - - for count, range_name in ranges_counts: - - yield check_ranges, ws, count, range_name - - -def test_merged_cells_named_range(): - - wb = load_workbook(os.path.join(DATADIR, 'genuine', 'merge_range.xlsx'), - use_iterators = False) - - ws = wb.worksheets[0] - - cell = ws.range('TRAP_3') - - eq_('B15', cell.get_coordinate()) - - eq_(10, cell.value) diff --git a/tablib/packages/openpyxl/tests/test_number_format.py b/tablib/packages/openpyxl/tests/test_number_format.py deleted file mode 100644 index 2f5c9da..0000000 --- a/tablib/packages/openpyxl/tests/test_number_format.py +++ /dev/null @@ -1,142 +0,0 @@ -# file openpyxl/tests/test_number_format.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -from __future__ import with_statement -from datetime import datetime, date - -# 3rd party imports -from nose.tools import eq_, assert_almost_equal, assert_raises - -# package imports -from openpyxl.workbook import Workbook -from openpyxl.worksheet import Worksheet -from openpyxl.cell import Cell -from openpyxl.style import NumberFormat -from openpyxl.shared.date_time import SharedDate - - -class TestNumberFormat(object): - - @classmethod - def setup_class(cls): - cls.workbook = Workbook() - cls.worksheet = Worksheet(cls.workbook, 'Test') - cls.sd = SharedDate() - - def test_convert_date_to_julian(self): - eq_(40167, self.sd.to_julian(2009, 12, 20)) - - def test_convert_date_from_julian(self): - - def test_date_equal(julian, datetime): - - eq_(self.sd.from_julian(julian), datetime) - - date_pairs= ( - (40167, datetime(2009, 12, 20)), - (21980, datetime(1960, 03, 05)), - ) - - for count, dt in date_pairs: - yield test_date_equal, count, dt - - def test_convert_datetime_to_julian(self): - eq_(40167, self.sd.datetime_to_julian(datetime(2009, 12, 20))) - - def test_insert_float(self): - self.worksheet.cell('A1').value = 3.14 - eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) - - def test_insert_percentage(self): - self.worksheet.cell('A1').value = '3.14%' - eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) - assert_almost_equal(0.0314, self.worksheet.cell('A1').value) - - def test_insert_datetime(self): - self.worksheet.cell('A1').value = date.today() - eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) - - def test_insert_date(self): - self.worksheet.cell('A1').value = datetime.now() - eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) - - def test_internal_date(self): - dt = datetime(2010, 7, 13, 6, 37, 41) - self.worksheet.cell('A3').value = dt - eq_(40372.27616898148, self.worksheet.cell('A3')._value) - - def test_datetime_interpretation(self): - dt = datetime(2010, 7, 13, 6, 37, 41) - self.worksheet.cell('A3').value = dt - eq_(dt, self.worksheet.cell('A3').value) - - def test_date_interpretation(self): - dt = date(2010, 7, 13) - self.worksheet.cell('A3').value = dt - eq_(datetime(2010, 7, 13, 0, 0), self.worksheet.cell('A3').value) - - def test_number_format_style(self): - self.worksheet.cell('A1').value = '12.6%' - eq_(NumberFormat.FORMAT_PERCENTAGE, \ - self.worksheet.cell('A1').style.number_format.format_code) - - def test_date_format_on_non_date(self): - cell = self.worksheet.cell('A1') - - def check_date_pair(count, date_string): - cell.value = datetime.strptime(date_string, '%Y-%m-%d') - eq_(count, cell._value) - - date_pairs = ( - (15, '1900-01-15'), - (59, '1900-02-28'), - (61, '1900-03-01'), - (367, '1901-01-01'), - (2958465, '9999-12-31'), ) - for count, date_string in date_pairs: - yield check_date_pair, count, date_string - - def test_1900_leap_year(self): - assert_raises(ValueError, self.sd.from_julian, 60) - assert_raises(ValueError, self.sd.to_julian, 1900, 2, 29) - - def test_bad_date(self): - - def check_bad_date(year, month, day): - assert_raises(ValueError, self.sd.to_julian, year, month, day) - - bad_dates = ((1776, 07, 04), (1899, 12, 31), ) - for year, month, day in bad_dates: - yield check_bad_date, year, month, day - - def test_bad_julian_date(self): - assert_raises(ValueError, self.sd.from_julian, -1) - - def test_mac_date(self): - self.sd.excel_base_date = self.sd.CALENDAR_MAC_1904 - assert_raises(NotImplementedError, self.sd.to_julian, 2000, 1, 1) - assert_raises(NotImplementedError, self.sd.from_julian, 1) - self.sd.excel_base_date = self.sd.CALENDAR_WINDOWS_1900 diff --git a/tablib/packages/openpyxl/tests/test_password_hash.py b/tablib/packages/openpyxl/tests/test_password_hash.py deleted file mode 100644 index fd2fddf..0000000 --- a/tablib/packages/openpyxl/tests/test_password_hash.py +++ /dev/null @@ -1,41 +0,0 @@ -# file openpyxl/tests/test_password_hash.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# 3rd party imports -from nose.tools import eq_ - -# package imports -from openpyxl.shared.password_hasher import hash_password -from openpyxl.worksheet import SheetProtection - - -def test_hasher(): - eq_('CBEB', hash_password('test')) - - -def test_sheet_protection(): - protection = SheetProtection() - protection.password = 'test' - eq_('CBEB', protection.password) diff --git a/tablib/packages/openpyxl/tests/test_props.py b/tablib/packages/openpyxl/tests/test_props.py deleted file mode 100644 index fb9c5d0..0000000 --- a/tablib/packages/openpyxl/tests/test_props.py +++ /dev/null @@ -1,124 +0,0 @@ -# coding=utf-8 -# file openpyxl/tests/test_props.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -from __future__ import with_statement -from zipfile import ZipFile, ZIP_DEFLATED -from datetime import datetime -import os.path - -# 3rd party imports -from nose.tools import eq_ - -# package imports -from openpyxl.tests.helper import DATADIR, TMPDIR, make_tmpdir, clean_tmpdir, \ - assert_equals_file_content -from openpyxl.reader.workbook import read_properties_core, \ - read_sheets_titles, get_number_of_parts -from openpyxl.writer.workbook import write_properties_core, \ - write_properties_app -from openpyxl.shared.ooxml import ARC_APP, ARC_CORE -from openpyxl.workbook import DocumentProperties, Workbook - - -class TestReaderProps(object): - - @classmethod - def setup_class(cls): - cls.genuine_filename = os.path.join(DATADIR, 'genuine', 'empty.xlsx') - cls.archive = ZipFile(cls.genuine_filename, 'r', ZIP_DEFLATED) - - @classmethod - def teardown_class(cls): - cls.archive.close() - - def test_read_properties_core(self): - content = self.archive.read(ARC_CORE) - prop = read_properties_core(content) - eq_(prop.creator, '*.*') - eq_(prop.last_modified_by, u'Aurélien Campéas') - eq_(prop.created, datetime(2010, 4, 9, 20, 43, 12)) - eq_(prop.modified, datetime(2011, 2, 9, 13, 49, 32)) - - def test_read_sheets_titles(self): - content = self.archive.read(ARC_APP) - sheet_titles = read_sheets_titles(content) - eq_(sheet_titles, \ - ['Sheet1 - Text', 'Sheet2 - Numbers', 'Sheet3 - Formulas', 'Sheet4 - Dates']) - - -class TestReaderPropsMixed(object): - - @classmethod - def setup_class(cls): - reference_filename = \ - os.path.join(DATADIR, 'reader', 'app-multi-titles.xml') - with open(reference_filename) as handle: - cls.content = handle.read() - - def test_read_sheet_titles_mixed(self): - sheet_titles = read_sheets_titles(self.content) - eq_(sheet_titles, - ['ToC', 'ContractYear', 'ContractTier', 'Demand', - 'LinearizedFunction', 'Market', 'Transmission']) - - def test_number_of_parts(self): - parts_number = get_number_of_parts(self.content) - eq_(parts_number, - ({'Worksheets': 7, 'Named Ranges': 7}, - ['Worksheets', 'Named Ranges'])) - - -class TestWriteProps(object): - - @classmethod - def setup_class(cls): - make_tmpdir() - cls.tmp_filename = os.path.join(TMPDIR, 'test.xlsx') - cls.prop = DocumentProperties() - - @classmethod - def teardown_class(cls): - clean_tmpdir() - - def test_write_properties_core(self): - self.prop.creator = 'TEST_USER' - self.prop.last_modified_by = 'SOMEBODY' - self.prop.created = datetime(2010, 4, 1, 20, 30, 00) - self.prop.modified = datetime(2010, 4, 5, 14, 5, 30) - content = write_properties_core(self.prop) - assert_equals_file_content( - os.path.join(DATADIR, 'writer', 'expected', 'core.xml'), - content) - - def test_write_properties_app(self): - wb = Workbook() - wb.create_sheet() - wb.create_sheet() - content = write_properties_app(wb) - assert_equals_file_content( - os.path.join(DATADIR, 'writer', 'expected', 'app.xml'), - content) diff --git a/tablib/packages/openpyxl/tests/test_read.py b/tablib/packages/openpyxl/tests/test_read.py deleted file mode 100644 index eb6ab07..0000000 --- a/tablib/packages/openpyxl/tests/test_read.py +++ /dev/null @@ -1,134 +0,0 @@ -# file openpyxl/tests/test_read.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -from __future__ import with_statement -import os.path - -# 3rd party imports -from nose.tools import eq_, raises - -# package imports -from openpyxl.tests.helper import DATADIR -from openpyxl.worksheet import Worksheet -from openpyxl.workbook import Workbook -from openpyxl.style import NumberFormat, Style -from openpyxl.reader.worksheet import read_worksheet, read_dimension -from openpyxl.reader.excel import load_workbook -from openpyxl.shared.exc import InvalidFileException - - -def test_read_standalone_worksheet(): - - class DummyWb(object): - - def get_sheet_by_name(self, value): - return None - - path = os.path.join(DATADIR, 'reader', 'sheet2.xml') - with open(path) as handle: - ws = read_worksheet(handle.read(), DummyWb(), - 'Sheet 2', {1: 'hello'}, {1: Style()}) - assert isinstance(ws, Worksheet) - eq_(ws.cell('G5').value, 'hello') - eq_(ws.cell('D30').value, 30) - eq_(ws.cell('K9').value, 0.09) - - -def test_read_standard_workbook(): - path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') - wb = load_workbook(path) - assert isinstance(wb, Workbook) - -def test_read_standard_workbook_from_fileobj(): - path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') - fo = open(path, mode = 'rb') - wb = load_workbook(fo) - assert isinstance(wb, Workbook) - -def test_read_worksheet(): - path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') - wb = load_workbook(path) - sheet2 = wb.get_sheet_by_name('Sheet2 - Numbers') - assert isinstance(sheet2, Worksheet) - eq_('This is cell G5', sheet2.cell('G5').value) - eq_(18, sheet2.cell('D18').value) - - -def test_read_nostring_workbook(): - genuine_wb = os.path.join(DATADIR, 'genuine', 'empty-no-string.xlsx') - wb = load_workbook(genuine_wb) - assert isinstance(wb, Workbook) - -@raises(InvalidFileException) -def test_read_empty_file(): - - null_file = os.path.join(DATADIR, 'reader', 'null_file.xlsx') - wb = load_workbook(null_file) - -@raises(InvalidFileException) -def test_read_empty_archive(): - - null_file = os.path.join(DATADIR, 'reader', 'null_archive.xlsx') - wb = load_workbook(null_file) - -def test_read_dimension(): - - path = os.path.join(DATADIR, 'reader', 'sheet2.xml') - - with open(path) as handle: - - dimension = read_dimension(xml_source = handle.read()) - - eq_(('D', 1, 'K', 30), dimension) - -class TestReadWorkbookWithStyles(object): - - @classmethod - def setup_class(cls): - cls.genuine_wb = os.path.join(DATADIR, 'genuine', \ - 'empty-with-styles.xlsx') - wb = load_workbook(cls.genuine_wb) - cls.ws = wb.get_sheet_by_name('Sheet1') - - def test_read_general_style(self): - eq_(self.ws.cell('A1').style.number_format.format_code, - NumberFormat.FORMAT_GENERAL) - - def test_read_date_style(self): - eq_(self.ws.cell('A2').style.number_format.format_code, - NumberFormat.FORMAT_DATE_XLSX14) - - def test_read_number_style(self): - eq_(self.ws.cell('A3').style.number_format.format_code, - NumberFormat.FORMAT_NUMBER_00) - - def test_read_time_style(self): - eq_(self.ws.cell('A4').style.number_format.format_code, - NumberFormat.FORMAT_DATE_TIME3) - - def test_read_percentage_style(self): - eq_(self.ws.cell('A5').style.number_format.format_code, - NumberFormat.FORMAT_PERCENTAGE_00) diff --git a/tablib/packages/openpyxl/tests/test_strings.py b/tablib/packages/openpyxl/tests/test_strings.py deleted file mode 100644 index bf3cc57..0000000 --- a/tablib/packages/openpyxl/tests/test_strings.py +++ /dev/null @@ -1,68 +0,0 @@ -# file openpyxl/tests/test_strings.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -from __future__ import with_statement -import os.path - -# 3rd party imports -from nose.tools import eq_ - -# package imports -from openpyxl.tests.helper import DATADIR -from openpyxl.workbook import Workbook -from openpyxl.writer.strings import create_string_table -from openpyxl.reader.strings import read_string_table - - -def test_create_string_table(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('B12').value = 'hello' - ws.cell('B13').value = 'world' - ws.cell('D28').value = 'hello' - table = create_string_table(wb) - eq_({'hello': 1, 'world': 0}, table) - - -def test_read_string_table(): - with open(os.path.join(DATADIR, 'reader', 'sharedStrings.xml')) as handle: - content = handle.read() - string_table = read_string_table(content) - eq_({0: 'This is cell A1 in Sheet 1', 1: 'This is cell G5'}, string_table) - -def test_empty_string(): - with open(os.path.join(DATADIR, 'reader', 'sharedStrings-emptystring.xml')) as handle: - content = handle.read() - string_table = read_string_table(content) - eq_({0: 'Testing empty cell', 1:''}, string_table) - -def test_formatted_string_table(): - with open(os.path.join(DATADIR, 'reader', 'shared-strings-rich.xml')) \ - as handle: - content = handle.read() - string_table = read_string_table(content) - eq_({0: 'Welcome', 1: 'to the best shop in town', - 2: " let's play "}, string_table) diff --git a/tablib/packages/openpyxl/tests/test_style.py b/tablib/packages/openpyxl/tests/test_style.py deleted file mode 100644 index ee51123..0000000 --- a/tablib/packages/openpyxl/tests/test_style.py +++ /dev/null @@ -1,181 +0,0 @@ -# file openpyxl/tests/test_style.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -from __future__ import with_statement -import os.path -import datetime - -# 3rd party imports -from nose.tools import eq_, assert_false, ok_ - -# package imports -from openpyxl.tests.helper import DATADIR, assert_equals_file_content, get_xml -from openpyxl.reader.style import read_style_table -from openpyxl.workbook import Workbook -from openpyxl.style import NumberFormat -from openpyxl.writer.styles import StyleWriter -from openpyxl.style import NumberFormat, Border, Color - - -class TestCreateStyle(object): - - @classmethod - def setup_class(cls): - now = datetime.datetime.now() - cls.workbook = Workbook() - cls.worksheet = cls.workbook.create_sheet() - cls.worksheet.cell(coordinate = 'A1').value = '12.34%' - cls.worksheet.cell(coordinate = 'B4').value = now - cls.worksheet.cell(coordinate = 'B5').value = now - cls.worksheet.cell(coordinate = 'C14').value = u'This is a test' - cls.worksheet.cell(coordinate = 'D9').value = '31.31415' - cls.worksheet.cell(coordinate = 'D9').style.number_format.format_code = \ - NumberFormat.FORMAT_NUMBER_00 - cls.writer = StyleWriter(cls.workbook) - - def test_create_style_table(self): - eq_(3, len(self.writer.style_table)) - - def test_write_style_table(self): - reference_file = os.path.join(DATADIR, 'writer', 'expected', 'simple-styles.xml') - assert_equals_file_content(reference_file, self.writer.write_table()) - -class TestStyleWriter(object): - - def setUp(self): - - self.workbook = Workbook() - self.worksheet = self.workbook.create_sheet() - - def test_no_style(self): - - w = StyleWriter(self.workbook) - eq_(0, len(w.style_table)) - - def test_nb_style(self): - - for i in range(1, 6): - self.worksheet.cell(row=1, column=i).style.font.size += i - w = StyleWriter(self.workbook) - eq_(5, len(w.style_table)) - - self.worksheet.cell('A10').style.borders.top = Border.BORDER_THIN - w = StyleWriter(self.workbook) - eq_(6, len(w.style_table)) - - def test_style_unicity(self): - - for i in range(1, 6): - self.worksheet.cell(row=1, column=i).style.font.bold = True - w = StyleWriter(self.workbook) - eq_(1, len(w.style_table)) - - def test_fonts(self): - - self.worksheet.cell('A1').style.font.size = 12 - self.worksheet.cell('A1').style.font.bold = True - w = StyleWriter(self.workbook) - w._write_fonts() - eq_(get_xml(w._root), '') - - def test_fills(self): - - self.worksheet.cell('A1').style.fill.fill_type = 'solid' - self.worksheet.cell('A1').style.fill.start_color.index = Color.DARKYELLOW - w = StyleWriter(self.workbook) - w._write_fills() - eq_(get_xml(w._root), '') - - def test_borders(self): - - self.worksheet.cell('A1').style.borders.top.border_style = Border.BORDER_THIN - self.worksheet.cell('A1').style.borders.top.color.index = Color.DARKYELLOW - w = StyleWriter(self.workbook) - w._write_borders() - eq_(get_xml(w._root), '') - - def test_write_cell_xfs_1(self): - - self.worksheet.cell('A1').style.font.size = 12 - w = StyleWriter(self.workbook) - ft = w._write_fonts() - nft = w._write_number_formats() - w._write_cell_xfs(nft, ft, {}, {}) - xml = get_xml(w._root) - ok_('applyFont="1"' in xml) - ok_('applyFillId="1"' not in xml) - ok_('applyBorder="1"' not in xml) - ok_('applyAlignment="1"' not in xml) - - def test_alignment(self): - self.worksheet.cell('A1').style.alignment.horizontal = 'center' - self.worksheet.cell('A1').style.alignment.vertical = 'center' - w = StyleWriter(self.workbook) - nft = w._write_number_formats() - w._write_cell_xfs(nft,{},{},{}) - xml = get_xml(w._root) - ok_('applyAlignment="1"' in xml) - ok_('horizontal="center"' in xml) - ok_('vertical="center"' in xml) - - -#def test_format_comparisions(): -# format1 = NumberFormat() -# format2 = NumberFormat() -# format3 = NumberFormat() -# format1.format_code = 'm/d/yyyy' -# format2.format_code = 'm/d/yyyy' -# format3.format_code = 'mm/dd/yyyy' -# assert not format1 < format2 -# assert format1 < format3 -# assert format1 == format2 -# assert format1 != format3 - - -def test_builtin_format(): - format = NumberFormat() - format.format_code = '0.00' - eq_(format.builtin_format_code(2), format._format_code) - - -def test_read_style(): - reference_file = os.path.join(DATADIR, 'reader', 'simple-styles.xml') - with open(reference_file, 'r') as handle: - content = handle.read() - style_table = read_style_table(content) - eq_(4, len(style_table)) - eq_(NumberFormat._BUILTIN_FORMATS[9], - style_table[1].number_format.format_code) - eq_('yyyy-mm-dd', style_table[2].number_format.format_code) - - -def test_read_cell_style(): - reference_file = os.path.join( - DATADIR, 'reader', 'empty-workbook-styles.xml') - with open(reference_file, 'r') as handle: - content = handle.read() - style_table = read_style_table(content) - eq_(2, len(style_table)) diff --git a/tablib/packages/openpyxl/tests/test_theme.py b/tablib/packages/openpyxl/tests/test_theme.py deleted file mode 100644 index b0f6f00..0000000 --- a/tablib/packages/openpyxl/tests/test_theme.py +++ /dev/null @@ -1,37 +0,0 @@ -# file openpyxl/tests/test_theme.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -import os.path - -# package imports -from openpyxl.tests.helper import DATADIR, assert_equals_file_content -from openpyxl.writer.theme import write_theme - - -def test_write_theme(): - content = write_theme() - assert_equals_file_content( - os.path.join(DATADIR, 'writer', 'expected', 'theme1.xml'), content) diff --git a/tablib/packages/openpyxl/tests/test_workbook.py b/tablib/packages/openpyxl/tests/test_workbook.py deleted file mode 100644 index 20191d2..0000000 --- a/tablib/packages/openpyxl/tests/test_workbook.py +++ /dev/null @@ -1,148 +0,0 @@ -# file openpyxl/tests/test_workbook.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# 3rd party imports -from nose.tools import eq_, with_setup, raises -import os.path as osp - -# package imports -from openpyxl.workbook import Workbook -from openpyxl.reader.excel import load_workbook -from openpyxl.namedrange import NamedRange -from openpyxl.shared.exc import ReadOnlyWorkbookException -from openpyxl.tests.helper import TMPDIR, clean_tmpdir, make_tmpdir - -import datetime - -def test_get_active_sheet(): - wb = Workbook() - active_sheet = wb.get_active_sheet() - eq_(active_sheet, wb.worksheets[0]) - - -def test_create_sheet(): - wb = Workbook() - new_sheet = wb.create_sheet(0) - eq_(new_sheet, wb.worksheets[0]) - - -@raises(ReadOnlyWorkbookException) -def test_create_sheet_readonly(): - wb = Workbook() - wb._set_optimized_read() - wb.create_sheet() - - -def test_remove_sheet(): - wb = Workbook() - new_sheet = wb.create_sheet(0) - wb.remove_sheet(new_sheet) - assert new_sheet not in wb.worksheets - - -def test_get_sheet_by_name(): - wb = Workbook() - new_sheet = wb.create_sheet() - title = 'my sheet' - new_sheet.title = title - found_sheet = wb.get_sheet_by_name(title) - eq_(new_sheet, found_sheet) - - -def test_get_index(): - wb = Workbook() - new_sheet = wb.create_sheet(0) - sheet_index = wb.get_index(new_sheet) - eq_(sheet_index, 0) - - -def test_get_sheet_names(): - wb = Workbook() - names = ['Sheet', 'Sheet1', 'Sheet2', 'Sheet3', 'Sheet4', 'Sheet5'] - for count in range(5): - wb.create_sheet(0) - actual_names = wb.get_sheet_names() - eq_(sorted(actual_names), sorted(names)) - - -def test_get_named_ranges(): - wb = Workbook() - eq_(wb.get_named_ranges(), wb._named_ranges) - - -def test_add_named_range(): - wb = Workbook() - new_sheet = wb.create_sheet() - named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) - wb.add_named_range(named_range) - named_ranges_list = wb.get_named_ranges() - assert named_range in named_ranges_list - - -def test_get_named_range(): - wb = Workbook() - new_sheet = wb.create_sheet() - named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) - wb.add_named_range(named_range) - found_named_range = wb.get_named_range('test_nr') - eq_(named_range, found_named_range) - - -def test_remove_named_range(): - wb = Workbook() - new_sheet = wb.create_sheet() - named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) - wb.add_named_range(named_range) - wb.remove_named_range(named_range) - named_ranges_list = wb.get_named_ranges() - assert named_range not in named_ranges_list - -@with_setup(setup = make_tmpdir, teardown = clean_tmpdir) -def test_add_local_named_range(): - wb = Workbook() - new_sheet = wb.create_sheet() - named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) - named_range.local_only = True - wb.add_named_range(named_range) - dest_filename = osp.join(TMPDIR, 'local_named_range_book.xlsx') - wb.save(dest_filename) - - -@with_setup(setup = make_tmpdir, teardown = clean_tmpdir) -def test_write_regular_date(): - - today = datetime.datetime(2010, 1, 18, 14, 15, 20, 1600) - - book = Workbook() - sheet = book.get_active_sheet() - sheet.cell("A1").value = today - dest_filename = osp.join(TMPDIR, 'date_read_write_issue.xlsx') - book.save(dest_filename) - - test_book = load_workbook(dest_filename) - test_sheet = test_book.get_active_sheet() - - eq_(test_sheet.cell("A1").value, today) - diff --git a/tablib/packages/openpyxl/tests/test_worksheet.py b/tablib/packages/openpyxl/tests/test_worksheet.py deleted file mode 100644 index 1894f1d..0000000 --- a/tablib/packages/openpyxl/tests/test_worksheet.py +++ /dev/null @@ -1,258 +0,0 @@ -# file openpyxl/tests/test_worksheet.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# 3rd party imports -from nose.tools import eq_, raises, assert_raises - -# package imports -from openpyxl.workbook import Workbook -from openpyxl.worksheet import Worksheet, Relationship, flatten -from openpyxl.cell import Cell -from openpyxl.shared.exc import CellCoordinatesException, \ - SheetTitleException, InsufficientCoordinatesException, \ - NamedRangeException - - -class TestWorksheet(): - - @classmethod - def setup_class(cls): - cls.wb = Workbook() - - def test_new_worksheet(self): - ws = Worksheet(self.wb) - eq_(self.wb, ws._parent) - - def test_new_sheet_name(self): - self.wb.worksheets = [] - ws = Worksheet(self.wb, title = '') - eq_(repr(ws), '') - - def test_get_cell(self): - ws = Worksheet(self.wb) - cell = ws.cell('A1') - eq_(cell.get_coordinate(), 'A1') - - @raises(SheetTitleException) - def test_set_bad_title(self): - Worksheet(self.wb, 'X' * 50) - - def test_set_bad_title_character(self): - assert_raises(SheetTitleException, Worksheet, self.wb, '[') - assert_raises(SheetTitleException, Worksheet, self.wb, ']') - assert_raises(SheetTitleException, Worksheet, self.wb, '*') - assert_raises(SheetTitleException, Worksheet, self.wb, ':') - assert_raises(SheetTitleException, Worksheet, self.wb, '?') - assert_raises(SheetTitleException, Worksheet, self.wb, '/') - assert_raises(SheetTitleException, Worksheet, self.wb, '\\') - - def test_worksheet_dimension(self): - ws = Worksheet(self.wb) - eq_('A1:A1', ws.calculate_dimension()) - ws.cell('B12').value = 'AAA' - eq_('A1:B12', ws.calculate_dimension()) - - def test_worksheet_range(self): - ws = Worksheet(self.wb) - xlrange = ws.range('A1:C4') - assert isinstance(xlrange, tuple) - eq_(4, len(xlrange)) - eq_(3, len(xlrange[0])) - - def test_worksheet_named_range(self): - ws = Worksheet(self.wb) - self.wb.create_named_range('test_range', ws, 'C5') - xlrange = ws.range('test_range') - assert isinstance(xlrange, Cell) - eq_(5, xlrange.row) - - @raises(NamedRangeException) - def test_bad_named_range(self): - ws = Worksheet(self.wb) - ws.range('bad_range') - - @raises(NamedRangeException) - def test_named_range_wrong_sheet(self): - ws1 = Worksheet(self.wb) - ws2 = Worksheet(self.wb) - self.wb.create_named_range('wrong_sheet_range', ws1, 'C5') - ws2.range('wrong_sheet_range') - - def test_cell_offset(self): - ws = Worksheet(self.wb) - eq_('C17', ws.cell('B15').offset(2, 1).get_coordinate()) - - def test_range_offset(self): - ws = Worksheet(self.wb) - xlrange = ws.range('A1:C4', 1, 3) - assert isinstance(xlrange, tuple) - eq_(4, len(xlrange)) - eq_(3, len(xlrange[0])) - eq_('D2', xlrange[0][0].get_coordinate()) - - def test_cell_alternate_coordinates(self): - ws = Worksheet(self.wb) - cell = ws.cell(row = 8, column = 4) - eq_('E9', cell.get_coordinate()) - - @raises(InsufficientCoordinatesException) - def test_cell_insufficient_coordinates(self): - ws = Worksheet(self.wb) - cell = ws.cell(row = 8) - - def test_cell_range_name(self): - ws = Worksheet(self.wb) - self.wb.create_named_range('test_range_single', ws, 'B12') - assert_raises(CellCoordinatesException, ws.cell, 'test_range_single') - c_range_name = ws.range('test_range_single') - c_range_coord = ws.range('B12') - c_cell = ws.cell('B12') - eq_(c_range_coord, c_range_name) - eq_(c_range_coord, c_cell) - - def test_garbage_collect(self): - ws = Worksheet(self.wb) - ws.cell('A1').value = '' - ws.cell('B2').value = '0' - ws.cell('C4').value = 0 - ws.garbage_collect() - eq_(ws.get_cell_collection(), [ws.cell('B2'), ws.cell('C4')]) - - def test_hyperlink_relationships(self): - ws = Worksheet(self.wb) - eq_(len(ws.relationships), 0) - - ws.cell('A1').hyperlink = "http://test.com" - eq_(len(ws.relationships), 1) - eq_("rId1", ws.cell('A1').hyperlink_rel_id) - eq_("rId1", ws.relationships[0].id) - eq_("http://test.com", ws.relationships[0].target) - eq_("External", ws.relationships[0].target_mode) - - ws.cell('A2').hyperlink = "http://test2.com" - eq_(len(ws.relationships), 2) - eq_("rId2", ws.cell('A2').hyperlink_rel_id) - eq_("rId2", ws.relationships[1].id) - eq_("http://test2.com", ws.relationships[1].target) - eq_("External", ws.relationships[1].target_mode) - - @raises(ValueError) - def test_bad_relationship_type(self): - rel = Relationship('bad_type') - - def test_append_list(self): - ws = Worksheet(self.wb) - - ws.append(['This is A1', 'This is B1']) - - eq_('This is A1', ws.cell('A1').value) - eq_('This is B1', ws.cell('B1').value) - - def test_append_dict_letter(self): - ws = Worksheet(self.wb) - - ws.append({'A' : 'This is A1', 'C' : 'This is C1'}) - - eq_('This is A1', ws.cell('A1').value) - eq_('This is C1', ws.cell('C1').value) - - def test_append_dict_index(self): - ws = Worksheet(self.wb) - - ws.append({0 : 'This is A1', 2 : 'This is C1'}) - - eq_('This is A1', ws.cell('A1').value) - eq_('This is C1', ws.cell('C1').value) - - @raises(TypeError) - def test_bad_append(self): - ws = Worksheet(self.wb) - ws.append("test") - - def test_append_2d_list(self): - - ws = Worksheet(self.wb) - - ws.append(['This is A1', 'This is B1']) - ws.append(['This is A2', 'This is B2']) - - vals = ws.range('A1:B2') - - eq_((('This is A1', 'This is B1'), - ('This is A2', 'This is B2'),), flatten(vals)) - - def test_rows(self): - - ws = Worksheet(self.wb) - - ws.cell('A1').value = 'first' - ws.cell('C9').value = 'last' - - rows = ws.rows - - eq_(len(rows), 9) - - eq_(rows[0][0].value, 'first') - eq_(rows[-1][-1].value, 'last') - - def test_cols(self): - - ws = Worksheet(self.wb) - - ws.cell('A1').value = 'first' - ws.cell('C9').value = 'last' - - cols = ws.columns - - eq_(len(cols), 3) - - eq_(cols[0][0].value, 'first') - eq_(cols[-1][-1].value, 'last') - - def test_auto_filter(self): - ws = Worksheet(self.wb) - ws.auto_filter = ws.range('a1:f1') - assert ws.auto_filter == 'A1:F1' - - ws.auto_filter = '' - assert ws.auto_filter is None - - ws.auto_filter = 'c1:g9' - assert ws.auto_filter == 'C1:G9' - - def test_freeze(self): - ws = Worksheet(self.wb) - ws.freeze_panes = ws.cell('b2') - assert ws.freeze_panes == 'B2' - - ws.freeze_panes = '' - assert ws.freeze_panes is None - - ws.freeze_panes = 'c5' - assert ws.freeze_panes == 'C5' - - ws.freeze_panes = ws.cell('A1') - assert ws.freeze_panes is None - diff --git a/tablib/packages/openpyxl/tests/test_write.py b/tablib/packages/openpyxl/tests/test_write.py deleted file mode 100644 index 834e73c..0000000 --- a/tablib/packages/openpyxl/tests/test_write.py +++ /dev/null @@ -1,203 +0,0 @@ -# file openpyxl/tests/test_write.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -from __future__ import with_statement -from StringIO import StringIO -import os.path - -# 3rd party imports -from nose.tools import eq_, with_setup, raises - -# package imports -from openpyxl.tests.helper import TMPDIR, DATADIR, \ - assert_equals_file_content, clean_tmpdir, make_tmpdir -from openpyxl.workbook import Workbook -from openpyxl.reader.excel import load_workbook -from openpyxl.writer.excel import save_workbook, save_virtual_workbook, \ - ExcelWriter -from openpyxl.writer.workbook import write_workbook, write_workbook_rels -from openpyxl.writer.worksheet import write_worksheet, write_worksheet_rels -from openpyxl.writer.strings import write_string_table -from openpyxl.writer.styles import StyleWriter - - -@with_setup(setup = make_tmpdir, teardown = clean_tmpdir) -def test_write_empty_workbook(): - wb = Workbook() - dest_filename = os.path.join(TMPDIR, 'empty_book.xlsx') - save_workbook(wb, dest_filename) - assert os.path.isfile(dest_filename) - - -def test_write_virtual_workbook(): - old_wb = Workbook() - saved_wb = save_virtual_workbook(old_wb) - new_wb = load_workbook(StringIO(saved_wb)) - assert new_wb - - -def test_write_workbook_rels(): - wb = Workbook() - content = write_workbook_rels(wb) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'workbook.xml.rels'), content) - - -def test_write_workbook(): - wb = Workbook() - content = write_workbook(wb) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'workbook.xml'), content) - - -def test_write_string_table(): - table = {'hello': 1, 'world': 2, 'nice': 3} - content = write_string_table(table) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sharedStrings.xml'), content) - - -def test_write_worksheet(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F42').value = 'hello' - content = write_worksheet(ws, {'hello': 0}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1.xml'), content) - - -def test_write_hidden_worksheet(): - wb = Workbook() - ws = wb.create_sheet() - ws.sheet_state = ws.SHEETSTATE_HIDDEN - ws.cell('F42').value = 'hello' - content = write_worksheet(ws, {'hello': 0}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1.xml'), content) - - -def test_write_formula(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F1').value = 10 - ws.cell('F2').value = 32 - ws.cell('F3').value = '=F1+F2' - content = write_worksheet(ws, {}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_formula.xml'), content) - - -def test_write_style(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F1').value = '13%' - style_id_by_hash = StyleWriter(wb).get_style_by_hash() - content = write_worksheet(ws, {}, style_id_by_hash) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_style.xml'), content) - - -def test_write_height(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F1').value = 10 - ws.row_dimensions[ws.cell('F1').row].height = 30 - content = write_worksheet(ws, {}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_height.xml'), content) - - -def test_write_hyperlink(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('A1').value = "test" - ws.cell('A1').hyperlink = "http://test.com" - content = write_worksheet(ws, {'test': 0}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_hyperlink.xml'), content) - - -def test_write_hyperlink_rels(): - wb = Workbook() - ws = wb.create_sheet() - eq_(0, len(ws.relationships)) - ws.cell('A1').value = "test" - ws.cell('A1').hyperlink = "http://test.com/" - eq_(1, len(ws.relationships)) - ws.cell('A2').value = "test" - ws.cell('A2').hyperlink = "http://test2.com/" - eq_(2, len(ws.relationships)) - content = write_worksheet_rels(ws, 1) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_hyperlink.xml.rels'), content) - - -def test_hyperlink_value(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('A1').hyperlink = "http://test.com" - eq_("http://test.com", ws.cell('A1').value) - ws.cell('A1').value = "test" - eq_("test", ws.cell('A1').value) - -def test_write_auto_filter(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F42').value = 'hello' - ws.auto_filter = 'A1:F1' - content = write_worksheet(ws, {'hello': 0}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_auto_filter.xml'), content) - -def test_freeze_panes_horiz(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F42').value = 'hello' - ws.freeze_panes = 'A4' - content = write_worksheet(ws, {'hello': 0}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_freeze_panes_horiz.xml'), content) - -def test_freeze_panes_vert(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F42').value = 'hello' - ws.freeze_panes = 'D1' - content = write_worksheet(ws, {'hello': 0}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_freeze_panes_vert.xml'), content) - pass - -def test_freeze_panes_both(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F42').value = 'hello' - ws.freeze_panes = 'D4' - content = write_worksheet(ws, {'hello': 0}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_freeze_panes_both.xml'), content) - - diff --git a/tablib/packages/openpyxl3/tests/__init__.py b/tablib/packages/openpyxl3/tests/__init__.py deleted file mode 100644 index de89032..0000000 --- a/tablib/packages/openpyxl3/tests/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# file openpyxl/tests/__init__.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni diff --git a/tablib/packages/openpyxl3/tests/helper.py b/tablib/packages/openpyxl3/tests/helper.py deleted file mode 100644 index 9dfbfaf..0000000 --- a/tablib/packages/openpyxl3/tests/helper.py +++ /dev/null @@ -1,94 +0,0 @@ -# file openpyxl/tests/helper.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports - -import os -import os.path -import shutil -import difflib -from io import StringIO -from pprint import pprint -from tempfile import gettempdir - -# package imports -from openpyxl.shared.xmltools import fromstring, ElementTree -from openpyxl.shared.xmltools import pretty_indent - -# constants -DATADIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test_data')) -TMPDIR = os.path.join(gettempdir(), 'openpyxl_test_temp') - - -def make_tmpdir(): - try: - os.makedirs(TMPDIR) - except OSError: - pass - - -def clean_tmpdir(): - if os.path.isdir(TMPDIR): - shutil.rmtree(TMPDIR, ignore_errors = True) - - -def assert_equals_file_content(reference_file, fixture, filetype = 'xml'): - if os.path.isfile(fixture): - with open(fixture) as fixture_file: - fixture_content = fixture_file.read() - else: - fixture_content = fixture - - with open(reference_file) as expected_file: - expected_content = expected_file.read() - - if filetype == 'xml': - fixture_content = fromstring(fixture_content) - pretty_indent(fixture_content) - temp = StringIO() - ElementTree(fixture_content).write(temp) - fixture_content = temp.getvalue() - - expected_content = fromstring(expected_content) - pretty_indent(expected_content) - temp = StringIO() - ElementTree(expected_content).write(temp) - expected_content = temp.getvalue() - - fixture_lines = fixture_content.split('\n') - expected_lines = expected_content.split('\n') - differences = list(difflib.unified_diff(expected_lines, fixture_lines)) - if differences: - temp = StringIO() - pprint(differences, stream = temp) - assert False, 'Differences found : %s' % temp.getvalue() - -def get_xml(xml_node): - - io = StringIO() - ElementTree(xml_node).write(io, encoding = 'UTF-8') - ret = io.getvalue() - io.close() - return ret.replace('\n', '') diff --git a/tablib/packages/openpyxl3/tests/test_cell.py b/tablib/packages/openpyxl3/tests/test_cell.py deleted file mode 100644 index 07f0b6f..0000000 --- a/tablib/packages/openpyxl3/tests/test_cell.py +++ /dev/null @@ -1,200 +0,0 @@ -# file openpyxl/tests/test_cell.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -from datetime import time, datetime - -# 3rd party imports -from nose.tools import eq_, raises, assert_raises - -# package imports -from openpyxl.worksheet import Worksheet -from openpyxl.workbook import Workbook -from openpyxl.shared.exc import ColumnStringIndexException, \ - CellCoordinatesException, DataTypeException -from openpyxl.cell import column_index_from_string, \ - coordinate_from_string, get_column_letter, Cell, absolute_coordinate - - -def test_coordinates(): - column, row = coordinate_from_string('ZF46') - eq_("ZF", column) - eq_(46, row) - - -@raises(CellCoordinatesException) -def test_invalid_coordinate(): - coordinate_from_string('AAA') - - -def test_absolute(): - eq_('$ZF$51', absolute_coordinate('ZF51')) - -def test_absolute_multiple(): - - eq_('$ZF$51:$ZF$53', absolute_coordinate('ZF51:ZF$53')) - - -def test_column_index(): - eq_(10, column_index_from_string('J')) - eq_(270, column_index_from_string('jJ')) - eq_(7030, column_index_from_string('jjj')) - - -def test_bad_column_index(): - - @raises(ColumnStringIndexException) - def _check(bad_string): - column_index_from_string(bad_string) - - bad_strings = ('JJJJ', '', '$', '1',) - for bad_string in bad_strings: - yield _check, bad_string - - -def test_column_letter_boundries(): - assert_raises(ColumnStringIndexException, get_column_letter, 0) - assert_raises(ColumnStringIndexException, get_column_letter, 18279) - - -def test_column_letter(): - eq_('ZZZ', get_column_letter(18278)) - eq_('JJJ', get_column_letter(7030)) - eq_('AB', get_column_letter(28)) - eq_('AA', get_column_letter(27)) - eq_('Z', get_column_letter(26)) - - -def test_initial_value(): - cell = Cell(None, 'A', 1, value = '17.5') - eq_(cell.TYPE_NUMERIC, cell.data_type) - - -class TestCellValueTypes(): - - @classmethod - def setup_class(cls): - cls.cell = Cell(None, 'A', 1) - - def test_1st(self): - eq_(self.cell.TYPE_NULL, self.cell.data_type) - - def test_null(self): - self.cell.value = None - eq_(self.cell.TYPE_NULL, self.cell.data_type) - - def test_numeric(self): - - def check_numeric(value): - self.cell.value = value - eq_(self.cell.TYPE_NUMERIC, self.cell.data_type) - - values = (42, '4.2', '-42.000', '0', 0, 0.0001, '0.9999', '99E-02') - for value in values: - yield check_numeric, value - - def test_string(self): - self.cell.value = 'hello' - eq_(self.cell.TYPE_STRING, self.cell.data_type) - - def test_formula(self): - self.cell.value = '=42' - eq_(self.cell.TYPE_FORMULA, self.cell.data_type) - - def test_boolean(self): - self.cell.value = True - eq_(self.cell.TYPE_BOOL, self.cell.data_type) - self.cell.value = False - eq_(self.cell.TYPE_BOOL, self.cell.data_type) - - def test_error_codes(self): - - def check_error(): - eq_(self.cell.TYPE_ERROR, self.cell.data_type) - - for error_string in list(self.cell.ERROR_CODES.keys()): - self.cell.value = error_string - yield check_error - - -def test_data_type_check(): - cell = Cell(None, 'A', 1) - cell.bind_value(None) - eq_(Cell.TYPE_NULL, cell._data_type) - - cell.bind_value('.0e000') - eq_(Cell.TYPE_NUMERIC, cell._data_type) - - cell.bind_value('-0.e-0') - eq_(Cell.TYPE_NUMERIC, cell._data_type) - - cell.bind_value('1E') - eq_(Cell.TYPE_STRING, cell._data_type) - -@raises(DataTypeException) -def test_set_bad_type(): - cell = Cell(None, 'A', 1) - cell.set_value_explicit(1, 'q') - - -def test_time(): - - def check_time(raw_value, coerced_value): - cell.value = raw_value - eq_(cell.value, coerced_value) - eq_(cell.TYPE_NUMERIC, cell.data_type) - - wb = Workbook() - ws = Worksheet(wb) - cell = Cell(ws, 'A', 1) - values = (('03:40:16', time(3, 40, 16)), ('03:40', time(3, 40)),) - for raw_value, coerced_value in values: - yield check_time, raw_value, coerced_value - - -def test_date_format_on_non_date(): - wb = Workbook() - ws = Worksheet(wb) - cell = Cell(ws, 'A', 1) - cell.value = datetime.now() - cell.value = 'testme' - eq_('testme', cell.value) - - -def test_repr(): - wb = Workbook() - ws = Worksheet(wb) - cell = Cell(ws, 'A', 1) - eq_(repr(cell), '', 'Got bad repr: %s' % repr(cell)) - -def test_is_date(): - wb = Workbook() - ws = Worksheet(wb) - cell = Cell(ws, 'A', 1) - cell.value = datetime.now() - eq_(cell.is_date(), True) - cell.value = 'testme' - eq_('testme', cell.value) - eq_(cell.is_date(), False) diff --git a/tablib/packages/openpyxl3/tests/test_chart.py b/tablib/packages/openpyxl3/tests/test_chart.py deleted file mode 100644 index 09a7bbc..0000000 --- a/tablib/packages/openpyxl3/tests/test_chart.py +++ /dev/null @@ -1,131 +0,0 @@ -# file openpyxl/tests/test_chart.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -from nose.tools import eq_ - -from openpyxl.tests.helper import get_xml -from openpyxl.shared.xmltools import Element -from openpyxl.writer.charts import ChartWriter -from openpyxl.workbook import Workbook -from openpyxl.chart import BarChart, ScatterChart, Serie, Reference -from openpyxl.style import Color - -class TestChartWriter(object): - - def setUp(self): - - wb = Workbook() - ws = wb.get_active_sheet() - ws.title = 'data' - for i in range(10): - ws.cell(row = i, column = 0).value = i - self.chart = BarChart() - self.chart.title = 'TITLE' - self.chart.add_serie(Serie(Reference(ws, (0, 0), (10, 0)))) - self.chart._series[-1].color = Color.GREEN - self.cw = ChartWriter(self.chart) - self.root = Element('test') - - def test_write_title(self): - self.cw._write_title(self.root) - eq_(get_xml(self.root), 'TITLE') - - def test_write_xaxis(self): - - self.cw._write_axis(self.root, self.chart.x_axis, 'c:catAx') - eq_(get_xml(self.root), '') - - def test_write_yaxis(self): - - self.cw._write_axis(self.root, self.chart.y_axis, 'c:valAx') - eq_(get_xml(self.root), '') - - def test_write_series(self): - - self.cw._write_series(self.root) - eq_(get_xml(self.root), 'data!$A$1:$A$11General0123456789None') - - def test_write_legend(self): - - self.cw._write_legend(self.root) - eq_(get_xml(self.root), '') - - def test_write_print_settings(self): - - self.cw._write_print_settings(self.root) - eq_(get_xml(self.root), '') - - def test_write_chart(self): - - self.cw._write_chart(self.root) - eq_(get_xml(self.root), 'TITLEdata!$A$1:$A$11General0123456789None') - - -class TestScatterChartWriter(object): - - def setUp(self): - - wb = Workbook() - ws = wb.get_active_sheet() - ws.title = 'data' - for i in range(10): - ws.cell(row = i, column = 0).value = i - ws.cell(row = i, column = 1).value = i - self.scatterchart = ScatterChart() - self.scatterchart.add_serie(Serie(Reference(ws, (0, 0), (10, 0)), - xvalues = Reference(ws, (0, 1), (10, 1)))) - self.cw = ChartWriter(self.scatterchart) - self.root = Element('test') - - def test_write_xaxis(self): - - self.cw._write_axis(self.root, self.scatterchart.x_axis, 'c:valAx') - eq_(get_xml(self.root), '') - - - def test_write_yaxis(self): - - self.cw._write_axis(self.root, self.scatterchart.y_axis, 'c:valAx') - eq_(get_xml(self.root), '') - - def test_write_series(self): - - self.cw._write_series(self.root) - eq_(get_xml(self.root), 'data!$B$1:$B$11General0123456789Nonedata!$A$1:$A$11General0123456789None') - - def test_write_legend(self): - - self.cw._write_legend(self.root) - eq_(get_xml(self.root), '') - - def test_write_print_settings(self): - - self.cw._write_print_settings(self.root) - eq_(get_xml(self.root), '') - - def test_write_chart(self): - - self.cw._write_chart(self.root) - eq_(get_xml(self.root), 'data!$B$1:$B$11General0123456789Nonedata!$A$1:$A$11General0123456789None') diff --git a/tablib/packages/openpyxl3/tests/test_data/genuine/empty-no-string.xlsx b/tablib/packages/openpyxl3/tests/test_data/genuine/empty-no-string.xlsx deleted file mode 100644 index 39490ac32434e02546ef2a888f12dedf4f5b58f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7842 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 diff --git a/tablib/packages/openpyxl3/tests/test_data/genuine/empty-with-styles.xlsx b/tablib/packages/openpyxl3/tests/test_data/genuine/empty-with-styles.xlsx deleted file mode 100644 index 250e9b4365699542af00bce79d17024ea1ae1131..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7511 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 diff --git a/tablib/packages/openpyxl3/tests/test_data/genuine/empty.xlsx b/tablib/packages/openpyxl3/tests/test_data/genuine/empty.xlsx deleted file mode 100644 index 3749d02093e170795608aa9c00a2b8e4e2be3a8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11005 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

j@~^R=nw%o zamN={t&gS>+}A@l(K9uDi*fVzemvj`*o||dR~OhH>^%>&+o01&!gZ+AQ6k`Qz;wCm zd?T5I9xY-;fP&~)q~XyP;ZlP~9b|A-P~N4g|6!YvpFZ8VN`?KjEI%eAkB2Z#+-d!) zB=;&iE;rJykmQq{HY4i;yc?*%{KP~9l?Dr;;Zg22!z;g)(QXk{Qvi6vtO8C#V*Vi`Co^MfeYfaNY2Et<_W$MmHV>zIqV9rwI8?# zkrVDuEtiQ6%a=XFE;44E0`C;2u?Qt-=)+Elniz_gj6l8ccKWXM^UO5DvMer3y@9p9 z&dS{v=iPu)r_mFvE?QH~{aLLyE)F-aol~rpv-{4T^_kfadMj{p6%69(-yb9@5+)e@ z4mMa1ZR(XOT7E#@f2T^*%91ah02NLbQGhLmc501iyEeg>*4PUeIn~8m=B`+DM31U$-^aac zm5aUWgpJVg7}5u)yPb*C9#Td|(>11;!1eBw6`#xPS<(WbY@hS9Q`{{#jo!zb9-5B3 zJ9i#5Rx|R6$d@^Hmj^?URf6~TEdx|1*8Y;>KVl`s^0hXf5uZGBVhgdSqM@*F$L>K> zF@-_#cY)u{Mj&3T;4v|7La~o_eItBI3-vZ<$<>HB$e_p!ew$E#6L7_CO3VaAwi7*n zq3*^V8R+oM;B_yto|k%mOQhjIeVYcP8XF>T;BqOJ+u(?eh@L1mm9}vEM`A3-;gQnb zob$Vz02x809TIV3Ss6LjLJFE#oJbdQXrHu{U8G7EtB9njIboNvHyRp|wNx~K2ide;AM75xx?2KG zPX+zeb(h_irONX$TpZeU^p6gJG@>7?-t6Lrx zEW@WUt}$SOB69(H6^^?Xt5r@%13-RS{t3X@~0~0k( z z)hcip-|!HE)W!iuf>(Eu z1oy?JVsL!yjW6Hs=Fxp;GdZBUWs(gRWZMF%B79=On zvXV&iHeL&viro!MK{jH@@w?A$QM@UyIIdJt0+^$gdqtY7Nt-XkCiKq{t9=od%Rt~8fHvcBx znm?8)*2NAwI)S*Zt58~FvZx}VL9;MJ=Y1c;@6|4~I`B(ki{a({AeB9OxZdZ4Ro7Qt!sN|TtOsV|yz7wVL`)xK7jee=< z_^r$G6UZ%R1xG$Y|9PhI%Wq|>EZP(>qj~F^KX#pM0O4b;7N0?=C6+A#D>xTqZmmYp z%71h(gcSOA1WKFLdzW59q~be{X?sz(2)>Z^0H^Y@CXGl`Ak#B|J38{dkIh*XwD#G% z5z+ziq|bP=n51sth%*0tT)jwEpUKVisIx{NCity1{O= zhU7XOIv8Fx`JPRO&f(p;0Qi>j_^uX^p?VIzAM;s@qXTQZku$-&vuJHl@oaT<yyy&E|SauE}W9$LS~rgCB6 zoLjCVk4Sufx?J*cHn$hzL)5km(8BO;zRSLQLWrBRoo4jI249jqLPkM%zS40M4SxVd zXf@}D$pxp^cS6mBQ!Gn@{Be>hc}AyW?Ur)}8(lsRL-VI;~Ue`+Y41Gvo&NpPt?Bd)__zRmL}~#Ep&wI#IGs~UW8Ds zvx7d2dN@CV`(5iRPyPMhfOFv{U`s&<*VK$`4HX@1?Hpek+Bz8jY(823e@kiLUIlo? zPXK$Eu|kevR`NC-K-AWuO(2&Yqc1o0!*@9MTs`eYX?Txj2B-#uRc zcoePOefzqL22X3|g`~ZhA|PTmUv;0TjbVF@5uw}=SY7rywg5_Q4_O{tZW)YrNJH(LHCh_OU4+~P??#yP?*&e!P{4hmqs-Xv<9g(;?6|cmc zL=cuKzR##K3fm!JODT@6b4Q%MoAr;o91nht&uzSmvf#?wM_dbY+`HV?Xl(oS&}D+j z+VIC6i;ot+V53@(GDqXOUxcjPXJqC#83tIn$1zqdbI4r-dSRdzu= z!`kvSAc`*}@24YHFyp1nh7ixVJu}7n{WL=vLfp3rtj1Ju^$qQ>YSg#0`>z;(%J7dZ zGoi}{T!suehPp%vI10~VzzY$6%~FWyq236v_k3f_i$g6FA_g~sqO>cd4}D;2Al=F>8Bjai;%dJM&TOX|WgIZMTNl7J_kGU{ntI8Q=A3;2g|~urO4d z5&|yUTXN2Q%JAj@%|4!BVsRj;MX(c6Xm85<3AK9a`Th1PZHr~Yl!@%Up`!(>&3qlS8=F zu7MUC!tpk7Gz}S->>#2L_L9-0xuHEB@5bji=|Ank7|90f1VDsAs!V+)!@kuxQtkAO zt?J(t=;;(#J)j3@SK|+x=NpdD+f}K@uS;rmfco!sFT%yN7BXhaxK!ztm*|7SjlL}F zB3pYc(YhA3SIxL?`1BQ1U+LrzGZf3$RH^y}^!tXd5Uu<7<5{H`snEIlt>?QF@3@?} zyO}xEWENk1M|3?6-@Gi`Bn5ZdA6X~}NJenR>Guok{@uI(KL2K!oubU&3I4wP=ik7e z=M1n3{be!GQ{dm%Qv4Md3ZCZw=am&td7jqG|76++uN{0+J%0**T5kOlyo>O^;C~cf zpRzoyUH!?j4$gS}s|kN)`Blk!%JMWZ{U?hI#&21ErKz8?JWVYA$ua@XGyX2k_>|#k z!tPIo7VJM5{x^O16#6tt^CuJ%oN5RE`O}omQ-Y@<^PdFuB)|OzzXIr|EYnW{qCZ)P z$$zo@Ej)UP{(C_3Cl&x$1f%~Eo;-#B-GlrUZch0Z_`m##q6`dJ?EnA@_!kQ{m^U;( Gul@&`W-g-u diff --git a/tablib/packages/openpyxl3/tests/test_data/genuine/merge_range.xlsx b/tablib/packages/openpyxl3/tests/test_data/genuine/merge_range.xlsx deleted file mode 100644 index 9452e362b623108660651ae51b199adb3d059133..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8742 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 diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/app-multi-titles.xml b/tablib/packages/openpyxl3/tests/test_data/reader/app-multi-titles.xml deleted file mode 100644 index b32ec40..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/reader/app-multi-titles.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - 0 - false - - - - Worksheets - - - 7 - - - Named Ranges - - - 7 - - - - - - ToC - ContractYear - ContractTier - Demand - LinearizedFunction - Market - Transmission - Excel_BuiltIn_Print_Area_10 - Excel_BuiltIn_Print_Area_12 - Excel_BuiltIn_Print_Area_13 - TS_discount_rate!Excel_BuiltIn_Print_Area_26 - Excel_BuiltIn_Print_Area_27 - Excel_BuiltIn_Print_Area_28 - Excel_BuiltIn_Print_Area_29 - - - false - false - false - 12.0000 - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/empty-workbook-styles.xml b/tablib/packages/openpyxl3/tests/test_data/reader/empty-workbook-styles.xml deleted file mode 100644 index 4819c45..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/reader/empty-workbook-styles.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/merged-ranges.xml b/tablib/packages/openpyxl3/tests/test_data/reader/merged-ranges.xml deleted file mode 100644 index 0cb17cf..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/reader/merged-ranges.xml +++ /dev/null @@ -1,2 +0,0 @@ - -12340123464646546464610 \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/null_archive.xlsx b/tablib/packages/openpyxl3/tests/test_data/reader/null_archive.xlsx deleted file mode 100644 index 967263ab8a68d2f5626d2ba7b24575fa340bf924..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 126 zcmWIWW@h1H0D+T5%64D|l;8u>d8Iiy@oAYksd^PT#T5bGj7%a7xK)ERGBPNDC?G)B S%gP24WduSSAgu%9FaQ96VG&#a diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/null_file.xlsx b/tablib/packages/openpyxl3/tests/test_data/reader/null_file.xlsx deleted file mode 100644 index e69de29..0000000 diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/shared-strings-rich.xml b/tablib/packages/openpyxl3/tests/test_data/reader/shared-strings-rich.xml deleted file mode 100644 index b3b31f6..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/reader/shared-strings-rich.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - Welcome - - - - to the best - - - - - - - - - shop in - - - - - - - - - - town - - - - let's play - - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings-emptystring.xml b/tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings-emptystring.xml deleted file mode 100644 index 1dd7caf..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings-emptystring.xml +++ /dev/null @@ -1,3 +0,0 @@ - -Testing empty cell - diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings.xml b/tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings.xml deleted file mode 100644 index aef87a0..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/reader/sharedStrings.xml +++ /dev/null @@ -1,2 +0,0 @@ - -This is cell A1 in Sheet 1This is cell G5 \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/sheet2.xml b/tablib/packages/openpyxl3/tests/test_data/reader/sheet2.xml deleted file mode 100644 index 0176e57..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/reader/sheet2.xml +++ /dev/null @@ -1,2 +0,0 @@ - -10.0120.0230.0340.04510.0560.0677.0000000000000007E-280.0890.09100.1110.11120.12130.13140.14000000000000001150.15160.16170.17180.18190.19200.2210.21220.22230.23240.24250.25260.26270.27280.28000000000000003290.28999999999999998300.3 \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/simple-styles.xml b/tablib/packages/openpyxl3/tests/test_data/reader/simple-styles.xml deleted file mode 100644 index 2f3b255..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/reader/simple-styles.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/reader/workbook.xml b/tablib/packages/openpyxl3/tests/test_data/reader/workbook.xml deleted file mode 100644 index 2697d4c..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/reader/workbook.xml +++ /dev/null @@ -1,2 +0,0 @@ - -'My Sheeet'!$D$8 \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/.rels b/tablib/packages/openpyxl3/tests/test_data/writer/expected/.rels deleted file mode 100644 index 450d174..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/.rels +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/[Content_Types].xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/[Content_Types].xml deleted file mode 100644 index 39285bb..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/[Content_Types].xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/app.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/app.xml deleted file mode 100644 index a7cccf7..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/app.xml +++ /dev/null @@ -1,2 +0,0 @@ - -Microsoft Excel0falsefalsefalsefalse12.0000Worksheets3SheetSheet1Sheet2 \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/core.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/core.xml deleted file mode 100644 index 4a99a55..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/core.xml +++ /dev/null @@ -1,2 +0,0 @@ - -TEST_USERSOMEBODY2010-04-01T20:30:00Z2010-04-05T14:05:30Z \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/font.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/font.xml deleted file mode 100644 index dabe15d..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/font.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sharedStrings.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sharedStrings.xml deleted file mode 100644 index ff7ec02..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sharedStrings.xml +++ /dev/null @@ -1,2 +0,0 @@ - -helloworldnice \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1.xml deleted file mode 100644 index ab96c9f..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - 0 - - - - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_auto_filter.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_auto_filter.xml deleted file mode 100644 index b4178b8..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_auto_filter.xml +++ /dev/null @@ -1 +0,0 @@ -0 \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_formula.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_formula.xml deleted file mode 100644 index ed98da2..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_formula.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - 10 - - - - - 32 - - - - - F1+F2 - - - - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_both.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_both.xml deleted file mode 100644 index a83d2a0..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_both.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - 0 - - - - diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_horiz.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_horiz.xml deleted file mode 100644 index f5aa23f..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_horiz.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - 0 - - - - diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_vert.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_vert.xml deleted file mode 100644 index 12ca034..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_freeze_panes_vert.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - 0 - - - - diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_height.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_height.xml deleted file mode 100644 index 428bbfa..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_height.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - 10 - - - - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml deleted file mode 100644 index 70e4eb4..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - 0 - - - - - - - - diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml.rels b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml.rels deleted file mode 100644 index 843c040..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_hyperlink.xml.rels +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_style.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_style.xml deleted file mode 100644 index 27af47e..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/sheet1_style.xml +++ /dev/null @@ -1 +0,0 @@ -0.13 \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/simple-styles.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/simple-styles.xml deleted file mode 100644 index 2f3b255..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/simple-styles.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/styles.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/styles.xml deleted file mode 100644 index 6a1ffd1..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/styles.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/theme1.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/theme1.xml deleted file mode 100644 index 012e249..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/theme1.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml b/tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml deleted file mode 100644 index 0e397a7..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml.rels b/tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml.rels deleted file mode 100644 index c41181c..0000000 --- a/tablib/packages/openpyxl3/tests/test_data/writer/expected/workbook.xml.rels +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/tablib/packages/openpyxl3/tests/test_dump.py b/tablib/packages/openpyxl3/tests/test_dump.py deleted file mode 100644 index 714cfb3..0000000 --- a/tablib/packages/openpyxl3/tests/test_dump.py +++ /dev/null @@ -1,109 +0,0 @@ - -# file openpyxl/tests/test_dump.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -from datetime import time, datetime - -# 3rd party imports -from nose.tools import eq_, raises, assert_raises - -from openpyxl.workbook import Workbook -from openpyxl.cell import get_column_letter - -from openpyxl.reader.excel import load_workbook - -from openpyxl.writer.strings import StringTableBuilder - -from tempfile import NamedTemporaryFile -import os -import shutil - -def test_dump_sheet(): - - test_file = NamedTemporaryFile(prefix='openpyxl.', suffix='.xlsx', delete=False) - test_file.close() - test_filename = test_file.name - - wb = Workbook(optimized_write = True) - - ws = wb.create_sheet() - - letters = [get_column_letter(x+1) for x in range(20)] - - expected_rows = [] - - for row in range(20): - - expected_rows.append(['%s%d' % (letter, row+1) for letter in letters]) - - for row in range(20): - - expected_rows.append([(row+1) for letter in letters]) - - for row in range(10): - - expected_rows.append([datetime(2010, ((x % 12)+1), row+1) for x in range(len(letters))]) - - for row in range(20): - - expected_rows.append(['=%s%d' % (letter, row+1) for letter in letters]) - - for row in expected_rows: - - ws.append(row) - - wb.save(test_filename) - - wb2 = load_workbook(test_filename, True) - - ws = wb2.worksheets[0] - - - for ex_row, ws_row in zip(expected_rows[:-20], ws.iter_rows()): - - for ex_cell, ws_cell in zip(ex_row, ws_row): - - eq_(ex_cell, ws_cell.internal_value) - - os.remove(test_filename) - - -def test_table_builder(): - - sb = StringTableBuilder() - - result = {'a':0, 'b':1, 'c':2, 'd':3} - - for letter in sorted(result.keys()): - - for x in range(5): - - sb.add(letter) - - table = dict(sb.get_table()) - - for key,idx in result.items(): - eq_(idx, table[key]) diff --git a/tablib/packages/openpyxl3/tests/test_iter.py b/tablib/packages/openpyxl3/tests/test_iter.py deleted file mode 100644 index 899f8d7..0000000 --- a/tablib/packages/openpyxl3/tests/test_iter.py +++ /dev/null @@ -1,112 +0,0 @@ -# file openpyxl/tests/test_iter.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -from nose.tools import eq_, raises, assert_raises -import os.path as osp -from openpyxl.tests.helper import DATADIR -from openpyxl.reader.iter_worksheet import get_range_boundaries -from openpyxl.reader.excel import load_workbook -import datetime - -class TestWorksheet(object): - - workbook_name = osp.join(DATADIR, 'genuine', 'empty.xlsx') - -class TestText(TestWorksheet): - sheet_name = 'Sheet1 - Text' - - expected = [['This is cell A1 in Sheet 1', None, None, None, None, None, None], - [None, None, None, None, None, None, None], - [None, None, None, None, None, None, None], - [None, None, None, None, None, None, None], - [None, None, None, None, None, None, 'This is cell G5'], ] - - def test_read_fast_integrated(self): - - wb = load_workbook(filename = self.workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = self.sheet_name) - - for row, expected_row in zip(ws.iter_rows(), self.expected): - - row_values = [x.internal_value for x in row] - - eq_(row_values, expected_row) - - - def test_get_boundaries_range(self): - - eq_(get_range_boundaries('C1:C4'), (3, 1, 3, 4)) - - def test_get_boundaries_one(self): - - - eq_(get_range_boundaries('C1'), (3, 1, 4, 1)) - - def test_read_single_cell_range(self): - - wb = load_workbook(filename = self.workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = self.sheet_name) - - eq_('This is cell A1 in Sheet 1', list(ws.iter_rows('A1'))[0][0].internal_value) - -class TestIntegers(TestWorksheet): - - sheet_name = 'Sheet2 - Numbers' - - expected = [[x + 1] for x in range(30)] - - query_range = 'D1:E30' - - def test_read_fast_integrated(self): - - wb = load_workbook(filename = self.workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = self.sheet_name) - - for row, expected_row in zip(ws.iter_rows(self.query_range), self.expected): - - row_values = [x.internal_value for x in row] - - eq_(row_values, expected_row) - -class TestFloats(TestWorksheet): - - sheet_name = 'Sheet2 - Numbers' - query_range = 'K1:L30' - expected = expected = [[(x + 1) / 100.0] for x in range(30)] - -class TestDates(TestWorksheet): - - sheet_name = 'Sheet4 - Dates' - - def test_read_single_cell_date(self): - - wb = load_workbook(filename = self.workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = self.sheet_name) - - eq_(datetime.datetime(1973, 5, 20), list(ws.iter_rows('A1'))[0][0].internal_value) - eq_(datetime.datetime(1973, 5, 20, 9, 15, 2), list(ws.iter_rows('C1'))[0][0].internal_value) - - - diff --git a/tablib/packages/openpyxl3/tests/test_meta.py b/tablib/packages/openpyxl3/tests/test_meta.py deleted file mode 100644 index 06051ad..0000000 --- a/tablib/packages/openpyxl3/tests/test_meta.py +++ /dev/null @@ -1,50 +0,0 @@ -# file openpyxl/tests/test_meta.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports - -import os.path - -# package imports -from openpyxl.tests.helper import DATADIR, assert_equals_file_content -from openpyxl.writer.workbook import write_content_types, write_root_rels -from openpyxl.workbook import Workbook - - -def test_write_content_types(): - wb = Workbook() - wb.create_sheet() - wb.create_sheet() - content = write_content_types(wb) - reference_file = os.path.join(DATADIR, 'writer', 'expected', - '[Content_Types].xml') - assert_equals_file_content(reference_file, content) - - -def test_write_root_rels(): - wb = Workbook() - content = write_root_rels(wb) - reference_file = os.path.join(DATADIR, 'writer', 'expected', '.rels') - assert_equals_file_content(reference_file, content) diff --git a/tablib/packages/openpyxl3/tests/test_named_range.py b/tablib/packages/openpyxl3/tests/test_named_range.py deleted file mode 100644 index c6d9d58..0000000 --- a/tablib/packages/openpyxl3/tests/test_named_range.py +++ /dev/null @@ -1,102 +0,0 @@ -# file openpyxl/tests/test_named_range.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports - -import os.path - -# 3rd-party imports -from nose.tools import eq_, assert_raises - -# package imports -from openpyxl.tests.helper import DATADIR -from openpyxl.namedrange import split_named_range -from openpyxl.reader.workbook import read_named_ranges -from openpyxl.shared.exc import NamedRangeException -from openpyxl.reader.excel import load_workbook - - -def test_split(): - eq_([('My Sheet', '$D$8'), ], split_named_range("'My Sheet'!$D$8")) - - -def test_split_no_quotes(): - eq_([('HYPOTHESES', '$B$3:$L$3'), ], split_named_range('HYPOTHESES!$B$3:$L$3')) - - -def test_bad_range_name(): - assert_raises(NamedRangeException, split_named_range, 'HYPOTHESES$B$3') - - -def test_read_named_ranges(): - - class DummyWs(object): - title = 'My Sheeet' - - def __str__(self): - return self.title - - class DummyWB(object): - - def get_sheet_by_name(self, name): - return DummyWs() - - with open(os.path.join(DATADIR, 'reader', 'workbook.xml')) as handle: - content = handle.read() - named_ranges = read_named_ranges(content, DummyWB()) - eq_(["My Sheeet!$D$8"], [str(range) for range in named_ranges]) - -def test_oddly_shaped_named_ranges(): - - ranges_counts = ((4, 'TEST_RANGE'), - (3, 'TRAP_1'), - (13, 'TRAP_2')) - - def check_ranges(ws, count, range_name): - - eq_(count, len(ws.range(range_name))) - - wb = load_workbook(os.path.join(DATADIR, 'genuine', 'merge_range.xlsx'), - use_iterators = False) - - ws = wb.worksheets[0] - - for count, range_name in ranges_counts: - - yield check_ranges, ws, count, range_name - - -def test_merged_cells_named_range(): - - wb = load_workbook(os.path.join(DATADIR, 'genuine', 'merge_range.xlsx'), - use_iterators = False) - - ws = wb.worksheets[0] - - cell = ws.range('TRAP_3') - - eq_('B15', cell.get_coordinate()) - - eq_(10, cell.value) diff --git a/tablib/packages/openpyxl3/tests/test_number_format.py b/tablib/packages/openpyxl3/tests/test_number_format.py deleted file mode 100644 index 54b0b88..0000000 --- a/tablib/packages/openpyxl3/tests/test_number_format.py +++ /dev/null @@ -1,142 +0,0 @@ -# file openpyxl/tests/test_number_format.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports - -from datetime import datetime, date - -# 3rd party imports -from nose.tools import eq_, assert_almost_equal, assert_raises - -# package imports -from openpyxl.workbook import Workbook -from openpyxl.worksheet import Worksheet -from openpyxl.cell import Cell -from openpyxl.style import NumberFormat -from openpyxl.shared.date_time import SharedDate - - -class TestNumberFormat(object): - - @classmethod - def setup_class(cls): - cls.workbook = Workbook() - cls.worksheet = Worksheet(cls.workbook, 'Test') - cls.sd = SharedDate() - - def test_convert_date_to_julian(self): - eq_(40167, self.sd.to_julian(2009, 12, 20)) - - def test_convert_date_from_julian(self): - - def test_date_equal(julian, datetime): - - eq_(self.sd.from_julian(julian), datetime) - - date_pairs= ( - (40167, datetime(2009, 12, 20)), - (21980, datetime(1960, 0o3, 0o5)), - ) - - for count, dt in date_pairs: - yield test_date_equal, count, dt - - def test_convert_datetime_to_julian(self): - eq_(40167, self.sd.datetime_to_julian(datetime(2009, 12, 20))) - - def test_insert_float(self): - self.worksheet.cell('A1').value = 3.14 - eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) - - def test_insert_percentage(self): - self.worksheet.cell('A1').value = '3.14%' - eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) - assert_almost_equal(0.0314, self.worksheet.cell('A1').value) - - def test_insert_datetime(self): - self.worksheet.cell('A1').value = date.today() - eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) - - def test_insert_date(self): - self.worksheet.cell('A1').value = datetime.now() - eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) - - def test_internal_date(self): - dt = datetime(2010, 7, 13, 6, 37, 41) - self.worksheet.cell('A3').value = dt - eq_(40372.27616898148, self.worksheet.cell('A3')._value) - - def test_datetime_interpretation(self): - dt = datetime(2010, 7, 13, 6, 37, 41) - self.worksheet.cell('A3').value = dt - eq_(dt, self.worksheet.cell('A3').value) - - def test_date_interpretation(self): - dt = date(2010, 7, 13) - self.worksheet.cell('A3').value = dt - eq_(datetime(2010, 7, 13, 0, 0), self.worksheet.cell('A3').value) - - def test_number_format_style(self): - self.worksheet.cell('A1').value = '12.6%' - eq_(NumberFormat.FORMAT_PERCENTAGE, \ - self.worksheet.cell('A1').style.number_format.format_code) - - def test_date_format_on_non_date(self): - cell = self.worksheet.cell('A1') - - def check_date_pair(count, date_string): - cell.value = datetime.strptime(date_string, '%Y-%m-%d') - eq_(count, cell._value) - - date_pairs = ( - (15, '1900-01-15'), - (59, '1900-02-28'), - (61, '1900-03-01'), - (367, '1901-01-01'), - (2958465, '9999-12-31'), ) - for count, date_string in date_pairs: - yield check_date_pair, count, date_string - - def test_1900_leap_year(self): - assert_raises(ValueError, self.sd.from_julian, 60) - assert_raises(ValueError, self.sd.to_julian, 1900, 2, 29) - - def test_bad_date(self): - - def check_bad_date(year, month, day): - assert_raises(ValueError, self.sd.to_julian, year, month, day) - - bad_dates = ((1776, 0o7, 0o4), (1899, 12, 31), ) - for year, month, day in bad_dates: - yield check_bad_date, year, month, day - - def test_bad_julian_date(self): - assert_raises(ValueError, self.sd.from_julian, -1) - - def test_mac_date(self): - self.sd.excel_base_date = self.sd.CALENDAR_MAC_1904 - assert_raises(NotImplementedError, self.sd.to_julian, 2000, 1, 1) - assert_raises(NotImplementedError, self.sd.from_julian, 1) - self.sd.excel_base_date = self.sd.CALENDAR_WINDOWS_1900 diff --git a/tablib/packages/openpyxl3/tests/test_password_hash.py b/tablib/packages/openpyxl3/tests/test_password_hash.py deleted file mode 100644 index fd2fddf..0000000 --- a/tablib/packages/openpyxl3/tests/test_password_hash.py +++ /dev/null @@ -1,41 +0,0 @@ -# file openpyxl/tests/test_password_hash.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# 3rd party imports -from nose.tools import eq_ - -# package imports -from openpyxl.shared.password_hasher import hash_password -from openpyxl.worksheet import SheetProtection - - -def test_hasher(): - eq_('CBEB', hash_password('test')) - - -def test_sheet_protection(): - protection = SheetProtection() - protection.password = 'test' - eq_('CBEB', protection.password) diff --git a/tablib/packages/openpyxl3/tests/test_props.py b/tablib/packages/openpyxl3/tests/test_props.py deleted file mode 100644 index 07d447c..0000000 --- a/tablib/packages/openpyxl3/tests/test_props.py +++ /dev/null @@ -1,124 +0,0 @@ -# coding=utf-8 -# file openpyxl/tests/test_props.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports - -from zipfile import ZipFile, ZIP_DEFLATED -from datetime import datetime -import os.path - -# 3rd party imports -from nose.tools import eq_ - -# package imports -from openpyxl.tests.helper import DATADIR, TMPDIR, make_tmpdir, clean_tmpdir, \ - assert_equals_file_content -from openpyxl.reader.workbook import read_properties_core, \ - read_sheets_titles, get_number_of_parts -from openpyxl.writer.workbook import write_properties_core, \ - write_properties_app -from openpyxl.shared.ooxml import ARC_APP, ARC_CORE -from openpyxl.workbook import DocumentProperties, Workbook - - -class TestReaderProps(object): - - @classmethod - def setup_class(cls): - cls.genuine_filename = os.path.join(DATADIR, 'genuine', 'empty.xlsx') - cls.archive = ZipFile(cls.genuine_filename, 'r', ZIP_DEFLATED) - - @classmethod - def teardown_class(cls): - cls.archive.close() - - def test_read_properties_core(self): - content = self.archive.read(ARC_CORE) - prop = read_properties_core(content) - eq_(prop.creator, '*.*') - eq_(prop.last_modified_by, 'Aurélien Campéas') - eq_(prop.created, datetime(2010, 4, 9, 20, 43, 12)) - eq_(prop.modified, datetime(2011, 2, 9, 13, 49, 32)) - - def test_read_sheets_titles(self): - content = self.archive.read(ARC_APP) - sheet_titles = read_sheets_titles(content) - eq_(sheet_titles, \ - ['Sheet1 - Text', 'Sheet2 - Numbers', 'Sheet3 - Formulas', 'Sheet4 - Dates']) - - -class TestReaderPropsMixed(object): - - @classmethod - def setup_class(cls): - reference_filename = \ - os.path.join(DATADIR, 'reader', 'app-multi-titles.xml') - with open(reference_filename) as handle: - cls.content = handle.read() - - def test_read_sheet_titles_mixed(self): - sheet_titles = read_sheets_titles(self.content) - eq_(sheet_titles, - ['ToC', 'ContractYear', 'ContractTier', 'Demand', - 'LinearizedFunction', 'Market', 'Transmission']) - - def test_number_of_parts(self): - parts_number = get_number_of_parts(self.content) - eq_(parts_number, - ({'Worksheets': 7, 'Named Ranges': 7}, - ['Worksheets', 'Named Ranges'])) - - -class TestWriteProps(object): - - @classmethod - def setup_class(cls): - make_tmpdir() - cls.tmp_filename = os.path.join(TMPDIR, 'test.xlsx') - cls.prop = DocumentProperties() - - @classmethod - def teardown_class(cls): - clean_tmpdir() - - def test_write_properties_core(self): - self.prop.creator = 'TEST_USER' - self.prop.last_modified_by = 'SOMEBODY' - self.prop.created = datetime(2010, 4, 1, 20, 30, 00) - self.prop.modified = datetime(2010, 4, 5, 14, 5, 30) - content = write_properties_core(self.prop) - assert_equals_file_content( - os.path.join(DATADIR, 'writer', 'expected', 'core.xml'), - content) - - def test_write_properties_app(self): - wb = Workbook() - wb.create_sheet() - wb.create_sheet() - content = write_properties_app(wb) - assert_equals_file_content( - os.path.join(DATADIR, 'writer', 'expected', 'app.xml'), - content) diff --git a/tablib/packages/openpyxl3/tests/test_read.py b/tablib/packages/openpyxl3/tests/test_read.py deleted file mode 100644 index 1240164..0000000 --- a/tablib/packages/openpyxl3/tests/test_read.py +++ /dev/null @@ -1,134 +0,0 @@ -# file openpyxl/tests/test_read.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports - -import os.path - -# 3rd party imports -from nose.tools import eq_, raises - -# package imports -from openpyxl.tests.helper import DATADIR -from openpyxl.worksheet import Worksheet -from openpyxl.workbook import Workbook -from openpyxl.style import NumberFormat, Style -from openpyxl.reader.worksheet import read_worksheet, read_dimension -from openpyxl.reader.excel import load_workbook -from openpyxl.shared.exc import InvalidFileException - - -def test_read_standalone_worksheet(): - - class DummyWb(object): - - def get_sheet_by_name(self, value): - return None - - path = os.path.join(DATADIR, 'reader', 'sheet2.xml') - with open(path) as handle: - ws = read_worksheet(handle.read(), DummyWb(), - 'Sheet 2', {1: 'hello'}, {1: Style()}) - assert isinstance(ws, Worksheet) - eq_(ws.cell('G5').value, 'hello') - eq_(ws.cell('D30').value, 30) - eq_(ws.cell('K9').value, 0.09) - - -def test_read_standard_workbook(): - path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') - wb = load_workbook(path) - assert isinstance(wb, Workbook) - -def test_read_standard_workbook_from_fileobj(): - path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') - fo = open(path, mode = 'rb') - wb = load_workbook(fo) - assert isinstance(wb, Workbook) - -def test_read_worksheet(): - path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') - wb = load_workbook(path) - sheet2 = wb.get_sheet_by_name('Sheet2 - Numbers') - assert isinstance(sheet2, Worksheet) - eq_('This is cell G5', sheet2.cell('G5').value) - eq_(18, sheet2.cell('D18').value) - - -def test_read_nostring_workbook(): - genuine_wb = os.path.join(DATADIR, 'genuine', 'empty-no-string.xlsx') - wb = load_workbook(genuine_wb) - assert isinstance(wb, Workbook) - -@raises(InvalidFileException) -def test_read_empty_file(): - - null_file = os.path.join(DATADIR, 'reader', 'null_file.xlsx') - wb = load_workbook(null_file) - -@raises(InvalidFileException) -def test_read_empty_archive(): - - null_file = os.path.join(DATADIR, 'reader', 'null_archive.xlsx') - wb = load_workbook(null_file) - -def test_read_dimension(): - - path = os.path.join(DATADIR, 'reader', 'sheet2.xml') - - with open(path) as handle: - - dimension = read_dimension(xml_source = handle.read()) - - eq_(('D', 1, 'K', 30), dimension) - -class TestReadWorkbookWithStyles(object): - - @classmethod - def setup_class(cls): - cls.genuine_wb = os.path.join(DATADIR, 'genuine', \ - 'empty-with-styles.xlsx') - wb = load_workbook(cls.genuine_wb) - cls.ws = wb.get_sheet_by_name('Sheet1') - - def test_read_general_style(self): - eq_(self.ws.cell('A1').style.number_format.format_code, - NumberFormat.FORMAT_GENERAL) - - def test_read_date_style(self): - eq_(self.ws.cell('A2').style.number_format.format_code, - NumberFormat.FORMAT_DATE_XLSX14) - - def test_read_number_style(self): - eq_(self.ws.cell('A3').style.number_format.format_code, - NumberFormat.FORMAT_NUMBER_00) - - def test_read_time_style(self): - eq_(self.ws.cell('A4').style.number_format.format_code, - NumberFormat.FORMAT_DATE_TIME3) - - def test_read_percentage_style(self): - eq_(self.ws.cell('A5').style.number_format.format_code, - NumberFormat.FORMAT_PERCENTAGE_00) diff --git a/tablib/packages/openpyxl3/tests/test_strings.py b/tablib/packages/openpyxl3/tests/test_strings.py deleted file mode 100644 index 7dbf17a..0000000 --- a/tablib/packages/openpyxl3/tests/test_strings.py +++ /dev/null @@ -1,68 +0,0 @@ -# file openpyxl/tests/test_strings.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports - -import os.path - -# 3rd party imports -from nose.tools import eq_ - -# package imports -from openpyxl.tests.helper import DATADIR -from openpyxl.workbook import Workbook -from openpyxl.writer.strings import create_string_table -from openpyxl.reader.strings import read_string_table - - -def test_create_string_table(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('B12').value = 'hello' - ws.cell('B13').value = 'world' - ws.cell('D28').value = 'hello' - table = create_string_table(wb) - eq_({'hello': 1, 'world': 0}, table) - - -def test_read_string_table(): - with open(os.path.join(DATADIR, 'reader', 'sharedStrings.xml')) as handle: - content = handle.read() - string_table = read_string_table(content) - eq_({0: 'This is cell A1 in Sheet 1', 1: 'This is cell G5'}, string_table) - -def test_empty_string(): - with open(os.path.join(DATADIR, 'reader', 'sharedStrings-emptystring.xml')) as handle: - content = handle.read() - string_table = read_string_table(content) - eq_({0: 'Testing empty cell', 1:''}, string_table) - -def test_formatted_string_table(): - with open(os.path.join(DATADIR, 'reader', 'shared-strings-rich.xml')) \ - as handle: - content = handle.read() - string_table = read_string_table(content) - eq_({0: 'Welcome', 1: 'to the best shop in town', - 2: " let's play "}, string_table) diff --git a/tablib/packages/openpyxl3/tests/test_style.py b/tablib/packages/openpyxl3/tests/test_style.py deleted file mode 100644 index 4c8962c..0000000 --- a/tablib/packages/openpyxl3/tests/test_style.py +++ /dev/null @@ -1,181 +0,0 @@ -# file openpyxl/tests/test_style.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports - -import os.path -import datetime - -# 3rd party imports -from nose.tools import eq_, assert_false, ok_ - -# package imports -from openpyxl.tests.helper import DATADIR, assert_equals_file_content, get_xml -from openpyxl.reader.style import read_style_table -from openpyxl.workbook import Workbook -from openpyxl.style import NumberFormat -from openpyxl.writer.styles import StyleWriter -from openpyxl.style import NumberFormat, Border, Color - - -class TestCreateStyle(object): - - @classmethod - def setup_class(cls): - now = datetime.datetime.now() - cls.workbook = Workbook() - cls.worksheet = cls.workbook.create_sheet() - cls.worksheet.cell(coordinate = 'A1').value = '12.34%' - cls.worksheet.cell(coordinate = 'B4').value = now - cls.worksheet.cell(coordinate = 'B5').value = now - cls.worksheet.cell(coordinate = 'C14').value = 'This is a test' - cls.worksheet.cell(coordinate = 'D9').value = '31.31415' - cls.worksheet.cell(coordinate = 'D9').style.number_format.format_code = \ - NumberFormat.FORMAT_NUMBER_00 - cls.writer = StyleWriter(cls.workbook) - - def test_create_style_table(self): - eq_(3, len(self.writer.style_table)) - - def test_write_style_table(self): - reference_file = os.path.join(DATADIR, 'writer', 'expected', 'simple-styles.xml') - assert_equals_file_content(reference_file, self.writer.write_table()) - -class TestStyleWriter(object): - - def setUp(self): - - self.workbook = Workbook() - self.worksheet = self.workbook.create_sheet() - - def test_no_style(self): - - w = StyleWriter(self.workbook) - eq_(0, len(w.style_table)) - - def test_nb_style(self): - - for i in range(1, 6): - self.worksheet.cell(row=1, column=i).style.font.size += i - w = StyleWriter(self.workbook) - eq_(5, len(w.style_table)) - - self.worksheet.cell('A10').style.borders.top = Border.BORDER_THIN - w = StyleWriter(self.workbook) - eq_(6, len(w.style_table)) - - def test_style_unicity(self): - - for i in range(1, 6): - self.worksheet.cell(row=1, column=i).style.font.bold = True - w = StyleWriter(self.workbook) - eq_(1, len(w.style_table)) - - def test_fonts(self): - - self.worksheet.cell('A1').style.font.size = 12 - self.worksheet.cell('A1').style.font.bold = True - w = StyleWriter(self.workbook) - w._write_fonts() - eq_(get_xml(w._root), '') - - def test_fills(self): - - self.worksheet.cell('A1').style.fill.fill_type = 'solid' - self.worksheet.cell('A1').style.fill.start_color.index = Color.DARKYELLOW - w = StyleWriter(self.workbook) - w._write_fills() - eq_(get_xml(w._root), '') - - def test_borders(self): - - self.worksheet.cell('A1').style.borders.top.border_style = Border.BORDER_THIN - self.worksheet.cell('A1').style.borders.top.color.index = Color.DARKYELLOW - w = StyleWriter(self.workbook) - w._write_borders() - eq_(get_xml(w._root), '') - - def test_write_cell_xfs_1(self): - - self.worksheet.cell('A1').style.font.size = 12 - w = StyleWriter(self.workbook) - ft = w._write_fonts() - nft = w._write_number_formats() - w._write_cell_xfs(nft, ft, {}, {}) - xml = get_xml(w._root) - ok_('applyFont="1"' in xml) - ok_('applyFillId="1"' not in xml) - ok_('applyBorder="1"' not in xml) - ok_('applyAlignment="1"' not in xml) - - def test_alignment(self): - self.worksheet.cell('A1').style.alignment.horizontal = 'center' - self.worksheet.cell('A1').style.alignment.vertical = 'center' - w = StyleWriter(self.workbook) - nft = w._write_number_formats() - w._write_cell_xfs(nft,{},{},{}) - xml = get_xml(w._root) - ok_('applyAlignment="1"' in xml) - ok_('horizontal="center"' in xml) - ok_('vertical="center"' in xml) - - -#def test_format_comparisions(): -# format1 = NumberFormat() -# format2 = NumberFormat() -# format3 = NumberFormat() -# format1.format_code = 'm/d/yyyy' -# format2.format_code = 'm/d/yyyy' -# format3.format_code = 'mm/dd/yyyy' -# assert not format1 < format2 -# assert format1 < format3 -# assert format1 == format2 -# assert format1 != format3 - - -def test_builtin_format(): - format = NumberFormat() - format.format_code = '0.00' - eq_(format.builtin_format_code(2), format._format_code) - - -def test_read_style(): - reference_file = os.path.join(DATADIR, 'reader', 'simple-styles.xml') - with open(reference_file, 'r') as handle: - content = handle.read() - style_table = read_style_table(content) - eq_(4, len(style_table)) - eq_(NumberFormat._BUILTIN_FORMATS[9], - style_table[1].number_format.format_code) - eq_('yyyy-mm-dd', style_table[2].number_format.format_code) - - -def test_read_cell_style(): - reference_file = os.path.join( - DATADIR, 'reader', 'empty-workbook-styles.xml') - with open(reference_file, 'r') as handle: - content = handle.read() - style_table = read_style_table(content) - eq_(2, len(style_table)) diff --git a/tablib/packages/openpyxl3/tests/test_theme.py b/tablib/packages/openpyxl3/tests/test_theme.py deleted file mode 100644 index b0f6f00..0000000 --- a/tablib/packages/openpyxl3/tests/test_theme.py +++ /dev/null @@ -1,37 +0,0 @@ -# file openpyxl/tests/test_theme.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports -import os.path - -# package imports -from openpyxl.tests.helper import DATADIR, assert_equals_file_content -from openpyxl.writer.theme import write_theme - - -def test_write_theme(): - content = write_theme() - assert_equals_file_content( - os.path.join(DATADIR, 'writer', 'expected', 'theme1.xml'), content) diff --git a/tablib/packages/openpyxl3/tests/test_workbook.py b/tablib/packages/openpyxl3/tests/test_workbook.py deleted file mode 100644 index 20191d2..0000000 --- a/tablib/packages/openpyxl3/tests/test_workbook.py +++ /dev/null @@ -1,148 +0,0 @@ -# file openpyxl/tests/test_workbook.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# 3rd party imports -from nose.tools import eq_, with_setup, raises -import os.path as osp - -# package imports -from openpyxl.workbook import Workbook -from openpyxl.reader.excel import load_workbook -from openpyxl.namedrange import NamedRange -from openpyxl.shared.exc import ReadOnlyWorkbookException -from openpyxl.tests.helper import TMPDIR, clean_tmpdir, make_tmpdir - -import datetime - -def test_get_active_sheet(): - wb = Workbook() - active_sheet = wb.get_active_sheet() - eq_(active_sheet, wb.worksheets[0]) - - -def test_create_sheet(): - wb = Workbook() - new_sheet = wb.create_sheet(0) - eq_(new_sheet, wb.worksheets[0]) - - -@raises(ReadOnlyWorkbookException) -def test_create_sheet_readonly(): - wb = Workbook() - wb._set_optimized_read() - wb.create_sheet() - - -def test_remove_sheet(): - wb = Workbook() - new_sheet = wb.create_sheet(0) - wb.remove_sheet(new_sheet) - assert new_sheet not in wb.worksheets - - -def test_get_sheet_by_name(): - wb = Workbook() - new_sheet = wb.create_sheet() - title = 'my sheet' - new_sheet.title = title - found_sheet = wb.get_sheet_by_name(title) - eq_(new_sheet, found_sheet) - - -def test_get_index(): - wb = Workbook() - new_sheet = wb.create_sheet(0) - sheet_index = wb.get_index(new_sheet) - eq_(sheet_index, 0) - - -def test_get_sheet_names(): - wb = Workbook() - names = ['Sheet', 'Sheet1', 'Sheet2', 'Sheet3', 'Sheet4', 'Sheet5'] - for count in range(5): - wb.create_sheet(0) - actual_names = wb.get_sheet_names() - eq_(sorted(actual_names), sorted(names)) - - -def test_get_named_ranges(): - wb = Workbook() - eq_(wb.get_named_ranges(), wb._named_ranges) - - -def test_add_named_range(): - wb = Workbook() - new_sheet = wb.create_sheet() - named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) - wb.add_named_range(named_range) - named_ranges_list = wb.get_named_ranges() - assert named_range in named_ranges_list - - -def test_get_named_range(): - wb = Workbook() - new_sheet = wb.create_sheet() - named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) - wb.add_named_range(named_range) - found_named_range = wb.get_named_range('test_nr') - eq_(named_range, found_named_range) - - -def test_remove_named_range(): - wb = Workbook() - new_sheet = wb.create_sheet() - named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) - wb.add_named_range(named_range) - wb.remove_named_range(named_range) - named_ranges_list = wb.get_named_ranges() - assert named_range not in named_ranges_list - -@with_setup(setup = make_tmpdir, teardown = clean_tmpdir) -def test_add_local_named_range(): - wb = Workbook() - new_sheet = wb.create_sheet() - named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) - named_range.local_only = True - wb.add_named_range(named_range) - dest_filename = osp.join(TMPDIR, 'local_named_range_book.xlsx') - wb.save(dest_filename) - - -@with_setup(setup = make_tmpdir, teardown = clean_tmpdir) -def test_write_regular_date(): - - today = datetime.datetime(2010, 1, 18, 14, 15, 20, 1600) - - book = Workbook() - sheet = book.get_active_sheet() - sheet.cell("A1").value = today - dest_filename = osp.join(TMPDIR, 'date_read_write_issue.xlsx') - book.save(dest_filename) - - test_book = load_workbook(dest_filename) - test_sheet = test_book.get_active_sheet() - - eq_(test_sheet.cell("A1").value, today) - diff --git a/tablib/packages/openpyxl3/tests/test_worksheet.py b/tablib/packages/openpyxl3/tests/test_worksheet.py deleted file mode 100644 index 1894f1d..0000000 --- a/tablib/packages/openpyxl3/tests/test_worksheet.py +++ /dev/null @@ -1,258 +0,0 @@ -# file openpyxl/tests/test_worksheet.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# 3rd party imports -from nose.tools import eq_, raises, assert_raises - -# package imports -from openpyxl.workbook import Workbook -from openpyxl.worksheet import Worksheet, Relationship, flatten -from openpyxl.cell import Cell -from openpyxl.shared.exc import CellCoordinatesException, \ - SheetTitleException, InsufficientCoordinatesException, \ - NamedRangeException - - -class TestWorksheet(): - - @classmethod - def setup_class(cls): - cls.wb = Workbook() - - def test_new_worksheet(self): - ws = Worksheet(self.wb) - eq_(self.wb, ws._parent) - - def test_new_sheet_name(self): - self.wb.worksheets = [] - ws = Worksheet(self.wb, title = '') - eq_(repr(ws), '') - - def test_get_cell(self): - ws = Worksheet(self.wb) - cell = ws.cell('A1') - eq_(cell.get_coordinate(), 'A1') - - @raises(SheetTitleException) - def test_set_bad_title(self): - Worksheet(self.wb, 'X' * 50) - - def test_set_bad_title_character(self): - assert_raises(SheetTitleException, Worksheet, self.wb, '[') - assert_raises(SheetTitleException, Worksheet, self.wb, ']') - assert_raises(SheetTitleException, Worksheet, self.wb, '*') - assert_raises(SheetTitleException, Worksheet, self.wb, ':') - assert_raises(SheetTitleException, Worksheet, self.wb, '?') - assert_raises(SheetTitleException, Worksheet, self.wb, '/') - assert_raises(SheetTitleException, Worksheet, self.wb, '\\') - - def test_worksheet_dimension(self): - ws = Worksheet(self.wb) - eq_('A1:A1', ws.calculate_dimension()) - ws.cell('B12').value = 'AAA' - eq_('A1:B12', ws.calculate_dimension()) - - def test_worksheet_range(self): - ws = Worksheet(self.wb) - xlrange = ws.range('A1:C4') - assert isinstance(xlrange, tuple) - eq_(4, len(xlrange)) - eq_(3, len(xlrange[0])) - - def test_worksheet_named_range(self): - ws = Worksheet(self.wb) - self.wb.create_named_range('test_range', ws, 'C5') - xlrange = ws.range('test_range') - assert isinstance(xlrange, Cell) - eq_(5, xlrange.row) - - @raises(NamedRangeException) - def test_bad_named_range(self): - ws = Worksheet(self.wb) - ws.range('bad_range') - - @raises(NamedRangeException) - def test_named_range_wrong_sheet(self): - ws1 = Worksheet(self.wb) - ws2 = Worksheet(self.wb) - self.wb.create_named_range('wrong_sheet_range', ws1, 'C5') - ws2.range('wrong_sheet_range') - - def test_cell_offset(self): - ws = Worksheet(self.wb) - eq_('C17', ws.cell('B15').offset(2, 1).get_coordinate()) - - def test_range_offset(self): - ws = Worksheet(self.wb) - xlrange = ws.range('A1:C4', 1, 3) - assert isinstance(xlrange, tuple) - eq_(4, len(xlrange)) - eq_(3, len(xlrange[0])) - eq_('D2', xlrange[0][0].get_coordinate()) - - def test_cell_alternate_coordinates(self): - ws = Worksheet(self.wb) - cell = ws.cell(row = 8, column = 4) - eq_('E9', cell.get_coordinate()) - - @raises(InsufficientCoordinatesException) - def test_cell_insufficient_coordinates(self): - ws = Worksheet(self.wb) - cell = ws.cell(row = 8) - - def test_cell_range_name(self): - ws = Worksheet(self.wb) - self.wb.create_named_range('test_range_single', ws, 'B12') - assert_raises(CellCoordinatesException, ws.cell, 'test_range_single') - c_range_name = ws.range('test_range_single') - c_range_coord = ws.range('B12') - c_cell = ws.cell('B12') - eq_(c_range_coord, c_range_name) - eq_(c_range_coord, c_cell) - - def test_garbage_collect(self): - ws = Worksheet(self.wb) - ws.cell('A1').value = '' - ws.cell('B2').value = '0' - ws.cell('C4').value = 0 - ws.garbage_collect() - eq_(ws.get_cell_collection(), [ws.cell('B2'), ws.cell('C4')]) - - def test_hyperlink_relationships(self): - ws = Worksheet(self.wb) - eq_(len(ws.relationships), 0) - - ws.cell('A1').hyperlink = "http://test.com" - eq_(len(ws.relationships), 1) - eq_("rId1", ws.cell('A1').hyperlink_rel_id) - eq_("rId1", ws.relationships[0].id) - eq_("http://test.com", ws.relationships[0].target) - eq_("External", ws.relationships[0].target_mode) - - ws.cell('A2').hyperlink = "http://test2.com" - eq_(len(ws.relationships), 2) - eq_("rId2", ws.cell('A2').hyperlink_rel_id) - eq_("rId2", ws.relationships[1].id) - eq_("http://test2.com", ws.relationships[1].target) - eq_("External", ws.relationships[1].target_mode) - - @raises(ValueError) - def test_bad_relationship_type(self): - rel = Relationship('bad_type') - - def test_append_list(self): - ws = Worksheet(self.wb) - - ws.append(['This is A1', 'This is B1']) - - eq_('This is A1', ws.cell('A1').value) - eq_('This is B1', ws.cell('B1').value) - - def test_append_dict_letter(self): - ws = Worksheet(self.wb) - - ws.append({'A' : 'This is A1', 'C' : 'This is C1'}) - - eq_('This is A1', ws.cell('A1').value) - eq_('This is C1', ws.cell('C1').value) - - def test_append_dict_index(self): - ws = Worksheet(self.wb) - - ws.append({0 : 'This is A1', 2 : 'This is C1'}) - - eq_('This is A1', ws.cell('A1').value) - eq_('This is C1', ws.cell('C1').value) - - @raises(TypeError) - def test_bad_append(self): - ws = Worksheet(self.wb) - ws.append("test") - - def test_append_2d_list(self): - - ws = Worksheet(self.wb) - - ws.append(['This is A1', 'This is B1']) - ws.append(['This is A2', 'This is B2']) - - vals = ws.range('A1:B2') - - eq_((('This is A1', 'This is B1'), - ('This is A2', 'This is B2'),), flatten(vals)) - - def test_rows(self): - - ws = Worksheet(self.wb) - - ws.cell('A1').value = 'first' - ws.cell('C9').value = 'last' - - rows = ws.rows - - eq_(len(rows), 9) - - eq_(rows[0][0].value, 'first') - eq_(rows[-1][-1].value, 'last') - - def test_cols(self): - - ws = Worksheet(self.wb) - - ws.cell('A1').value = 'first' - ws.cell('C9').value = 'last' - - cols = ws.columns - - eq_(len(cols), 3) - - eq_(cols[0][0].value, 'first') - eq_(cols[-1][-1].value, 'last') - - def test_auto_filter(self): - ws = Worksheet(self.wb) - ws.auto_filter = ws.range('a1:f1') - assert ws.auto_filter == 'A1:F1' - - ws.auto_filter = '' - assert ws.auto_filter is None - - ws.auto_filter = 'c1:g9' - assert ws.auto_filter == 'C1:G9' - - def test_freeze(self): - ws = Worksheet(self.wb) - ws.freeze_panes = ws.cell('b2') - assert ws.freeze_panes == 'B2' - - ws.freeze_panes = '' - assert ws.freeze_panes is None - - ws.freeze_panes = 'c5' - assert ws.freeze_panes == 'C5' - - ws.freeze_panes = ws.cell('A1') - assert ws.freeze_panes is None - diff --git a/tablib/packages/openpyxl3/tests/test_write.py b/tablib/packages/openpyxl3/tests/test_write.py deleted file mode 100644 index 74363cb..0000000 --- a/tablib/packages/openpyxl3/tests/test_write.py +++ /dev/null @@ -1,203 +0,0 @@ -# file openpyxl/tests/test_write.py - -# Copyright (c) 2010 openpyxl -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# @license: http://www.opensource.org/licenses/mit-license.php -# @author: Eric Gazoni - -# Python stdlib imports - -from io import StringIO -import os.path - -# 3rd party imports -from nose.tools import eq_, with_setup, raises - -# package imports -from openpyxl.tests.helper import TMPDIR, DATADIR, \ - assert_equals_file_content, clean_tmpdir, make_tmpdir -from openpyxl.workbook import Workbook -from openpyxl.reader.excel import load_workbook -from openpyxl.writer.excel import save_workbook, save_virtual_workbook, \ - ExcelWriter -from openpyxl.writer.workbook import write_workbook, write_workbook_rels -from openpyxl.writer.worksheet import write_worksheet, write_worksheet_rels -from openpyxl.writer.strings import write_string_table -from openpyxl.writer.styles import StyleWriter - - -@with_setup(setup = make_tmpdir, teardown = clean_tmpdir) -def test_write_empty_workbook(): - wb = Workbook() - dest_filename = os.path.join(TMPDIR, 'empty_book.xlsx') - save_workbook(wb, dest_filename) - assert os.path.isfile(dest_filename) - - -def test_write_virtual_workbook(): - old_wb = Workbook() - saved_wb = save_virtual_workbook(old_wb) - new_wb = load_workbook(StringIO(saved_wb)) - assert new_wb - - -def test_write_workbook_rels(): - wb = Workbook() - content = write_workbook_rels(wb) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'workbook.xml.rels'), content) - - -def test_write_workbook(): - wb = Workbook() - content = write_workbook(wb) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'workbook.xml'), content) - - -def test_write_string_table(): - table = {'hello': 1, 'world': 2, 'nice': 3} - content = write_string_table(table) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sharedStrings.xml'), content) - - -def test_write_worksheet(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F42').value = 'hello' - content = write_worksheet(ws, {'hello': 0}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1.xml'), content) - - -def test_write_hidden_worksheet(): - wb = Workbook() - ws = wb.create_sheet() - ws.sheet_state = ws.SHEETSTATE_HIDDEN - ws.cell('F42').value = 'hello' - content = write_worksheet(ws, {'hello': 0}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1.xml'), content) - - -def test_write_formula(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F1').value = 10 - ws.cell('F2').value = 32 - ws.cell('F3').value = '=F1+F2' - content = write_worksheet(ws, {}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_formula.xml'), content) - - -def test_write_style(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F1').value = '13%' - style_id_by_hash = StyleWriter(wb).get_style_by_hash() - content = write_worksheet(ws, {}, style_id_by_hash) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_style.xml'), content) - - -def test_write_height(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F1').value = 10 - ws.row_dimensions[ws.cell('F1').row].height = 30 - content = write_worksheet(ws, {}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_height.xml'), content) - - -def test_write_hyperlink(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('A1').value = "test" - ws.cell('A1').hyperlink = "http://test.com" - content = write_worksheet(ws, {'test': 0}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_hyperlink.xml'), content) - - -def test_write_hyperlink_rels(): - wb = Workbook() - ws = wb.create_sheet() - eq_(0, len(ws.relationships)) - ws.cell('A1').value = "test" - ws.cell('A1').hyperlink = "http://test.com/" - eq_(1, len(ws.relationships)) - ws.cell('A2').value = "test" - ws.cell('A2').hyperlink = "http://test2.com/" - eq_(2, len(ws.relationships)) - content = write_worksheet_rels(ws, 1) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_hyperlink.xml.rels'), content) - - -def test_hyperlink_value(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('A1').hyperlink = "http://test.com" - eq_("http://test.com", ws.cell('A1').value) - ws.cell('A1').value = "test" - eq_("test", ws.cell('A1').value) - -def test_write_auto_filter(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F42').value = 'hello' - ws.auto_filter = 'A1:F1' - content = write_worksheet(ws, {'hello': 0}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_auto_filter.xml'), content) - -def test_freeze_panes_horiz(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F42').value = 'hello' - ws.freeze_panes = 'A4' - content = write_worksheet(ws, {'hello': 0}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_freeze_panes_horiz.xml'), content) - -def test_freeze_panes_vert(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F42').value = 'hello' - ws.freeze_panes = 'D1' - content = write_worksheet(ws, {'hello': 0}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_freeze_panes_vert.xml'), content) - pass - -def test_freeze_panes_both(): - wb = Workbook() - ws = wb.create_sheet() - ws.cell('F42').value = 'hello' - ws.freeze_panes = 'D4' - content = write_worksheet(ws, {'hello': 0}, {}) - assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ - 'sheet1_freeze_panes_both.xml'), content) - - diff --git a/test.xlsx b/test.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..5813e469ef637972ff16286f2e09ff9c1db45a02 GIT binary patch literal 5377 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 HzwZ7EHpUWO literal 0 HcmV?d00001 diff --git a/test_tablib.py.orig b/test_tablib.py.orig new file mode 100755 index 0000000..21131bb --- /dev/null +++ b/test_tablib.py.orig @@ -0,0 +1,522 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Tests for Tablib.""" + +import unittest +import sys + +if sys.version_info[0] > 2: + from tablib.packages import markup3 as markup +else: + from tablib.packages import markup + + + +import tablib + + + +class TablibTestCase(unittest.TestCase): + """Tablib test cases.""" + + def setUp(self): + """Create simple data set with headers.""" + + global data, book + + data = tablib.Dataset() + book = tablib.Databook() + + self.headers = ('first_name', 'last_name', 'gpa') + self.john = ('John', 'Adams', 90) + self.george = ('George', 'Washington', 67) + self.tom = ('Thomas', 'Jefferson', 50) + + self.founders = tablib.Dataset(headers=self.headers) + self.founders.append(self.john) + self.founders.append(self.george) + self.founders.append(self.tom) + + + def tearDown(self): + """Teardown.""" + pass + + + def test_empty_append(self): + """Verify append() correctly adds tuple with no headers.""" + new_row = (1, 2, 3) + data.append(new_row) + + # Verify width/data + self.assertTrue(data.width == len(new_row)) + self.assertTrue(data[0] == new_row) + + + def test_empty_append_with_headers(self): + """Verify append() correctly detects mismatch of number of + headers and data. + """ + data.headers = ['first', 'second'] + new_row = (1, 2, 3, 4) + + self.assertRaises(tablib.InvalidDimensions, data.append, new_row) + + + def test_add_column(self): + """Verify adding column works with/without headers.""" + + data.append(['kenneth']) + data.append(['bessie']) + + new_col = ['reitz', 'monke'] + + data.append(col=new_col) + + self.assertEquals(data[0], ('kenneth', 'reitz')) + self.assertEquals(data.width, 2) + + # With Headers + data.headers = ('fname', 'lname') + new_col = [21, 22] + data.append(col=new_col, header='age') + + self.assertEquals(data['age'], new_col) + + + def test_add_column_no_data_no_headers(self): + """Verify adding new column with no headers.""" + + new_col = ('reitz', 'monke') + + data.append(col=new_col) + + self.assertEquals(data[0], tuple([new_col[0]])) + self.assertEquals(data.width, 1) + self.assertEquals(data.height, len(new_col)) + + + def test_add_callable_column(self): + """Verify adding column with values specified as callable.""" + new_col = [lambda x: x[0]] + self.founders.append(col=new_col, header='first_again') +# +# self.assertTrue(map(lambda x: x[0] == x[-1], self.founders)) + + + def test_header_slicing(self): + """Verify slicing by headers.""" + + self.assertEqual(self.founders['first_name'], + [self.john[0], self.george[0], self.tom[0]]) + self.assertEqual(self.founders['last_name'], + [self.john[1], self.george[1], self.tom[1]]) + self.assertEqual(self.founders['gpa'], + [self.john[2], self.george[2], self.tom[2]]) + + + def test_data_slicing(self): + """Verify slicing by data.""" + + # Slice individual rows + self.assertEqual(self.founders[0], self.john) + self.assertEqual(self.founders[:1], [self.john]) + self.assertEqual(self.founders[1:2], [self.george]) + self.assertEqual(self.founders[-1], self.tom) + self.assertEqual(self.founders[3:], []) + + # Slice multiple rows + self.assertEqual(self.founders[:], [self.john, self.george, self.tom]) + self.assertEqual(self.founders[0:2], [self.john, self.george]) + self.assertEqual(self.founders[1:3], [self.george, self.tom]) + self.assertEqual(self.founders[2:], [self.tom]) + + + def test_delete(self): + """Verify deleting from dataset works.""" + + # Delete from front of object + del self.founders[0] + self.assertEqual(self.founders[:], [self.george, self.tom]) + + # Verify dimensions, width should NOT change + self.assertEqual(self.founders.height, 2) + self.assertEqual(self.founders.width, 3) + + # Delete from back of object + del self.founders[1] + self.assertEqual(self.founders[:], [self.george]) + + # Verify dimensions, width should NOT change + self.assertEqual(self.founders.height, 1) + self.assertEqual(self.founders.width, 3) + + # Delete from invalid index + self.assertRaises(IndexError, self.founders.__delitem__, 3) + + + def test_csv_export(self): + """Verify exporting dataset object as CSV.""" + + # Build up the csv string with headers first, followed by each row + csv = '' + for col in self.headers: + csv += col + ',' + + csv = csv.strip(',') + '\r\n' + + for founder in self.founders: + for col in founder: + csv += str(col) + ',' + csv = csv.strip(',') + '\r\n' + + self.assertEqual(csv, self.founders.csv) + + def test_tsv_export(self): + """Verify exporting dataset object as CSV.""" + + # Build up the csv string with headers first, followed by each row + tsv = '' + for col in self.headers: + tsv += col + '\t' + + tsv = tsv.strip('\t') + '\r\n' + + for founder in self.founders: + for col in founder: + tsv += str(col) + '\t' + tsv = tsv.strip('\t') + '\r\n' + + self.assertEqual(tsv, self.founders.tsv) + + def test_html_export(self): + + """HTML export""" + + html = markup.page() + html.table.open() + html.thead.open() + + html.tr(markup.oneliner.th(self.founders.headers)) + html.thead.close() + + for founder in self.founders: + + html.tr(markup.oneliner.td(founder)) + + html.table.close() + html = str(html) + + self.assertEqual(html, self.founders.html) + + + def test_unicode_append(self): + """Passes in a single unicode charecter and exports.""" + + new_row = ('å', 'é') + data.append(new_row) + + data.json + data.yaml + data.csv + data.tsv + data.xls +<<<<<<< HEAD + data.html +======= + data.xlsx +>>>>>>> 5350355fbe0aefe053d40fda03c0688a7b7eae3d + + + def test_book_export_no_exceptions(self): + """Test that varoius exports don't error out.""" + + book = tablib.Databook() + book.add_sheet(data) + + book.json + book.yaml + book.xls + book.xlsx + + + def test_json_import_set(self): + """Generate and import JSON set serialization.""" + data.append(self.john) + data.append(self.george) + data.headers = self.headers + + _json = data.json + + data.json = _json + + self.assertEqual(_json, data.json) + + + def test_json_import_book(self): + """Generate and import JSON book serialization.""" + data.append(self.john) + data.append(self.george) + data.headers = self.headers + + book.add_sheet(data) + _json = book.json + + book.json = _json + + self.assertEqual(_json, book.json) + + + def test_yaml_import_set(self): + """Generate and import YAML set serialization.""" + data.append(self.john) + data.append(self.george) + data.headers = self.headers + + _yaml = data.yaml + + data.yaml = _yaml + + self.assertEqual(_yaml, data.yaml) + + + def test_yaml_import_book(self): + """Generate and import YAML book serialization.""" + data.append(self.john) + data.append(self.george) + data.headers = self.headers + + book.add_sheet(data) + _yaml = book.yaml + + book.yaml = _yaml + + self.assertEqual(_yaml, book.yaml) + + + def test_csv_import_set(self): + """Generate and import CSV set serialization.""" + data.append(self.john) + data.append(self.george) + data.headers = self.headers + + _csv = data.csv + + data.csv = _csv + + self.assertEqual(_csv, data.csv) + + + def test_csv_import_set_with_spaces(self): + """Generate and import CSV set serialization when row values have + spaces.""" + data.append(('Bill Gates', 'Microsoft')) + data.append(('Steve Jobs', 'Apple')) + data.headers = ('Name', 'Company') + + _csv = data.csv + + data.csv = _csv + + self.assertEqual(_csv, data.csv) + + + def test_tsv_import_set(self): + """Generate and import TSV set serialization.""" + data.append(self.john) + data.append(self.george) + data.headers = self.headers + + _tsv = data.tsv + + data.tsv = _tsv + + self.assertEqual(_tsv, data.tsv) + + + def test_csv_format_detect(self): + """Test CSV format detection.""" + + _csv = ( + '1,2,3\n' + '4,5,6\n' + '7,8,9\n' + ) + _bunk = ( + '¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶' + ) + + self.assertTrue(tablib.formats.csv.detect(_csv)) + self.assertFalse(tablib.formats.csv.detect(_bunk)) + + + def test_tsv_format_detect(self): + """Test TSV format detection.""" + + _tsv = ( + '1\t2\t3\n' + '4\t5\t6\n' + '7\t8\t9\n' + ) + _bunk = ( + '¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶' + ) + + self.assertTrue(tablib.formats.tsv.detect(_tsv)) + self.assertFalse(tablib.formats.tsv.detect(_bunk)) + + + def test_json_format_detect(self): + """Test JSON format detection.""" + + _json = '[{"last_name": "Adams","age": 90,"first_name": "John"}]' + _bunk = ( + '¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶' + ) + + self.assertTrue(tablib.formats.json.detect(_json)) + self.assertFalse(tablib.formats.json.detect(_bunk)) + + + def test_yaml_format_detect(self): + """Test YAML format detection.""" + + _yaml = '- {age: 90, first_name: John, last_name: Adams}' + _bunk = ( + '¡¡¡¡¡¡---///\n\n\n¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶' + ) + + self.assertTrue(tablib.formats.yaml.detect(_yaml)) + self.assertFalse(tablib.formats.yaml.detect(_bunk)) + + + def test_auto_format_detect(self): + """Test auto format detection.""" + + _yaml = '- {age: 90, first_name: John, last_name: Adams}' + _json = '[{"last_name": "Adams","age": 90,"first_name": "John"}]' + _csv = '1,2,3\n4,5,6\n7,8,9\n' + _bunk = '¡¡¡¡¡¡---///\n\n\n¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶' + + self.assertEqual(tablib.detect(_yaml)[0], tablib.formats.yaml) + self.assertEqual(tablib.detect(_csv)[0], tablib.formats.csv) + self.assertEqual(tablib.detect(_json)[0], tablib.formats.json) + self.assertEqual(tablib.detect(_bunk)[0], None) + + + def test_transpose(self): + """Transpose a dataset.""" + + transposed_founders = self.founders.transpose() + first_row = transposed_founders[0] + second_row = transposed_founders[1] + + self.assertEqual(transposed_founders.headers, + ["first_name","John", "George", "Thomas"]) + self.assertEqual(first_row, + ("last_name","Adams", "Washington", "Jefferson")) + self.assertEqual(second_row, + ("gpa",90, 67, 50)) + + + def test_row_stacking(self): + + """Row stacking.""" + + to_join = tablib.Dataset(headers=self.founders.headers) + + for row in self.founders: + to_join.append(row=row) + + row_stacked = self.founders.stack_rows(to_join) + + for column in row_stacked.headers: + + original_data = self.founders[column] + expected_data = original_data + original_data + self.assertEqual(row_stacked[column], expected_data) + + + def test_column_stacking(self): + + """Column stacking""" + + to_join = tablib.Dataset(headers=self.founders.headers) + + for row in self.founders: + to_join.append(row=row) + + column_stacked = self.founders.stack_columns(to_join) + + for index, row in enumerate(column_stacked): + + original_data = self.founders[index] + expected_data = original_data + original_data + self.assertEqual(row, expected_data) + + self.assertEqual(column_stacked[0], + ("John", "Adams", 90, "John", "Adams", 90)) + + + def test_sorting(self): + + """Sort columns.""" + + sorted_data = self.founders.sort(col="first_name") + + first_row = sorted_data[0] + second_row = sorted_data[2] + third_row = sorted_data[1] + expected_first = self.founders[1] + expected_second = self.founders[2] + expected_third = self.founders[0] + + self.assertEqual(first_row, expected_first) + self.assertEqual(second_row, expected_second) + self.assertEqual(third_row, expected_third) + + + def test_wipe(self): + """Purge a dataset.""" + + new_row = (1, 2, 3) + data.append(new_row) + + # Verify width/data + self.assertTrue(data.width == len(new_row)) + self.assertTrue(data[0] == new_row) + + data.wipe() + new_row = (1, 2, 3, 4) + data.append(new_row) + self.assertTrue(data.width == len(new_row)) + self.assertTrue(data[0] == new_row) + + + def test_formatters(self): + """Confirm formatters are being triggered.""" + + def _formatter(cell_value): + return str(cell_value).upper() + + self.founders.add_formatter('last_name', _formatter) + + for name in [r['last_name'] for r in self.founders.dict]: + self.assertTrue(name.isupper()) + + def test_unicode_csv(self): + """Check if unicode in csv export doesn't raise.""" + + data = tablib.Dataset() + + if sys.version_info[0] > 2: + data.append(['\xfc', '\xfd']) + else: + exec("data.append([u'\xfc', u'\xfd'])") + + + data.csv + +if __name__ == '__main__': + unittest.main() diff --git a/toy.py b/toy.py new file mode 100644 index 0000000..ef3ff37 --- /dev/null +++ b/toy.py @@ -0,0 +1,9 @@ +import tablib + +d = tablib.Dataset() + +d.headers = ['face', 'book'] +d.append([1,2]) + +f = open('test.xlsx', 'wb') +f.write(d.xlsx) \ No newline at end of file From 4a0129929348024a0bd4b5e9e876b9c2d4e78faa Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 13 May 2011 00:40:36 -0400 Subject: [PATCH 19/22] v0.9.7 release notes --- HISTORY.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index f35c538..2f04060 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,9 +1,10 @@ History ------- -0.9.7 -+++++ +0.9.7 (2011-05-12) +++++++++++++++++++ +* Full XLSX Support! * Pickling Bugfix * Compat Module From 56ef89424f923e29dfbedf29429dac0c2ecdc341 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 13 May 2011 01:05:11 -0400 Subject: [PATCH 20/22] fallback on from xml.etree.ElementTree for pypy --- tablib/packages/openpyxl/reader/iter_worksheet.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tablib/packages/openpyxl/reader/iter_worksheet.py b/tablib/packages/openpyxl/reader/iter_worksheet.py index 7b3bc04..46ee318 100644 --- a/tablib/packages/openpyxl/reader/iter_worksheet.py +++ b/tablib/packages/openpyxl/reader/iter_worksheet.py @@ -41,7 +41,12 @@ from ..shared.date_time import SharedDate from ..reader.worksheet import read_dimension from ..shared.ooxml import (MIN_COLUMN, MAX_COLUMN, PACKAGE_WORKSHEETS, MAX_ROW, MIN_ROW, ARC_SHARED_STRINGS, ARC_APP, ARC_STYLE) -from xml.etree.cElementTree import iterparse +try: + from xml.etree.cElementTree import iterparse +except ImportError: + from xml.etree.ElementTree import iterparse + + from zipfile import ZipFile from .. import cell import re From e5259cbb58092184fdeb4d0635c57480164cb1ce Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 13 May 2011 01:13:25 -0400 Subject: [PATCH 21/22] version bump --- docs/conf.py | 2 +- setup.py | 2 +- tablib/core.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 01f4933..5a42816 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,7 +48,7 @@ copyright = u'2011, Kenneth Reitz. Styles (modified) © Armin Ronacher' # built documents. # # The short X.Y version. -version = '0.9.6' +version = '0.9.7' # The full version, including alpha/beta/rc tags. release = version diff --git a/setup.py b/setup.py index 850f915..307307f 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ if sys.version_info[:2] < (2,6): setup( name='tablib', - version='0.9.6', + version='0.9.7', description='Format agnostic tabular data library (XLS, JSON, YAML, CSV)', long_description=open('README.rst').read() + '\n\n' + open('HISTORY.rst').read(), diff --git a/tablib/core.py b/tablib/core.py index 6ab0fc5..03547ff 100644 --- a/tablib/core.py +++ b/tablib/core.py @@ -19,8 +19,8 @@ from tablib.compat import OrderedDict __title__ = 'tablib' -__version__ = '0.9.4' -__build__ = 0x000904 +__version__ = '0.9.7' +__build__ = 0x000907 __author__ = 'Kenneth Reitz' __license__ = 'MIT' __copyright__ = 'Copyright 2011 Kenneth Reitz' From 9761ff5e9ef3bed0cd4c7e9a00e5c10bd453e6c7 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 13 May 2011 01:13:50 -0400 Subject: [PATCH 22/22] hmmm --- test.xlsx | Bin 5377 -> 0 bytes toy.py | 9 --------- 2 files changed, 9 deletions(-) delete mode 100644 test.xlsx delete mode 100644 toy.py diff --git a/test.xlsx b/test.xlsx deleted file mode 100644 index 5813e469ef637972ff16286f2e09ff9c1db45a02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5377 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 HzwZ7EHpUWO diff --git a/toy.py b/toy.py deleted file mode 100644 index ef3ff37..0000000 --- a/toy.py +++ /dev/null @@ -1,9 +0,0 @@ -import tablib - -d = tablib.Dataset() - -d.headers = ['face', 'book'] -d.append([1,2]) - -f = open('test.xlsx', 'wb') -f.write(d.xlsx) \ No newline at end of file