mirror of
https://github.com/kennethreitz/tablib.git
synced 2026-06-05 06:56:13 +00:00
Replaced vendored openpyxl by a dependency (#221)
It is time to make it happen. * Dropped Python 3.2 support Recent dependencies are dropping Python 3.2 too. * Replaced vendored openpyxl by a dependency Thanks Tommy Anthony for the initial patch.
This commit is contained in:
committed by
Iuri de Silvio
parent
0e720d78ca
commit
e66eb4a189
+2
-1
@@ -2,9 +2,10 @@ language: python
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.2
|
||||
- 3.3
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
install:
|
||||
- python setup.py install
|
||||
script: python test_tablib.py
|
||||
|
||||
@@ -41,24 +41,17 @@ packages = [
|
||||
'tablib.packages.xlwt',
|
||||
'tablib.packages.xlrd',
|
||||
'tablib.packages.odf',
|
||||
'tablib.packages.openpyxl',
|
||||
'tablib.packages.openpyxl.shared',
|
||||
'tablib.packages.openpyxl.reader',
|
||||
'tablib.packages.openpyxl.writer',
|
||||
'tablib.packages.yaml',
|
||||
'tablib.packages.dbfpy',
|
||||
'tablib.packages.xlwt3',
|
||||
'tablib.packages.xlrd3',
|
||||
'tablib.packages.odf3',
|
||||
'tablib.packages.openpyxl3',
|
||||
'tablib.packages.openpyxl3.shared',
|
||||
'tablib.packages.openpyxl3.reader',
|
||||
'tablib.packages.openpyxl3.writer',
|
||||
'tablib.packages.yaml3',
|
||||
'tablib.packages.dbfpy3'
|
||||
]
|
||||
|
||||
install = [
|
||||
'openpyxl',
|
||||
'unicodecsv',
|
||||
]
|
||||
|
||||
@@ -83,12 +76,8 @@ setup(
|
||||
'Natural Language :: English',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.5',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.0',
|
||||
'Programming Language :: Python :: 3.1',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
|
||||
@@ -26,7 +26,6 @@ if is_py3:
|
||||
import tablib.packages.xlrd3 as xlrd
|
||||
from tablib.packages.xlrd3.biffh import XLRDError
|
||||
from tablib.packages import markup3 as markup
|
||||
from tablib.packages import openpyxl3 as openpyxl
|
||||
from tablib.packages.odf3 import opendocument, style, text, table
|
||||
import tablib.packages.dbfpy3 as dbfpy
|
||||
|
||||
@@ -48,7 +47,6 @@ else:
|
||||
from tablib.packages.xlrd.biffh import XLRDError
|
||||
from tablib.packages import markup
|
||||
from itertools import ifilter
|
||||
from tablib.packages import openpyxl
|
||||
from tablib.packages.odf import opendocument, style, text, table
|
||||
|
||||
import unicodecsv as csv
|
||||
|
||||
+17
-20
@@ -11,12 +11,12 @@ if sys.version_info[0] > 2:
|
||||
else:
|
||||
from cStringIO import StringIO as BytesIO
|
||||
|
||||
from tablib.compat import openpyxl
|
||||
import openpyxl
|
||||
import tablib
|
||||
|
||||
Workbook = openpyxl.workbook.Workbook
|
||||
ExcelWriter = openpyxl.writer.excel.ExcelWriter
|
||||
get_column_letter = openpyxl.cell.get_column_letter
|
||||
get_column_letter = openpyxl.utils.get_column_letter
|
||||
|
||||
from tablib.compat import unicode
|
||||
|
||||
@@ -51,7 +51,8 @@ def export_book(databook, freeze_panes=True):
|
||||
"""Returns XLSX representation of DataBook."""
|
||||
|
||||
wb = Workbook()
|
||||
wb.worksheets = []
|
||||
for sheet in wb.worksheets:
|
||||
wb.remove_sheet(sheet)
|
||||
for i, dset in enumerate(databook._datasets):
|
||||
ws = wb.create_sheet()
|
||||
ws.title = dset.title if dset.title else 'Sheet%s' % (i)
|
||||
@@ -111,42 +112,38 @@ def dset_sheet(dataset, ws, freeze_panes=True):
|
||||
_offset = i
|
||||
_package.insert((sep[0] + _offset), (sep[1],))
|
||||
|
||||
bold = openpyxl.styles.Font(bold=True)
|
||||
wrap_text = openpyxl.styles.Alignment(wrap_text=True)
|
||||
|
||||
for i, row in enumerate(_package):
|
||||
row_number = i + 1
|
||||
for j, col in enumerate(row):
|
||||
col_idx = get_column_letter(j + 1)
|
||||
cell = ws.cell('%s%s' % (col_idx, row_number))
|
||||
|
||||
# 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(col)
|
||||
style = ws.get_style('%s%s' % (col_idx, row_number))
|
||||
style.font.bold = True
|
||||
# cell.value = unicode('%s' % col, errors='ignore')
|
||||
cell.value = unicode(col)
|
||||
cell.font = bold
|
||||
if freeze_panes:
|
||||
# As already done in #53, but after Merge lost:
|
||||
# Export Freeze only after first Line
|
||||
ws.freeze_panes = 'A2'
|
||||
|
||||
# bold separators
|
||||
elif len(row) < dataset.width:
|
||||
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
|
||||
cell.value = unicode('%s' % col, errors='ignore')
|
||||
cell.font = bold
|
||||
|
||||
# wrap the rest
|
||||
else:
|
||||
try:
|
||||
if '\n' in col:
|
||||
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
|
||||
cell.value = unicode('%s' % col, errors='ignore')
|
||||
cell.alignment = wrap_text
|
||||
else:
|
||||
ws.cell('%s%s'%(col_idx, row_number)).value = unicode(
|
||||
'%s' % col, errors='ignore')
|
||||
cell.value = unicode('%s' % col, errors='ignore')
|
||||
except TypeError:
|
||||
ws.cell('%s%s'%(col_idx, row_number)).value = unicode(col)
|
||||
cell.value = unicode(col)
|
||||
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
# 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',)
|
||||
@@ -1,384 +0,0 @@
|
||||
# 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 "<Cell %s.%s>" % (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)))
|
||||
@@ -1,340 +0,0 @@
|
||||
'''
|
||||
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], 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)
|
||||
|
||||
|
||||
@@ -1,401 +0,0 @@
|
||||
'''
|
||||
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))
|
||||
@@ -1,68 +0,0 @@
|
||||
# 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
|
||||
@@ -1,33 +0,0 @@
|
||||
# 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
|
||||
@@ -1,109 +0,0 @@
|
||||
# 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):
|
||||
raise InvalidFileException()
|
||||
wb = Workbook()
|
||||
|
||||
if use_iterators:
|
||||
wb._set_optimized_read()
|
||||
|
||||
try:
|
||||
_load_workbook(wb, archive, filename, use_iterators)
|
||||
except KeyError:
|
||||
raise InvalidFileException()
|
||||
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)
|
||||
@@ -1,348 +0,0 @@
|
||||
# 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 ....compat import BytesIO as StringIO
|
||||
import warnings
|
||||
import operator
|
||||
from functools import partial
|
||||
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 ..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)
|
||||
try:
|
||||
from xml.etree.cElementTree import iterparse
|
||||
except ImportError:
|
||||
from xml.etree.ElementTree 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 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("<HH", archive.fp.read(4))
|
||||
return zinfo.header_offset + 30 + file_name_len + extra_len
|
||||
@@ -1,64 +0,0 @@
|
||||
# file openpyxl/reader/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
|
||||
|
||||
"""Read the shared strings table."""
|
||||
|
||||
# package imports
|
||||
from ..shared.xmltools import fromstring, QName
|
||||
from ..shared.ooxml import NAMESPACES
|
||||
|
||||
|
||||
def read_string_table(xml_source):
|
||||
"""Read in all shared strings in the table"""
|
||||
table = {}
|
||||
xmlns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
|
||||
root = fromstring(text=xml_source)
|
||||
string_index_nodes = root.findall(QName(xmlns, 'si').text)
|
||||
for index, string_index_node in enumerate(string_index_nodes):
|
||||
table[index] = get_string(xmlns, string_index_node)
|
||||
return table
|
||||
|
||||
|
||||
def get_string(xmlns, string_index_node):
|
||||
"""Read the contents of a specific string index"""
|
||||
rich_nodes = string_index_node.findall(QName(xmlns, 'r').text)
|
||||
if rich_nodes:
|
||||
reconstructed_text = []
|
||||
for rich_node in rich_nodes:
|
||||
partial_text = get_text(xmlns, rich_node)
|
||||
reconstructed_text.append(partial_text)
|
||||
return ''.join(reconstructed_text)
|
||||
else:
|
||||
return get_text(xmlns, string_index_node)
|
||||
|
||||
|
||||
def get_text(xmlns, rich_node):
|
||||
"""Read rich text, discarding formatting if not disallowed"""
|
||||
text_node = rich_node.find(QName(xmlns, 't').text)
|
||||
partial_text = text_node.text or ''
|
||||
|
||||
if text_node.get(QName(NAMESPACES['xml'], 'space').text) != 'preserve':
|
||||
partial_text = partial_text.strip()
|
||||
return unicode(partial_text)
|
||||
@@ -1,69 +0,0 @@
|
||||
# file openpyxl/reader/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
|
||||
|
||||
"""Read shared style definitions"""
|
||||
|
||||
# package imports
|
||||
from ..shared.xmltools import fromstring, QName
|
||||
from ..shared.exc import MissingNumberFormat
|
||||
from ..style import Style, NumberFormat
|
||||
|
||||
|
||||
def read_style_table(xml_source):
|
||||
"""Read styles from the shared style table"""
|
||||
table = {}
|
||||
xmlns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
|
||||
root = fromstring(xml_source)
|
||||
custom_num_formats = parse_custom_num_formats(root, xmlns)
|
||||
builtin_formats = NumberFormat._BUILTIN_FORMATS
|
||||
cell_xfs = root.find(QName(xmlns, 'cellXfs').text)
|
||||
cell_xfs_nodes = cell_xfs.findall(QName(xmlns, 'xf').text)
|
||||
for index, cell_xfs_node in enumerate(cell_xfs_nodes):
|
||||
new_style = Style()
|
||||
number_format_id = int(cell_xfs_node.get('numFmtId'))
|
||||
if number_format_id < 164:
|
||||
new_style.number_format.format_code = \
|
||||
builtin_formats.get(number_format_id, 'General')
|
||||
else:
|
||||
|
||||
if number_format_id in custom_num_formats:
|
||||
new_style.number_format.format_code = \
|
||||
custom_num_formats[number_format_id]
|
||||
else:
|
||||
raise MissingNumberFormat('%s' % number_format_id)
|
||||
table[index] = new_style
|
||||
return table
|
||||
|
||||
|
||||
def parse_custom_num_formats(root, xmlns):
|
||||
"""Read in custom numeric formatting rules from the shared style table"""
|
||||
custom_formats = {}
|
||||
num_fmts = root.find(QName(xmlns, 'numFmts').text)
|
||||
if num_fmts is not None:
|
||||
num_fmt_nodes = num_fmts.findall(QName(xmlns, 'numFmt').text)
|
||||
for num_fmt_node in num_fmt_nodes:
|
||||
custom_formats[int(num_fmt_node.get('numFmtId'))] = \
|
||||
num_fmt_node.get('formatCode')
|
||||
return custom_formats
|
||||
@@ -1,156 +0,0 @@
|
||||
# file openpyxl/reader/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
|
||||
|
||||
"""Read in global settings to be maintained by the workbook object."""
|
||||
|
||||
# package imports
|
||||
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
|
||||
|
||||
# constants
|
||||
BUGGY_NAMED_RANGES = ['NA()', '#REF!']
|
||||
DISCARDED_RANGES = ['Excel_BuiltIn', 'Print_Area']
|
||||
|
||||
def get_sheet_ids(xml_source):
|
||||
|
||||
sheet_names = read_sheets_titles(xml_source)
|
||||
|
||||
return dict((sheet, 'sheet%d.xml' % (i + 1)) for i, sheet in enumerate(sheet_names))
|
||||
|
||||
|
||||
def read_properties_core(xml_source):
|
||||
"""Read assorted file properties."""
|
||||
properties = DocumentProperties()
|
||||
root = fromstring(xml_source)
|
||||
creator_node = root.find(QName(NAMESPACES['dc'], 'creator').text)
|
||||
if creator_node is not None:
|
||||
properties.creator = creator_node.text
|
||||
else:
|
||||
properties.creator = ''
|
||||
last_modified_by_node = root.find(
|
||||
QName(NAMESPACES['cp'], 'lastModifiedBy').text)
|
||||
if last_modified_by_node is not None:
|
||||
properties.last_modified_by = last_modified_by_node.text
|
||||
else:
|
||||
properties.last_modified_by = ''
|
||||
|
||||
created_node = root.find(QName(NAMESPACES['dcterms'], 'created').text)
|
||||
if created_node is not None:
|
||||
properties.created = W3CDTF_to_datetime(created_node.text)
|
||||
else:
|
||||
properties.created = datetime.datetime.now()
|
||||
|
||||
modified_node = root.find(QName(NAMESPACES['dcterms'], 'modified').text)
|
||||
if modified_node is not None:
|
||||
properties.modified = W3CDTF_to_datetime(modified_node.text)
|
||||
else:
|
||||
properties.modified = properties.created
|
||||
|
||||
return properties
|
||||
|
||||
|
||||
def get_number_of_parts(xml_source):
|
||||
"""Get a list of contents of the workbook."""
|
||||
parts_size = {}
|
||||
parts_names = []
|
||||
root = fromstring(xml_source)
|
||||
heading_pairs = root.find(QName('http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
|
||||
'HeadingPairs').text)
|
||||
vector = heading_pairs.find(QName(NAMESPACES['vt'], 'vector').text)
|
||||
children = vector.getchildren()
|
||||
for child_id in range(0, len(children), 2):
|
||||
part_name = children[child_id].find(QName(NAMESPACES['vt'],
|
||||
'lpstr').text).text
|
||||
if not part_name in parts_names:
|
||||
parts_names.append(part_name)
|
||||
part_size = int(children[child_id + 1].find(QName(
|
||||
NAMESPACES['vt'], 'i4').text).text)
|
||||
parts_size[part_name] = part_size
|
||||
return parts_size, parts_names
|
||||
|
||||
|
||||
def read_sheets_titles(xml_source):
|
||||
"""Read titles for all sheets."""
|
||||
root = fromstring(xml_source)
|
||||
titles_root = root.find(QName('http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
|
||||
'TitlesOfParts').text)
|
||||
vector = titles_root.find(QName(NAMESPACES['vt'], 'vector').text)
|
||||
parts, names = get_number_of_parts(xml_source)
|
||||
|
||||
# we can't assume 'Worksheets' to be written in english,
|
||||
# but it's always the first item of the parts list (see bug #22)
|
||||
size = parts[names[0]]
|
||||
children = [c.text for c in vector.getchildren()]
|
||||
return children[:size]
|
||||
|
||||
|
||||
def read_named_ranges(xml_source, workbook):
|
||||
"""Read named ranges, excluding poorly defined ranges."""
|
||||
named_ranges = []
|
||||
root = fromstring(xml_source)
|
||||
names_root = root.find(QName('http://schemas.openxmlformats.org/spreadsheetml/2006/main',
|
||||
'definedNames').text)
|
||||
if names_root is not None:
|
||||
|
||||
for name_node in names_root.getchildren():
|
||||
range_name = name_node.get('name')
|
||||
|
||||
if name_node.get("hidden", '0') == '1':
|
||||
continue
|
||||
|
||||
valid = True
|
||||
|
||||
for discarded_range in DISCARDED_RANGES:
|
||||
if discarded_range in range_name:
|
||||
valid = False
|
||||
|
||||
for bad_range in BUGGY_NAMED_RANGES:
|
||||
if bad_range in name_node.text:
|
||||
valid = False
|
||||
|
||||
if valid:
|
||||
destinations = split_named_range(name_node.text)
|
||||
|
||||
new_destinations = []
|
||||
for worksheet, cells_range in destinations:
|
||||
|
||||
# it can happen that a valid named range references
|
||||
# a missing worksheet, when Excel didn't properly maintain
|
||||
# the named range list
|
||||
#
|
||||
# we just ignore them here
|
||||
worksheet = workbook.get_sheet_by_name(worksheet)
|
||||
if worksheet:
|
||||
new_destinations.append((worksheet, cells_range))
|
||||
|
||||
named_range = NamedRange(range_name, new_destinations)
|
||||
named_ranges.append(named_range)
|
||||
|
||||
return named_ranges
|
||||
@@ -1,114 +0,0 @@
|
||||
# file openpyxl/reader/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
|
||||
|
||||
"""Reader for a single worksheet."""
|
||||
|
||||
# Python stdlib imports
|
||||
try:
|
||||
from xml.etree.cElementTree import iterparse
|
||||
except ImportError:
|
||||
from xml.etree.ElementTree import iterparse
|
||||
|
||||
from ....compat import ifilter
|
||||
from ....compat import BytesIO as StringIO
|
||||
|
||||
# package imports
|
||||
from ..cell import Cell, coordinate_from_string
|
||||
from ..worksheet import Worksheet
|
||||
|
||||
def _get_xml_iter(xml_source):
|
||||
|
||||
if not hasattr(xml_source, 'name'):
|
||||
return StringIO(xml_source)
|
||||
else:
|
||||
xml_source.seek(0)
|
||||
return xml_source
|
||||
|
||||
def read_dimension(xml_source):
|
||||
|
||||
source = _get_xml_iter(xml_source)
|
||||
|
||||
it = iterparse(source)
|
||||
|
||||
for event, element in it:
|
||||
|
||||
if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}dimension':
|
||||
ref = element.get('ref')
|
||||
|
||||
min_range, max_range = ref.split(':')
|
||||
min_col, min_row = coordinate_from_string(min_range)
|
||||
max_col, max_row = coordinate_from_string(max_range)
|
||||
|
||||
return min_col, min_row, max_col, max_row
|
||||
|
||||
else:
|
||||
element.clear()
|
||||
|
||||
return None
|
||||
|
||||
def filter_cells(x):
|
||||
(event, element) = x
|
||||
|
||||
return element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c'
|
||||
|
||||
def fast_parse(ws, xml_source, string_table, style_table):
|
||||
|
||||
source = _get_xml_iter(xml_source)
|
||||
|
||||
it = iterparse(source)
|
||||
|
||||
for event, element in ifilter(filter_cells, it):
|
||||
|
||||
value = element.findtext('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v')
|
||||
|
||||
if value is not None:
|
||||
|
||||
coordinate = element.get('r')
|
||||
data_type = element.get('t', 'n')
|
||||
style_id = element.get('s')
|
||||
|
||||
if data_type == Cell.TYPE_STRING:
|
||||
value = string_table.get(int(value))
|
||||
|
||||
ws.cell(coordinate).value = value
|
||||
|
||||
if style_id is not None:
|
||||
ws._styles[coordinate] = style_table.get(int(style_id))
|
||||
|
||||
# to avoid memory exhaustion, clear the item after use
|
||||
element.clear()
|
||||
|
||||
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,
|
||||
sheet_codename, xml_source)
|
||||
else:
|
||||
ws = Worksheet(parent, preset_title)
|
||||
fast_parse(ws, xml_source, string_table, style_table)
|
||||
return ws
|
||||
@@ -1,33 +0,0 @@
|
||||
# file openpyxl/shared/__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.shared namespace."""
|
||||
|
||||
# package imports
|
||||
from . import date_time
|
||||
from . import exc
|
||||
from . import ooxml
|
||||
from . import password_hasher
|
||||
from . import xmltools
|
||||
@@ -1,154 +0,0 @@
|
||||
# file openpyxl/shared/date_time.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 Excel date weirdness."""
|
||||
|
||||
# Python stdlib imports
|
||||
from __future__ import division
|
||||
from math import floor
|
||||
import calendar
|
||||
import datetime
|
||||
import time
|
||||
import re
|
||||
|
||||
# constants
|
||||
W3CDTF_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
|
||||
|
||||
RE_W3CDTF = '(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(.(\d{2}))?Z'
|
||||
|
||||
EPOCH = datetime.datetime.utcfromtimestamp(0)
|
||||
|
||||
def datetime_to_W3CDTF(dt):
|
||||
"""Convert from a datetime to a timestamp string."""
|
||||
return datetime.datetime.strftime(dt, W3CDTF_FORMAT)
|
||||
|
||||
|
||||
def W3CDTF_to_datetime(formatted_string):
|
||||
"""Convert from a timestamp string to a datetime object."""
|
||||
match = re.match(RE_W3CDTF,formatted_string)
|
||||
digits = map(int, match.groups()[:6])
|
||||
return datetime.datetime(*digits)
|
||||
|
||||
|
||||
class SharedDate(object):
|
||||
"""Date formatting utilities for Excel with shared state.
|
||||
|
||||
Excel has a two primary date tracking schemes:
|
||||
Windows - Day 1 == 1900-01-01
|
||||
Mac - Day 1 == 1904-01-01
|
||||
|
||||
SharedDate stores which system we are using and converts dates between
|
||||
Python and Excel accordingly.
|
||||
|
||||
"""
|
||||
CALENDAR_WINDOWS_1900 = 1900
|
||||
CALENDAR_MAC_1904 = 1904
|
||||
datetime_object_type = 'DateTime'
|
||||
|
||||
def __init__(self):
|
||||
self.excel_base_date = self.CALENDAR_WINDOWS_1900
|
||||
|
||||
def datetime_to_julian(self, date):
|
||||
"""Convert from python datetime to excel julian date representation."""
|
||||
|
||||
if isinstance(date, datetime.datetime):
|
||||
return self.to_julian(date.year, date.month, date.day, \
|
||||
hours=date.hour, minutes=date.minute, seconds=date.second)
|
||||
elif isinstance(date, datetime.date):
|
||||
return self.to_julian(date.year, date.month, date.day)
|
||||
|
||||
def to_julian(self, year, month, day, hours=0, minutes=0, seconds=0):
|
||||
"""Convert from Python date to Excel JD."""
|
||||
# explicitly disallow bad years
|
||||
# Excel 2000 treats JD=0 as 1/0/1900 (buggy, disallow)
|
||||
# Excel 2000 treats JD=2958466 as a bad date (Y10K bug!)
|
||||
if year < 1900 or year > 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)
|
||||
@@ -1,59 +0,0 @@
|
||||
# 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"""
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
# 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'
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
# 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 <xnoguer@rezebra.com>.
|
||||
|
||||
"""
|
||||
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:]
|
||||
@@ -1,67 +0,0 @@
|
||||
# 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
|
||||
@@ -1,114 +0,0 @@
|
||||
# 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 = {}
|
||||
|
||||
|
||||
# name = bytes(name, 'utf-8')
|
||||
|
||||
# if namespace is not None:
|
||||
# namespace = bytes(namespace, 'utf-8')
|
||||
|
||||
|
||||
attr_vals = {}
|
||||
attr_keys = {}
|
||||
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:
|
||||
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)
|
||||
@@ -1,392 +0,0 @@
|
||||
# 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()
|
||||
@@ -1,186 +0,0 @@
|
||||
# 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)
|
||||
@@ -1,534 +0,0 @@
|
||||
# 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 '<Worksheet "%s">' % 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 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.items():
|
||||
|
||||
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)
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# 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
|
||||
@@ -1,261 +0,0 @@
|
||||
# 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], 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.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)
|
||||
@@ -1,192 +0,0 @@
|
||||
# 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'})
|
||||
@@ -1,256 +0,0 @@
|
||||
# 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 []
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
# 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 ....compat 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))
|
||||
|
||||
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):
|
||||
|
||||
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
|
||||
@@ -1,86 +0,0 @@
|
||||
# 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 ....compat 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
|
||||
@@ -1,256 +0,0 @@
|
||||
# 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
|
||||
@@ -1,202 +0,0 @@
|
||||
# -*- 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(
|
||||
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
|
||||
|
||||
'<a:theme xmlns:a="http://schemas.openxmlformats.org/'
|
||||
'drawingml/2006/main" name="Office Theme">'
|
||||
'<a:themeElements>'
|
||||
|
||||
'<a:clrScheme name="Office">'
|
||||
'<a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>'
|
||||
'<a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>'
|
||||
'<a:dk2><a:srgbClr val="1F497D"/></a:dk2>'
|
||||
'<a:lt2><a:srgbClr val="EEECE1"/></a:lt2>'
|
||||
'<a:accent1><a:srgbClr val="4F81BD"/></a:accent1>'
|
||||
'<a:accent2><a:srgbClr val="C0504D"/></a:accent2>'
|
||||
'<a:accent3><a:srgbClr val="9BBB59"/></a:accent3>'
|
||||
'<a:accent4><a:srgbClr val="8064A2"/></a:accent4>'
|
||||
'<a:accent5><a:srgbClr val="4BACC6"/></a:accent5>'
|
||||
'<a:accent6><a:srgbClr val="F79646"/></a:accent6>'
|
||||
'<a:hlink><a:srgbClr val="0000FF"/></a:hlink>'
|
||||
'<a:folHlink><a:srgbClr val="800080"/></a:folHlink>'
|
||||
'</a:clrScheme>'
|
||||
|
||||
'<a:fontScheme name="Office">'
|
||||
'<a:majorFont>'
|
||||
'<a:latin typeface="Cambria"/>'
|
||||
'<a:ea typeface=""/>'
|
||||
'<a:cs typeface=""/>'
|
||||
'<a:font script="Jpan" typeface="MS Pゴシック"/>'
|
||||
'<a:font script="Hang" typeface="맑은 고딕"/>'
|
||||
'<a:font script="Hans" typeface="宋体"/>'
|
||||
'<a:font script="Hant" typeface="新細明體"/>'
|
||||
'<a:font script="Arab" typeface="Times New Roman"/>'
|
||||
'<a:font script="Hebr" typeface="Times New Roman"/>'
|
||||
'<a:font script="Thai" typeface="Tahoma"/>'
|
||||
'<a:font script="Ethi" typeface="Nyala"/>'
|
||||
'<a:font script="Beng" typeface="Vrinda"/>'
|
||||
'<a:font script="Gujr" typeface="Shruti"/>'
|
||||
'<a:font script="Khmr" typeface="MoolBoran"/>'
|
||||
'<a:font script="Knda" typeface="Tunga"/>'
|
||||
'<a:font script="Guru" typeface="Raavi"/>'
|
||||
'<a:font script="Cans" typeface="Euphemia"/>'
|
||||
'<a:font script="Cher" typeface="Plantagenet Cherokee"/>'
|
||||
'<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>'
|
||||
'<a:font script="Tibt" typeface="Microsoft Himalaya"/>'
|
||||
'<a:font script="Thaa" typeface="MV Boli"/>'
|
||||
'<a:font script="Deva" typeface="Mangal"/>'
|
||||
'<a:font script="Telu" typeface="Gautami"/>'
|
||||
'<a:font script="Taml" typeface="Latha"/>'
|
||||
'<a:font script="Syrc" typeface="Estrangelo Edessa"/>'
|
||||
'<a:font script="Orya" typeface="Kalinga"/>'
|
||||
'<a:font script="Mlym" typeface="Kartika"/>'
|
||||
'<a:font script="Laoo" typeface="DokChampa"/>'
|
||||
'<a:font script="Sinh" typeface="Iskoola Pota"/>'
|
||||
'<a:font script="Mong" typeface="Mongolian Baiti"/>'
|
||||
'<a:font script="Viet" typeface="Times New Roman"/>'
|
||||
'<a:font script="Uigh" typeface="Microsoft Uighur"/>'
|
||||
'</a:majorFont>'
|
||||
'<a:minorFont>'
|
||||
'<a:latin typeface="Calibri"/>'
|
||||
'<a:ea typeface=""/>'
|
||||
'<a:cs typeface=""/>'
|
||||
'<a:font script="Jpan" typeface="MS Pゴシック"/>'
|
||||
'<a:font script="Hang" typeface="맑은 고딕"/>'
|
||||
'<a:font script="Hans" typeface="宋体"/>'
|
||||
'<a:font script="Hant" typeface="新細明體"/>'
|
||||
'<a:font script="Arab" typeface="Arial"/>'
|
||||
'<a:font script="Hebr" typeface="Arial"/>'
|
||||
'<a:font script="Thai" typeface="Tahoma"/>'
|
||||
'<a:font script="Ethi" typeface="Nyala"/>'
|
||||
'<a:font script="Beng" typeface="Vrinda"/>'
|
||||
'<a:font script="Gujr" typeface="Shruti"/>'
|
||||
'<a:font script="Khmr" typeface="DaunPenh"/>'
|
||||
'<a:font script="Knda" typeface="Tunga"/>'
|
||||
'<a:font script="Guru" typeface="Raavi"/>'
|
||||
'<a:font script="Cans" typeface="Euphemia"/>'
|
||||
'<a:font script="Cher" typeface="Plantagenet Cherokee"/>'
|
||||
'<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>'
|
||||
'<a:font script="Tibt" typeface="Microsoft Himalaya"/>'
|
||||
'<a:font script="Thaa" typeface="MV Boli"/>'
|
||||
'<a:font script="Deva" typeface="Mangal"/>'
|
||||
'<a:font script="Telu" typeface="Gautami"/>'
|
||||
'<a:font script="Taml" typeface="Latha"/>'
|
||||
'<a:font script="Syrc" typeface="Estrangelo Edessa"/>'
|
||||
'<a:font script="Orya" typeface="Kalinga"/>'
|
||||
'<a:font script="Mlym" typeface="Kartika"/>'
|
||||
'<a:font script="Laoo" typeface="DokChampa"/>'
|
||||
'<a:font script="Sinh" typeface="Iskoola Pota"/>'
|
||||
'<a:font script="Mong" typeface="Mongolian Baiti"/>'
|
||||
'<a:font script="Viet" typeface="Arial"/>'
|
||||
'<a:font script="Uigh" typeface="Microsoft Uighur"/>'
|
||||
'</a:minorFont>'
|
||||
'</a:fontScheme>'
|
||||
|
||||
'<a:fmtScheme name="Office">'
|
||||
'<a:fillStyleLst>'
|
||||
'<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>'
|
||||
'<a:gradFill rotWithShape="1"><a:gsLst>'
|
||||
'<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="50000"/>'
|
||||
'<a:satMod val="300000"/></a:schemeClr></a:gs>'
|
||||
'<a:gs pos="35000"><a:schemeClr val="phClr"><a:tint val="37000"/>'
|
||||
'<a:satMod val="300000"/></a:schemeClr></a:gs>'
|
||||
'<a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="15000"/>'
|
||||
'<a:satMod val="350000"/></a:schemeClr></a:gs></a:gsLst>'
|
||||
'<a:lin ang="16200000" scaled="1"/></a:gradFill>'
|
||||
'<a:gradFill rotWithShape="1"><a:gsLst>'
|
||||
'<a:gs pos="0"><a:schemeClr val="phClr"><a:shade val="51000"/>'
|
||||
'<a:satMod val="130000"/></a:schemeClr></a:gs>'
|
||||
'<a:gs pos="80000"><a:schemeClr val="phClr"><a:shade val="93000"/>'
|
||||
'<a:satMod val="130000"/></a:schemeClr></a:gs>'
|
||||
'<a:gs pos="100000"><a:schemeClr val="phClr">'
|
||||
'<a:shade val="94000"/>'
|
||||
'<a:satMod val="135000"/></a:schemeClr></a:gs></a:gsLst>'
|
||||
'<a:lin ang="16200000" scaled="0"/></a:gradFill></a:fillStyleLst>'
|
||||
'<a:lnStyleLst>'
|
||||
'<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr">'
|
||||
'<a:solidFill><a:schemeClr val="phClr"><a:shade val="95000"/>'
|
||||
'<a:satMod val="105000"/></a:schemeClr></a:solidFill>'
|
||||
'<a:prstDash val="solid"/></a:ln>'
|
||||
'<a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill>'
|
||||
'<a:schemeClr val="phClr"/></a:solidFill>'
|
||||
'<a:prstDash val="solid"/></a:ln>'
|
||||
'<a:ln w="38100" cap="flat" cmpd="sng" algn="ctr"><a:solidFill>'
|
||||
'<a:schemeClr val="phClr"/></a:solidFill>'
|
||||
'<a:prstDash val="solid"/></a:ln></a:lnStyleLst>'
|
||||
'<a:effectStyleLst><a:effectStyle><a:effectLst>'
|
||||
'<a:outerShdw blurRad="40000" dist="20000" dir="5400000" '
|
||||
'rotWithShape="0"><a:srgbClr val="000000">'
|
||||
'<a:alpha val="38000"/></a:srgbClr></a:outerShdw></a:effectLst>'
|
||||
'</a:effectStyle><a:effectStyle><a:effectLst>'
|
||||
'<a:outerShdw blurRad="40000" dist="23000" dir="5400000" '
|
||||
'rotWithShape="0"><a:srgbClr val="000000">'
|
||||
'<a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst>'
|
||||
'</a:effectStyle><a:effectStyle><a:effectLst>'
|
||||
'<a:outerShdw blurRad="40000" dist="23000" dir="5400000" '
|
||||
'rotWithShape="0"><a:srgbClr val="000000">'
|
||||
'<a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst>'
|
||||
'<a:scene3d><a:camera prst="orthographicFront">'
|
||||
'<a:rot lat="0" lon="0" rev="0"/></a:camera>'
|
||||
'<a:lightRig rig="threePt" dir="t">'
|
||||
'<a:rot lat="0" lon="0" rev="1200000"/></a:lightRig>'
|
||||
'</a:scene3d><a:sp3d><a:bevelT w="63500" h="25400"/>'
|
||||
'</a:sp3d></a:effectStyle></a:effectStyleLst>'
|
||||
'<a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/>'
|
||||
'</a:solidFill><a:gradFill rotWithShape="1"><a:gsLst>'
|
||||
'<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="40000"/>'
|
||||
'<a:satMod val="350000"/></a:schemeClr></a:gs>'
|
||||
'<a:gs pos="40000"><a:schemeClr val="phClr"><a:tint val="45000"/>'
|
||||
'<a:shade val="99000"/><a:satMod val="350000"/>'
|
||||
'</a:schemeClr></a:gs>'
|
||||
'<a:gs pos="100000"><a:schemeClr val="phClr">'
|
||||
'<a:shade val="20000"/><a:satMod val="255000"/>'
|
||||
'</a:schemeClr></a:gs></a:gsLst>'
|
||||
'<a:path path="circle">'
|
||||
'<a:fillToRect l="50000" t="-80000" r="50000" b="180000"/>'
|
||||
'</a:path>'
|
||||
'</a:gradFill><a:gradFill rotWithShape="1"><a:gsLst>'
|
||||
'<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="80000"/>'
|
||||
'<a:satMod val="300000"/></a:schemeClr></a:gs>'
|
||||
'<a:gs pos="100000"><a:schemeClr val="phClr">'
|
||||
'<a:shade val="30000"/><a:satMod val="200000"/>'
|
||||
'</a:schemeClr></a:gs></a:gsLst>'
|
||||
'<a:path path="circle">'
|
||||
'<a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path>'
|
||||
'</a:gradFill></a:bgFillStyleLst></a:fmtScheme>'
|
||||
'</a:themeElements>'
|
||||
'<a:objectDefaults/><a:extraClrSchemeLst/>'
|
||||
'</a:theme>')
|
||||
return get_document_content(xml_node)
|
||||
@@ -1,204 +0,0 @@
|
||||
# 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)
|
||||
@@ -1,209 +0,0 @@
|
||||
# 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 ....compat 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)
|
||||
@@ -1,53 +0,0 @@
|
||||
# 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',)
|
||||
@@ -1,384 +0,0 @@
|
||||
# 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 "<Cell %s.%s>" % (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:`.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:`.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)))
|
||||
@@ -1,340 +0,0 @@
|
||||
'''
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,402 +0,0 @@
|
||||
'''
|
||||
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))
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# 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
|
||||
@@ -1,33 +0,0 @@
|
||||
# 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 . import excel
|
||||
from . import strings
|
||||
from . import style
|
||||
from . import workbook
|
||||
from . import worksheet
|
||||
@@ -1,121 +0,0 @@
|
||||
# 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 .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 .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
|
||||
|
||||
:param filename: the path to open
|
||||
:type filename: string
|
||||
|
||||
:param use_iterators: use lazy load for cells
|
||||
:type use_iterators: bool
|
||||
|
||||
:rtype: :class:`..workbook.Workbook`
|
||||
|
||||
.. note::
|
||||
|
||||
When using lazy load, all worksheets will be :class:`.iter_worksheet.IterableWorksheet`
|
||||
and the returned workbook will be read-only.
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
# 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")
|
||||
except AttributeError:
|
||||
# filename is not an object
|
||||
# it doesn't have mode attribute
|
||||
pass
|
||||
|
||||
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)
|
||||
@@ -1,343 +0,0 @@
|
||||
# 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 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 .excel import get_sheet_ids
|
||||
from .strings import read_string_table
|
||||
from .style import read_style_table, NumberFormat
|
||||
from ..shared.date_time import SharedDate
|
||||
from .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:`..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("<HH", archive.fp.read(4))
|
||||
return zinfo.header_offset + 30 + file_name_len + extra_len
|
||||
@@ -1,64 +0,0 @@
|
||||
# file openpyxl/reader/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
|
||||
|
||||
"""Read the shared strings table."""
|
||||
|
||||
# package imports
|
||||
from ..shared.xmltools import fromstring, QName
|
||||
from ..shared.ooxml import NAMESPACES
|
||||
|
||||
|
||||
def read_string_table(xml_source):
|
||||
"""Read in all shared strings in the table"""
|
||||
table = {}
|
||||
xmlns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
|
||||
root = fromstring(text=xml_source)
|
||||
string_index_nodes = root.findall(QName(xmlns, 'si').text)
|
||||
for index, string_index_node in enumerate(string_index_nodes):
|
||||
table[index] = get_string(xmlns, string_index_node)
|
||||
return table
|
||||
|
||||
|
||||
def get_string(xmlns, string_index_node):
|
||||
"""Read the contents of a specific string index"""
|
||||
rich_nodes = string_index_node.findall(QName(xmlns, 'r').text)
|
||||
if rich_nodes:
|
||||
reconstructed_text = []
|
||||
for rich_node in rich_nodes:
|
||||
partial_text = get_text(xmlns, rich_node)
|
||||
reconstructed_text.append(partial_text)
|
||||
return ''.join(reconstructed_text)
|
||||
else:
|
||||
return get_text(xmlns, string_index_node)
|
||||
|
||||
|
||||
def get_text(xmlns, rich_node):
|
||||
"""Read rich text, discarding formatting if not disallowed"""
|
||||
text_node = rich_node.find(QName(xmlns, 't').text)
|
||||
partial_text = text_node.text or ''
|
||||
|
||||
if text_node.get(QName(NAMESPACES['xml'], 'space').text) != 'preserve':
|
||||
partial_text = partial_text.strip()
|
||||
return str(partial_text)
|
||||
@@ -1,69 +0,0 @@
|
||||
# file openpyxl/reader/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
|
||||
|
||||
"""Read shared style definitions"""
|
||||
|
||||
# package imports
|
||||
from ..shared.xmltools import fromstring, QName
|
||||
from ..shared.exc import MissingNumberFormat
|
||||
from ..style import Style, NumberFormat
|
||||
|
||||
|
||||
def read_style_table(xml_source):
|
||||
"""Read styles from the shared style table"""
|
||||
table = {}
|
||||
xmlns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
|
||||
root = fromstring(xml_source)
|
||||
custom_num_formats = parse_custom_num_formats(root, xmlns)
|
||||
builtin_formats = NumberFormat._BUILTIN_FORMATS
|
||||
cell_xfs = root.find(QName(xmlns, 'cellXfs').text)
|
||||
cell_xfs_nodes = cell_xfs.findall(QName(xmlns, 'xf').text)
|
||||
for index, cell_xfs_node in enumerate(cell_xfs_nodes):
|
||||
new_style = Style()
|
||||
number_format_id = int(cell_xfs_node.get('numFmtId'))
|
||||
if number_format_id < 164:
|
||||
new_style.number_format.format_code = \
|
||||
builtin_formats.get(number_format_id, 'General')
|
||||
else:
|
||||
|
||||
if number_format_id in custom_num_formats:
|
||||
new_style.number_format.format_code = \
|
||||
custom_num_formats[number_format_id]
|
||||
else:
|
||||
raise MissingNumberFormat('%s' % number_format_id)
|
||||
table[index] = new_style
|
||||
return table
|
||||
|
||||
|
||||
def parse_custom_num_formats(root, xmlns):
|
||||
"""Read in custom numeric formatting rules from the shared style table"""
|
||||
custom_formats = {}
|
||||
num_fmts = root.find(QName(xmlns, 'numFmts').text)
|
||||
if num_fmts is not None:
|
||||
num_fmt_nodes = num_fmts.findall(QName(xmlns, 'numFmt').text)
|
||||
for num_fmt_node in num_fmt_nodes:
|
||||
custom_formats[int(num_fmt_node.get('numFmtId'))] = \
|
||||
num_fmt_node.get('formatCode')
|
||||
return custom_formats
|
||||
@@ -1,156 +0,0 @@
|
||||
# file openpyxl/reader/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
|
||||
|
||||
"""Read in global settings to be maintained by the workbook object."""
|
||||
|
||||
# package imports
|
||||
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
|
||||
|
||||
# constants
|
||||
BUGGY_NAMED_RANGES = ['NA()', '#REF!']
|
||||
DISCARDED_RANGES = ['Excel_BuiltIn', 'Print_Area']
|
||||
|
||||
def get_sheet_ids(xml_source):
|
||||
|
||||
sheet_names = read_sheets_titles(xml_source)
|
||||
|
||||
return dict((sheet, 'sheet%d.xml' % (i + 1)) for i, sheet in enumerate(sheet_names))
|
||||
|
||||
|
||||
def read_properties_core(xml_source):
|
||||
"""Read assorted file properties."""
|
||||
properties = DocumentProperties()
|
||||
root = fromstring(xml_source)
|
||||
creator_node = root.find(QName(NAMESPACES['dc'], 'creator').text)
|
||||
if creator_node is not None:
|
||||
properties.creator = creator_node.text
|
||||
else:
|
||||
properties.creator = ''
|
||||
last_modified_by_node = root.find(
|
||||
QName(NAMESPACES['cp'], 'lastModifiedBy').text)
|
||||
if last_modified_by_node is not None:
|
||||
properties.last_modified_by = last_modified_by_node.text
|
||||
else:
|
||||
properties.last_modified_by = ''
|
||||
|
||||
created_node = root.find(QName(NAMESPACES['dcterms'], 'created').text)
|
||||
if created_node is not None:
|
||||
properties.created = W3CDTF_to_datetime(created_node.text)
|
||||
else:
|
||||
properties.created = datetime.datetime.now()
|
||||
|
||||
modified_node = root.find(QName(NAMESPACES['dcterms'], 'modified').text)
|
||||
if modified_node is not None:
|
||||
properties.modified = W3CDTF_to_datetime(modified_node.text)
|
||||
else:
|
||||
properties.modified = properties.created
|
||||
|
||||
return properties
|
||||
|
||||
|
||||
def get_number_of_parts(xml_source):
|
||||
"""Get a list of contents of the workbook."""
|
||||
parts_size = {}
|
||||
parts_names = []
|
||||
root = fromstring(xml_source)
|
||||
heading_pairs = root.find(QName('http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
|
||||
'HeadingPairs').text)
|
||||
vector = heading_pairs.find(QName(NAMESPACES['vt'], 'vector').text)
|
||||
children = vector.getchildren()
|
||||
for child_id in range(0, len(children), 2):
|
||||
part_name = children[child_id].find(QName(NAMESPACES['vt'],
|
||||
'lpstr').text).text
|
||||
if not part_name in parts_names:
|
||||
parts_names.append(part_name)
|
||||
part_size = int(children[child_id + 1].find(QName(
|
||||
NAMESPACES['vt'], 'i4').text).text)
|
||||
parts_size[part_name] = part_size
|
||||
return parts_size, parts_names
|
||||
|
||||
|
||||
def read_sheets_titles(xml_source):
|
||||
"""Read titles for all sheets."""
|
||||
root = fromstring(xml_source)
|
||||
titles_root = root.find(QName('http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
|
||||
'TitlesOfParts').text)
|
||||
vector = titles_root.find(QName(NAMESPACES['vt'], 'vector').text)
|
||||
parts, names = get_number_of_parts(xml_source)
|
||||
|
||||
# we can't assume 'Worksheets' to be written in english,
|
||||
# but it's always the first item of the parts list (see bug #22)
|
||||
size = parts[names[0]]
|
||||
children = [c.text for c in vector.getchildren()]
|
||||
return children[:size]
|
||||
|
||||
|
||||
def read_named_ranges(xml_source, workbook):
|
||||
"""Read named ranges, excluding poorly defined ranges."""
|
||||
named_ranges = []
|
||||
root = fromstring(xml_source)
|
||||
names_root = root.find(QName('http://schemas.openxmlformats.org/spreadsheetml/2006/main',
|
||||
'definedNames').text)
|
||||
if names_root is not None:
|
||||
|
||||
for name_node in names_root.getchildren():
|
||||
range_name = name_node.get('name')
|
||||
|
||||
if name_node.get("hidden", '0') == '1':
|
||||
continue
|
||||
|
||||
valid = True
|
||||
|
||||
for discarded_range in DISCARDED_RANGES:
|
||||
if discarded_range in range_name:
|
||||
valid = False
|
||||
|
||||
for bad_range in BUGGY_NAMED_RANGES:
|
||||
if bad_range in name_node.text:
|
||||
valid = False
|
||||
|
||||
if valid:
|
||||
destinations = split_named_range(name_node.text)
|
||||
|
||||
new_destinations = []
|
||||
for worksheet, cells_range in destinations:
|
||||
|
||||
# it can happen that a valid named range references
|
||||
# a missing worksheet, when Excel didn't properly maintain
|
||||
# the named range list
|
||||
#
|
||||
# we just ignore them here
|
||||
worksheet = workbook.get_sheet_by_name(worksheet)
|
||||
if worksheet:
|
||||
new_destinations.append((worksheet, cells_range))
|
||||
|
||||
named_range = NamedRange(range_name, new_destinations)
|
||||
named_ranges.append(named_range)
|
||||
|
||||
return named_ranges
|
||||
@@ -1,117 +0,0 @@
|
||||
# file openpyxl/reader/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
|
||||
|
||||
"""Reader for a single worksheet."""
|
||||
|
||||
# Python stdlib imports
|
||||
try:
|
||||
from xml.etree.cElementTree import iterparse
|
||||
except ImportError:
|
||||
from xml.etree.ElementTree import iterparse
|
||||
|
||||
from io import StringIO
|
||||
|
||||
# package imports
|
||||
from ..cell import Cell, coordinate_from_string
|
||||
from ..worksheet import Worksheet
|
||||
|
||||
def _get_xml_iter(xml_source):
|
||||
|
||||
if not hasattr(xml_source, 'name'):
|
||||
return StringIO(xml_source)
|
||||
else:
|
||||
xml_source.seek(0)
|
||||
return xml_source
|
||||
|
||||
def read_dimension(xml_source):
|
||||
|
||||
source = _get_xml_iter(xml_source)
|
||||
|
||||
it = iterparse(source)
|
||||
|
||||
for event, element in it:
|
||||
|
||||
if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}dimension':
|
||||
ref = element.get('ref')
|
||||
|
||||
if ':' in ref:
|
||||
min_range, max_range = ref.split(':')
|
||||
else:
|
||||
min_range = max_range = ref
|
||||
|
||||
min_col, min_row = coordinate_from_string(min_range)
|
||||
max_col, max_row = coordinate_from_string(max_range)
|
||||
|
||||
return min_col, min_row, max_col, max_row
|
||||
|
||||
else:
|
||||
element.clear()
|
||||
|
||||
return None
|
||||
|
||||
def filter_cells(xxx_todo_changeme):
|
||||
|
||||
(event, element) = xxx_todo_changeme
|
||||
return element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c'
|
||||
|
||||
def fast_parse(ws, xml_source, string_table, style_table):
|
||||
|
||||
source = _get_xml_iter(xml_source)
|
||||
|
||||
it = iterparse(source)
|
||||
|
||||
for event, element in filter(filter_cells, it):
|
||||
|
||||
value = element.findtext('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v')
|
||||
|
||||
if value is not None:
|
||||
|
||||
coordinate = element.get('r')
|
||||
data_type = element.get('t', 'n')
|
||||
style_id = element.get('s')
|
||||
|
||||
if data_type == Cell.TYPE_STRING:
|
||||
value = string_table.get(int(value))
|
||||
|
||||
ws.cell(coordinate).value = value
|
||||
|
||||
if style_id is not None:
|
||||
ws._styles[coordinate] = style_table.get(int(style_id))
|
||||
|
||||
# to avoid memory exhaustion, clear the item after use
|
||||
element.clear()
|
||||
|
||||
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,
|
||||
sheet_codename, xml_source)
|
||||
else:
|
||||
ws = Worksheet(parent, preset_title)
|
||||
fast_parse(ws, xml_source, string_table, style_table)
|
||||
return ws
|
||||
@@ -1,33 +0,0 @@
|
||||
# file openpyxl/shared/__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 . namespace."""
|
||||
|
||||
# package imports
|
||||
from . import date_time
|
||||
from . import exc
|
||||
from . import ooxml
|
||||
from . import password_hasher
|
||||
from . import xmltools
|
||||
@@ -1,154 +0,0 @@
|
||||
# file openpyxl/shared/date_time.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 Excel date weirdness."""
|
||||
|
||||
# Python stdlib imports
|
||||
|
||||
from math import floor
|
||||
import calendar
|
||||
import datetime
|
||||
import time
|
||||
import re
|
||||
|
||||
# constants
|
||||
W3CDTF_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
|
||||
|
||||
RE_W3CDTF = '(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(.(\d{2}))?Z'
|
||||
|
||||
EPOCH = datetime.datetime.utcfromtimestamp(0)
|
||||
|
||||
def datetime_to_W3CDTF(dt):
|
||||
"""Convert from a datetime to a timestamp string."""
|
||||
return datetime.datetime.strftime(dt, W3CDTF_FORMAT)
|
||||
|
||||
|
||||
def W3CDTF_to_datetime(formatted_string):
|
||||
"""Convert from a timestamp string to a datetime object."""
|
||||
match = re.match(RE_W3CDTF,formatted_string)
|
||||
digits = list(map(int, match.groups()[:6]))
|
||||
return datetime.datetime(*digits)
|
||||
|
||||
|
||||
class SharedDate(object):
|
||||
"""Date formatting utilities for Excel with shared state.
|
||||
|
||||
Excel has a two primary date tracking schemes:
|
||||
Windows - Day 1 == 1900-01-01
|
||||
Mac - Day 1 == 1904-01-01
|
||||
|
||||
SharedDate stores which system we are using and converts dates between
|
||||
Python and Excel accordingly.
|
||||
|
||||
"""
|
||||
CALENDAR_WINDOWS_1900 = 1900
|
||||
CALENDAR_MAC_1904 = 1904
|
||||
datetime_object_type = 'DateTime'
|
||||
|
||||
def __init__(self):
|
||||
self.excel_base_date = self.CALENDAR_WINDOWS_1900
|
||||
|
||||
def datetime_to_julian(self, date):
|
||||
"""Convert from python datetime to excel julian date representation."""
|
||||
|
||||
if isinstance(date, datetime.datetime):
|
||||
return self.to_julian(date.year, date.month, date.day, \
|
||||
hours=date.hour, minutes=date.minute, seconds=date.second)
|
||||
elif isinstance(date, datetime.date):
|
||||
return self.to_julian(date.year, date.month, date.day)
|
||||
|
||||
def to_julian(self, year, month, day, hours=0, minutes=0, seconds=0):
|
||||
"""Convert from Python date to Excel JD."""
|
||||
# explicitly disallow bad years
|
||||
# Excel 2000 treats JD=0 as 1/0/1900 (buggy, disallow)
|
||||
# Excel 2000 treats JD=2958466 as a bad date (Y10K bug!)
|
||||
if year < 1900 or year > 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)
|
||||
@@ -1,59 +0,0 @@
|
||||
# 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"""
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
# 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'
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
# 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 <xnoguer@rezebra.com>.
|
||||
|
||||
"""
|
||||
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:]
|
||||
@@ -1,67 +0,0 @@
|
||||
# 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
|
||||
@@ -1,96 +0,0 @@
|
||||
# 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)
|
||||
@@ -1,392 +0,0 @@
|
||||
# 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 list(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()
|
||||
@@ -1,186 +0,0 @@
|
||||
# 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)
|
||||
@@ -1,534 +0,0 @@
|
||||
# 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:`.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 '<Worksheet "%s">' % 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 list(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:`.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:`.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)
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# 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
|
||||
@@ -1,262 +0,0 @@
|
||||
# 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)
|
||||
@@ -1,192 +0,0 @@
|
||||
# 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'})
|
||||
@@ -1,256 +0,0 @@
|
||||
# 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:`..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='..', 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'
|
||||
|
||||
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 []
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
# 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 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 .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 .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):
|
||||
"""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
|
||||
@@ -1,86 +0,0 @@
|
||||
# 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 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(iter(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
|
||||
@@ -1,256 +0,0 @@
|
||||
# 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 list(worksheet._styles.values()):
|
||||
crc[hash(style)] = style
|
||||
self.style_table = dict([(style, i+1) \
|
||||
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]
|
||||
|
||||
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
|
||||
@@ -1,202 +0,0 @@
|
||||
# -*- 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(
|
||||
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
|
||||
|
||||
'<a:theme xmlns:a="http://schemas.openxmlformats.org/'
|
||||
'drawingml/2006/main" name="Office Theme">'
|
||||
'<a:themeElements>'
|
||||
|
||||
'<a:clrScheme name="Office">'
|
||||
'<a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>'
|
||||
'<a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>'
|
||||
'<a:dk2><a:srgbClr val="1F497D"/></a:dk2>'
|
||||
'<a:lt2><a:srgbClr val="EEECE1"/></a:lt2>'
|
||||
'<a:accent1><a:srgbClr val="4F81BD"/></a:accent1>'
|
||||
'<a:accent2><a:srgbClr val="C0504D"/></a:accent2>'
|
||||
'<a:accent3><a:srgbClr val="9BBB59"/></a:accent3>'
|
||||
'<a:accent4><a:srgbClr val="8064A2"/></a:accent4>'
|
||||
'<a:accent5><a:srgbClr val="4BACC6"/></a:accent5>'
|
||||
'<a:accent6><a:srgbClr val="F79646"/></a:accent6>'
|
||||
'<a:hlink><a:srgbClr val="0000FF"/></a:hlink>'
|
||||
'<a:folHlink><a:srgbClr val="800080"/></a:folHlink>'
|
||||
'</a:clrScheme>'
|
||||
|
||||
'<a:fontScheme name="Office">'
|
||||
'<a:majorFont>'
|
||||
'<a:latin typeface="Cambria"/>'
|
||||
'<a:ea typeface=""/>'
|
||||
'<a:cs typeface=""/>'
|
||||
'<a:font script="Jpan" typeface="MS Pゴシック"/>'
|
||||
'<a:font script="Hang" typeface="맑은 고딕"/>'
|
||||
'<a:font script="Hans" typeface="宋体"/>'
|
||||
'<a:font script="Hant" typeface="新細明體"/>'
|
||||
'<a:font script="Arab" typeface="Times New Roman"/>'
|
||||
'<a:font script="Hebr" typeface="Times New Roman"/>'
|
||||
'<a:font script="Thai" typeface="Tahoma"/>'
|
||||
'<a:font script="Ethi" typeface="Nyala"/>'
|
||||
'<a:font script="Beng" typeface="Vrinda"/>'
|
||||
'<a:font script="Gujr" typeface="Shruti"/>'
|
||||
'<a:font script="Khmr" typeface="MoolBoran"/>'
|
||||
'<a:font script="Knda" typeface="Tunga"/>'
|
||||
'<a:font script="Guru" typeface="Raavi"/>'
|
||||
'<a:font script="Cans" typeface="Euphemia"/>'
|
||||
'<a:font script="Cher" typeface="Plantagenet Cherokee"/>'
|
||||
'<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>'
|
||||
'<a:font script="Tibt" typeface="Microsoft Himalaya"/>'
|
||||
'<a:font script="Thaa" typeface="MV Boli"/>'
|
||||
'<a:font script="Deva" typeface="Mangal"/>'
|
||||
'<a:font script="Telu" typeface="Gautami"/>'
|
||||
'<a:font script="Taml" typeface="Latha"/>'
|
||||
'<a:font script="Syrc" typeface="Estrangelo Edessa"/>'
|
||||
'<a:font script="Orya" typeface="Kalinga"/>'
|
||||
'<a:font script="Mlym" typeface="Kartika"/>'
|
||||
'<a:font script="Laoo" typeface="DokChampa"/>'
|
||||
'<a:font script="Sinh" typeface="Iskoola Pota"/>'
|
||||
'<a:font script="Mong" typeface="Mongolian Baiti"/>'
|
||||
'<a:font script="Viet" typeface="Times New Roman"/>'
|
||||
'<a:font script="Uigh" typeface="Microsoft Uighur"/>'
|
||||
'</a:majorFont>'
|
||||
'<a:minorFont>'
|
||||
'<a:latin typeface="Calibri"/>'
|
||||
'<a:ea typeface=""/>'
|
||||
'<a:cs typeface=""/>'
|
||||
'<a:font script="Jpan" typeface="MS Pゴシック"/>'
|
||||
'<a:font script="Hang" typeface="맑은 고딕"/>'
|
||||
'<a:font script="Hans" typeface="宋体"/>'
|
||||
'<a:font script="Hant" typeface="新細明體"/>'
|
||||
'<a:font script="Arab" typeface="Arial"/>'
|
||||
'<a:font script="Hebr" typeface="Arial"/>'
|
||||
'<a:font script="Thai" typeface="Tahoma"/>'
|
||||
'<a:font script="Ethi" typeface="Nyala"/>'
|
||||
'<a:font script="Beng" typeface="Vrinda"/>'
|
||||
'<a:font script="Gujr" typeface="Shruti"/>'
|
||||
'<a:font script="Khmr" typeface="DaunPenh"/>'
|
||||
'<a:font script="Knda" typeface="Tunga"/>'
|
||||
'<a:font script="Guru" typeface="Raavi"/>'
|
||||
'<a:font script="Cans" typeface="Euphemia"/>'
|
||||
'<a:font script="Cher" typeface="Plantagenet Cherokee"/>'
|
||||
'<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>'
|
||||
'<a:font script="Tibt" typeface="Microsoft Himalaya"/>'
|
||||
'<a:font script="Thaa" typeface="MV Boli"/>'
|
||||
'<a:font script="Deva" typeface="Mangal"/>'
|
||||
'<a:font script="Telu" typeface="Gautami"/>'
|
||||
'<a:font script="Taml" typeface="Latha"/>'
|
||||
'<a:font script="Syrc" typeface="Estrangelo Edessa"/>'
|
||||
'<a:font script="Orya" typeface="Kalinga"/>'
|
||||
'<a:font script="Mlym" typeface="Kartika"/>'
|
||||
'<a:font script="Laoo" typeface="DokChampa"/>'
|
||||
'<a:font script="Sinh" typeface="Iskoola Pota"/>'
|
||||
'<a:font script="Mong" typeface="Mongolian Baiti"/>'
|
||||
'<a:font script="Viet" typeface="Arial"/>'
|
||||
'<a:font script="Uigh" typeface="Microsoft Uighur"/>'
|
||||
'</a:minorFont>'
|
||||
'</a:fontScheme>'
|
||||
|
||||
'<a:fmtScheme name="Office">'
|
||||
'<a:fillStyleLst>'
|
||||
'<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>'
|
||||
'<a:gradFill rotWithShape="1"><a:gsLst>'
|
||||
'<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="50000"/>'
|
||||
'<a:satMod val="300000"/></a:schemeClr></a:gs>'
|
||||
'<a:gs pos="35000"><a:schemeClr val="phClr"><a:tint val="37000"/>'
|
||||
'<a:satMod val="300000"/></a:schemeClr></a:gs>'
|
||||
'<a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="15000"/>'
|
||||
'<a:satMod val="350000"/></a:schemeClr></a:gs></a:gsLst>'
|
||||
'<a:lin ang="16200000" scaled="1"/></a:gradFill>'
|
||||
'<a:gradFill rotWithShape="1"><a:gsLst>'
|
||||
'<a:gs pos="0"><a:schemeClr val="phClr"><a:shade val="51000"/>'
|
||||
'<a:satMod val="130000"/></a:schemeClr></a:gs>'
|
||||
'<a:gs pos="80000"><a:schemeClr val="phClr"><a:shade val="93000"/>'
|
||||
'<a:satMod val="130000"/></a:schemeClr></a:gs>'
|
||||
'<a:gs pos="100000"><a:schemeClr val="phClr">'
|
||||
'<a:shade val="94000"/>'
|
||||
'<a:satMod val="135000"/></a:schemeClr></a:gs></a:gsLst>'
|
||||
'<a:lin ang="16200000" scaled="0"/></a:gradFill></a:fillStyleLst>'
|
||||
'<a:lnStyleLst>'
|
||||
'<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr">'
|
||||
'<a:solidFill><a:schemeClr val="phClr"><a:shade val="95000"/>'
|
||||
'<a:satMod val="105000"/></a:schemeClr></a:solidFill>'
|
||||
'<a:prstDash val="solid"/></a:ln>'
|
||||
'<a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill>'
|
||||
'<a:schemeClr val="phClr"/></a:solidFill>'
|
||||
'<a:prstDash val="solid"/></a:ln>'
|
||||
'<a:ln w="38100" cap="flat" cmpd="sng" algn="ctr"><a:solidFill>'
|
||||
'<a:schemeClr val="phClr"/></a:solidFill>'
|
||||
'<a:prstDash val="solid"/></a:ln></a:lnStyleLst>'
|
||||
'<a:effectStyleLst><a:effectStyle><a:effectLst>'
|
||||
'<a:outerShdw blurRad="40000" dist="20000" dir="5400000" '
|
||||
'rotWithShape="0"><a:srgbClr val="000000">'
|
||||
'<a:alpha val="38000"/></a:srgbClr></a:outerShdw></a:effectLst>'
|
||||
'</a:effectStyle><a:effectStyle><a:effectLst>'
|
||||
'<a:outerShdw blurRad="40000" dist="23000" dir="5400000" '
|
||||
'rotWithShape="0"><a:srgbClr val="000000">'
|
||||
'<a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst>'
|
||||
'</a:effectStyle><a:effectStyle><a:effectLst>'
|
||||
'<a:outerShdw blurRad="40000" dist="23000" dir="5400000" '
|
||||
'rotWithShape="0"><a:srgbClr val="000000">'
|
||||
'<a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst>'
|
||||
'<a:scene3d><a:camera prst="orthographicFront">'
|
||||
'<a:rot lat="0" lon="0" rev="0"/></a:camera>'
|
||||
'<a:lightRig rig="threePt" dir="t">'
|
||||
'<a:rot lat="0" lon="0" rev="1200000"/></a:lightRig>'
|
||||
'</a:scene3d><a:sp3d><a:bevelT w="63500" h="25400"/>'
|
||||
'</a:sp3d></a:effectStyle></a:effectStyleLst>'
|
||||
'<a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/>'
|
||||
'</a:solidFill><a:gradFill rotWithShape="1"><a:gsLst>'
|
||||
'<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="40000"/>'
|
||||
'<a:satMod val="350000"/></a:schemeClr></a:gs>'
|
||||
'<a:gs pos="40000"><a:schemeClr val="phClr"><a:tint val="45000"/>'
|
||||
'<a:shade val="99000"/><a:satMod val="350000"/>'
|
||||
'</a:schemeClr></a:gs>'
|
||||
'<a:gs pos="100000"><a:schemeClr val="phClr">'
|
||||
'<a:shade val="20000"/><a:satMod val="255000"/>'
|
||||
'</a:schemeClr></a:gs></a:gsLst>'
|
||||
'<a:path path="circle">'
|
||||
'<a:fillToRect l="50000" t="-80000" r="50000" b="180000"/>'
|
||||
'</a:path>'
|
||||
'</a:gradFill><a:gradFill rotWithShape="1"><a:gsLst>'
|
||||
'<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="80000"/>'
|
||||
'<a:satMod val="300000"/></a:schemeClr></a:gs>'
|
||||
'<a:gs pos="100000"><a:schemeClr val="phClr">'
|
||||
'<a:shade val="30000"/><a:satMod val="200000"/>'
|
||||
'</a:schemeClr></a:gs></a:gsLst>'
|
||||
'<a:path path="circle">'
|
||||
'<a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path>'
|
||||
'</a:gradFill></a:bgFillStyleLst></a:fmtScheme>'
|
||||
'</a:themeElements>'
|
||||
'<a:objectDefaults/><a:extraClrSchemeLst/>'
|
||||
'</a:theme>')
|
||||
return get_document_content(xml_node)
|
||||
@@ -1,204 +0,0 @@
|
||||
# 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)
|
||||
@@ -1,209 +0,0 @@
|
||||
# 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 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)
|
||||
@@ -956,6 +956,11 @@ class TablibTestCase(unittest.TestCase):
|
||||
"""Test XLSX export with formatter configuration."""
|
||||
self.founders.export('xlsx', freeze_panes=False)
|
||||
|
||||
def test_databook_formatter_with_new_lines(self):
|
||||
"""Test XLSX export with new line in content."""
|
||||
self.founders.append(('First\nSecond', 'Name', 42))
|
||||
self.founders.export('xlsx')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user