Version 0.1

Added sidebar, Dashboard, Line Graph
This commit is contained in:
Ayxan
2022-05-23 04:12:37 +04:00
parent d660f2a4ca
commit 9e90b4a150
204 changed files with 29218 additions and 109 deletions

View File

@ -0,0 +1,18 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.compat.numbers import NUMPY
from openpyxl.xml import DEFUSEDXML, LXML
from openpyxl.workbook import Workbook
from openpyxl.reader.excel import load_workbook as open
from openpyxl.reader.excel import load_workbook
import openpyxl._constants as constants
# Expose constants especially the version number
__author__ = constants.__author__
__author_email__ = constants.__author_email__
__license__ = constants.__license__
__maintainer_email__ = constants.__maintainer_email__
__url__ = constants.__url__
__version__ = constants.__version__

View File

@ -0,0 +1,13 @@
# Copyright (c) 2010-2022 openpyxl
"""
Package metadata
"""
__author__ = "See AUTHORS"
__author_email__ = "charlie.clark@clark-consulting.eu"
__license__ = "MIT"
__maintainer_email__ = "openpyxl-users@googlegroups.com"
__url__ = "https://openpyxl.readthedocs.io"
__version__ = "3.0.10"
__python__ = "3.6"

View File

@ -0,0 +1,4 @@
# Copyright (c) 2010-2022 openpyxl
from .cell import Cell, WriteOnlyCell, MergedCell
from .read_only import ReadOnlyCell

View File

@ -0,0 +1,108 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.compat import safe_string
from openpyxl.xml.functions import Element, SubElement, whitespace, XML_NS, REL_NS
from openpyxl import LXML
from openpyxl.utils.datetime import to_excel, to_ISO8601
from datetime import timedelta
def _set_attributes(cell, styled=None):
"""
Set coordinate and datatype
"""
coordinate = cell.coordinate
attrs = {'r': coordinate}
if styled:
attrs['s'] = f"{cell.style_id}"
if cell.data_type == "s":
attrs['t'] = "inlineStr"
elif cell.data_type != 'f':
attrs['t'] = cell.data_type
value = cell._value
if cell.data_type == "d":
if hasattr(value, "tzinfo") and value.tzinfo is not None:
raise TypeError("Excel does not support timezones in datetimes. "
"The tzinfo in the datetime/time object must be set to None.")
if cell.parent.parent.iso_dates and not isinstance(value, timedelta):
value = to_ISO8601(value)
else:
attrs['t'] = "n"
value = to_excel(value, cell.parent.parent.epoch)
if cell.hyperlink:
cell.parent._hyperlinks.append(cell.hyperlink)
return value, attrs
def etree_write_cell(xf, worksheet, cell, styled=None):
value, attributes = _set_attributes(cell, styled)
el = Element("c", attributes)
if value is None or value == "":
xf.write(el)
return
if cell.data_type == 'f':
shared_formula = worksheet.formula_attributes.get(cell.coordinate, {})
formula = SubElement(el, 'f', shared_formula)
if value is not None:
formula.text = value[1:]
value = None
if cell.data_type == 's':
inline_string = SubElement(el, 'is')
text = SubElement(inline_string, 't')
text.text = value
whitespace(text)
else:
cell_content = SubElement(el, 'v')
if value is not None:
cell_content.text = safe_string(value)
xf.write(el)
def lxml_write_cell(xf, worksheet, cell, styled=False):
value, attributes = _set_attributes(cell, styled)
if value == '' or value is None:
with xf.element("c", attributes):
return
with xf.element('c', attributes):
if cell.data_type == 'f':
shared_formula = worksheet.formula_attributes.get(cell.coordinate, {})
with xf.element('f', shared_formula):
if value is not None:
xf.write(value[1:])
value = None
if cell.data_type == 's':
with xf.element("is"):
attrs = {}
if value != value.strip():
attrs["{%s}space" % XML_NS] = "preserve"
el = Element("t", attrs) # lxml can't handle xml-ns
el.text = value
xf.write(el)
#with xf.element("t", attrs):
#xf.write(value)
else:
with xf.element("v"):
if value is not None:
xf.write(safe_string(value))
if LXML:
write_cell = lxml_write_cell
else:
write_cell = etree_write_cell

View File

@ -0,0 +1,329 @@
# Copyright (c) 2010-2022 openpyxl
"""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
from copy import copy
import datetime
import re
from openpyxl.compat import (
NUMERIC_TYPES,
deprecated,
)
from openpyxl.utils.exceptions import IllegalCharacterError
from openpyxl.utils import get_column_letter
from openpyxl.styles import numbers, is_date_format
from openpyxl.styles.styleable import StyleableObject
from openpyxl.worksheet.hyperlink import Hyperlink
# constants
TIME_TYPES = (datetime.datetime, datetime.date, datetime.time, datetime.timedelta)
TIME_FORMATS = {
datetime.datetime:numbers.FORMAT_DATE_DATETIME,
datetime.date:numbers.FORMAT_DATE_YYYYMMDD2,
datetime.time:numbers.FORMAT_DATE_TIME6,
datetime.timedelta:numbers.FORMAT_DATE_TIMEDELTA,
}
STRING_TYPES = (str, bytes)
KNOWN_TYPES = NUMERIC_TYPES + TIME_TYPES + STRING_TYPES + (bool, type(None))
ILLEGAL_CHARACTERS_RE = re.compile(r'[\000-\010]|[\013-\014]|[\016-\037]')
ERROR_CODES = ('#NULL!', '#DIV/0!', '#VALUE!', '#REF!', '#NAME?', '#NUM!',
'#N/A')
TYPE_STRING = 's'
TYPE_FORMULA = 'f'
TYPE_NUMERIC = 'n'
TYPE_BOOL = 'b'
TYPE_NULL = 'n'
TYPE_INLINE = 'inlineStr'
TYPE_ERROR = 'e'
TYPE_FORMULA_CACHE_STRING = 'str'
VALID_TYPES = (TYPE_STRING, TYPE_FORMULA, TYPE_NUMERIC, TYPE_BOOL,
TYPE_NULL, TYPE_INLINE, TYPE_ERROR, TYPE_FORMULA_CACHE_STRING)
_TYPES = {int:'n', float:'n', str:'s', bool:'b'}
def get_type(t, value):
if isinstance(value, NUMERIC_TYPES):
dt = 'n'
elif isinstance(value, STRING_TYPES):
dt = 's'
elif isinstance(value, TIME_TYPES):
dt = 'd'
else:
return
_TYPES[t] = dt
return dt
def get_time_format(t):
value = TIME_FORMATS.get(t)
if value:
return value
for base in t.mro()[1:]:
value = TIME_FORMATS.get(base)
if value:
TIME_FORMATS[t] = value
return value
raise ValueError("Could not get time format for {0!r}".format(value))
class Cell(StyleableObject):
"""Describes cell associated properties.
Properties of interest include style, type, value, and address.
"""
__slots__ = (
'row',
'column',
'_value',
'data_type',
'parent',
'_hyperlink',
'_comment',
)
def __init__(self, worksheet, row=None, column=None, value=None, style_array=None):
super(Cell, self).__init__(worksheet, style_array)
self.row = row
"""Row number of this cell (1-based)"""
self.column = column
"""Column number of this cell (1-based)"""
# _value is the stored value, while value is the displayed value
self._value = None
self._hyperlink = None
self.data_type = 'n'
if value is not None:
self.value = value
self._comment = None
@property
def coordinate(self):
"""This cell's coordinate (ex. 'A5')"""
col = get_column_letter(self.column)
return f"{col}{self.row}"
@property
def col_idx(self):
"""The numerical index of the column"""
return self.column
@property
def column_letter(self):
return get_column_letter(self.column)
@property
def encoding(self):
return self.parent.encoding
@property
def base_date(self):
return self.parent.parent.epoch
def __repr__(self):
return "<Cell {0!r}.{1}>".format(self.parent.title, self.coordinate)
def check_string(self, value):
"""Check string coding, length, and line break character"""
if value is None:
return
# convert to str string
if not isinstance(value, str):
value = str(value, self.encoding)
value = str(value)
# string must never be longer than 32,767 characters
# truncate if necessary
value = value[:32767]
if next(ILLEGAL_CHARACTERS_RE.finditer(value), None):
raise IllegalCharacterError
return value
def check_error(self, value):
"""Tries to convert Error" else N/A"""
try:
return str(value)
except UnicodeDecodeError:
return u'#N/A'
def _bind_value(self, value):
"""Given a value, infer the correct data type"""
self.data_type = "n"
t = type(value)
try:
dt = _TYPES[t]
except KeyError:
dt = get_type(t, value)
if dt is None and value is not None:
raise ValueError("Cannot convert {0!r} to Excel".format(value))
if dt:
self.data_type = dt
if dt == 'd':
if not is_date_format(self.number_format):
self.number_format = get_time_format(t)
elif dt == "s":
value = self.check_string(value)
if len(value) > 1 and value.startswith("="):
self.data_type = 'f'
elif value in ERROR_CODES:
self.data_type = 'e'
self._value = value
@property
def value(self):
"""Get or set the value held in the cell.
:type: depends on the value (string, float, int or
:class:`datetime.datetime`)
"""
return self._value
@value.setter
def value(self, value):
"""Set the value and infer type and display options."""
self._bind_value(value)
@property
def internal_value(self):
"""Always returns the value for excel."""
return self._value
@property
def hyperlink(self):
"""Return the hyperlink target or an empty string"""
return self._hyperlink
@hyperlink.setter
def hyperlink(self, val):
"""Set value and display for hyperlinks in a 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.
Hyperlink is removed if set to ``None``."""
if val is None:
self._hyperlink = None
else:
if not isinstance(val, Hyperlink):
val = Hyperlink(ref="", target=val)
val.ref = self.coordinate
self._hyperlink = val
if self._value is None:
self.value = val.target or val.location
@property
def is_date(self):
"""True if the value is formatted as a date
:type: bool
"""
return self.data_type == 'd' or (
self.data_type == 'n' and is_date_format(self.number_format)
)
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 = self.col_idx + column
offset_row = self.row + row
return self.parent.cell(column=offset_column, row=offset_row)
@property
def comment(self):
""" Returns the comment associated with this cell
:type: :class:`openpyxl.comments.Comment`
"""
return self._comment
@comment.setter
def comment(self, value):
"""
Assign a comment to a cell
"""
if value is not None:
if value.parent:
value = copy(value)
value.bind(self)
elif value is None and self._comment:
self._comment.unbind()
self._comment = value
class MergedCell(StyleableObject):
"""
Describes the properties of a cell in a merged cell and helps to
display the borders of the merged cell.
The value of a MergedCell is always None.
"""
__slots__ = ('row', 'column')
_value = None
data_type = "n"
comment = None
hyperlink = None
def __init__(self, worksheet, row=None, column=None):
super(MergedCell, self).__init__(worksheet)
self.row = row
self.column = column
def __repr__(self):
return "<MergedCell {0!r}.{1}>".format(self.parent.title, self.coordinate)
coordinate = Cell.coordinate
_comment = comment
value = _value
def WriteOnlyCell(ws=None, value=None):
return Cell(worksheet=ws, column=1, row=1, value=value)

View File

@ -0,0 +1,136 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.cell import Cell
from openpyxl.utils import get_column_letter
from openpyxl.utils.datetime import from_excel
from openpyxl.styles import is_date_format
from openpyxl.styles.numbers import BUILTIN_FORMATS, BUILTIN_FORMATS_MAX_SIZE
class ReadOnlyCell(object):
__slots__ = ('parent', 'row', 'column', '_value', 'data_type', '_style_id')
def __init__(self, sheet, row, column, value, data_type='n', style_id=0):
self.parent = sheet
self._value = None
self.row = row
self.column = column
self.data_type = data_type
self.value = value
self._style_id = style_id
def __eq__(self, other):
for a in self.__slots__:
if getattr(self, a) != getattr(other, a):
return
return True
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return "<ReadOnlyCell {0!r}.{1}>".format(self.parent.title, self.coordinate)
@property
def coordinate(self):
column = get_column_letter(self.column)
return "{1}{0}".format(self.row, column)
@property
def coordinate(self):
return Cell.coordinate.__get__(self)
@property
def column_letter(self):
return Cell.column_letter.__get__(self)
@property
def style_array(self):
return self.parent.parent._cell_styles[self._style_id]
@property
def has_style(self):
return self._style_id != 0
@property
def number_format(self):
_id = self.style_array.numFmtId
if _id < BUILTIN_FORMATS_MAX_SIZE:
return BUILTIN_FORMATS.get(_id, "General")
else:
return self.parent.parent._number_formats[
_id - BUILTIN_FORMATS_MAX_SIZE]
@property
def font(self):
_id = self.style_array.fontId
return self.parent.parent._fonts[_id]
@property
def fill(self):
_id = self.style_array.fillId
return self.parent.parent._fills[_id]
@property
def border(self):
_id = self.style_array.borderId
return self.parent.parent._borders[_id]
@property
def alignment(self):
_id = self.style_array.alignmentId
return self.parent.parent._alignments[_id]
@property
def protection(self):
_id = self.style_array.protectionId
return self.parent.parent._protections[_id]
@property
def is_date(self):
return Cell.is_date.__get__(self)
@property
def internal_value(self):
return self._value
@property
def value(self):
return self._value
@value.setter
def value(self, value):
if self._value is not None:
raise AttributeError("Cell is read only")
self._value = value
class EmptyCell(object):
__slots__ = ()
value = None
is_date = False
font = None
border = None
fill = None
number_format = None
alignment = None
data_type = 'n'
def __repr__(self):
return "<EmptyCell>"
EMPTY_CELL = EmptyCell()

View File

@ -0,0 +1,184 @@
# Copyright (c) 2010-2022 openpyxl
"""
Richtext definition
"""
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Alias,
Typed,
Integer,
Set,
NoneSet,
Bool,
String,
Sequence,
)
from openpyxl.descriptors.nested import (
NestedBool,
NestedInteger,
NestedString,
NestedText,
)
from openpyxl.styles.fonts import Font
class PhoneticProperties(Serialisable):
tagname = "phoneticPr"
fontId = Integer()
type = NoneSet(values=(['halfwidthKatakana', 'fullwidthKatakana',
'Hiragana', 'noConversion']))
alignment = NoneSet(values=(['noControl', 'left', 'center', 'distributed']))
def __init__(self,
fontId=None,
type=None,
alignment=None,
):
self.fontId = fontId
self.type = type
self.alignment = alignment
class PhoneticText(Serialisable):
tagname = "rPh"
sb = Integer()
eb = Integer()
t = NestedText(expected_type=str)
text = Alias('t')
def __init__(self,
sb=None,
eb=None,
t=None,
):
self.sb = sb
self.eb = eb
self.t = t
class InlineFont(Font):
"""
Font for inline text because, yes what you need are different objects with the same elements but different constraints.
"""
tagname = "RPrElt"
rFont = NestedString(allow_none=True)
charset = Font.charset
family = Font.family
b =Font.b
i = Font.i
strike = Font.strike
outline = Font.outline
shadow = Font.shadow
condense = Font.condense
extend = Font.extend
color = Font.color
sz = Font.sz
u = Font.u
vertAlign = Font.vertAlign
scheme = Font.scheme
__elements__ = ('rFont', 'charset', 'family', 'b', 'i', 'strike',
'outline', 'shadow', 'condense', 'extend', 'color', 'sz', 'u',
'vertAlign', 'scheme')
def __init__(self,
rFont=None,
charset=None,
family=None,
b=None,
i=None,
strike=None,
outline=None,
shadow=None,
condense=None,
extend=None,
color=None,
sz=None,
u=None,
vertAlign=None,
scheme=None,
):
self.rFont = rFont
self.charset = charset
self.family = family
self.b = b
self.i = i
self.strike = strike
self.outline = outline
self.shadow = shadow
self.condense = condense
self.extend = extend
self.color = color
self.sz = sz
self.u = u
self.vertAlign = vertAlign
self.scheme = scheme
class RichText(Serialisable):
tagname = "RElt"
rPr = Typed(expected_type=InlineFont, allow_none=True)
font = Alias("rPr")
t = NestedText(expected_type=str, allow_none=True)
text = Alias("t")
__elements__ = ('rPr', 't')
def __init__(self,
rPr=None,
t=None,
):
self.rPr = rPr
self.t = t
class Text(Serialisable):
tagname = "text"
t = NestedText(allow_none=True, expected_type=str)
plain = Alias("t")
r = Sequence(expected_type=RichText, allow_none=True)
formatted = Alias("r")
rPh = Sequence(expected_type=PhoneticText, allow_none=True)
phonetic = Alias("rPh")
phoneticPr = Typed(expected_type=PhoneticProperties, allow_none=True)
PhoneticProperties = Alias("phoneticPr")
__elements__ = ('t', 'r', 'rPh', 'phoneticPr')
def __init__(self,
t=None,
r=(),
rPh=(),
phoneticPr=None,
):
self.t = t
self.r = r
self.rPh = rPh
self.phoneticPr = phoneticPr
@property
def content(self):
"""
Text stripped of all formatting
"""
snippets = []
if self.plain is not None:
snippets.append(self.plain)
for block in self.formatted:
if block.t is not None:
snippets.append(block.t)
return u"".join(snippets)

View File

@ -0,0 +1,105 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors import Typed, Alias
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors.nested import (
NestedBool,
NestedInteger,
NestedMinMax,
)
from openpyxl.descriptors.excel import ExtensionList
from .marker import PictureOptions
from .shapes import GraphicalProperties
class View3D(Serialisable):
tagname = "view3D"
rotX = NestedMinMax(min=-90, max=90, allow_none=True)
x_rotation = Alias('rotX')
hPercent = NestedMinMax(min=5, max=500, allow_none=True)
height_percent = Alias('hPercent')
rotY = NestedInteger(min=-90, max=90, allow_none=True)
y_rotation = Alias('rotY')
depthPercent = NestedInteger(allow_none=True)
rAngAx = NestedBool(allow_none=True)
right_angle_axes = Alias('rAngAx')
perspective = NestedInteger(allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('rotX', 'hPercent', 'rotY', 'depthPercent', 'rAngAx',
'perspective',)
def __init__(self,
rotX=15,
hPercent=None,
rotY=20,
depthPercent=None,
rAngAx=True,
perspective=None,
extLst=None,
):
self.rotX = rotX
self.hPercent = hPercent
self.rotY = rotY
self.depthPercent = depthPercent
self.rAngAx = rAngAx
self.perspective = perspective
class Surface(Serialisable):
tagname = "surface"
thickness = NestedInteger(allow_none=True)
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias('spPr')
pictureOptions = Typed(expected_type=PictureOptions, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('thickness', 'spPr', 'pictureOptions',)
def __init__(self,
thickness=None,
spPr=None,
pictureOptions=None,
extLst=None,
):
self.thickness = thickness
self.spPr = spPr
self.pictureOptions = pictureOptions
class _3DBase(Serialisable):
"""
Base class for 3D charts
"""
tagname = "ChartBase"
view3D = Typed(expected_type=View3D, allow_none=True)
floor = Typed(expected_type=Surface, allow_none=True)
sideWall = Typed(expected_type=Surface, allow_none=True)
backWall = Typed(expected_type=Surface, allow_none=True)
def __init__(self,
view3D=None,
floor=None,
sideWall=None,
backWall=None,
):
if view3D is None:
view3D = View3D()
self.view3D = view3D
if floor is None:
floor = Surface()
self.floor = floor
if sideWall is None:
sideWall = Surface()
self.sideWall = sideWall
if backWall is None:
backWall = Surface()
self.backWall = backWall
super(_3DBase, self).__init__()

View File

@ -0,0 +1,19 @@
# Copyright (c) 2010-2022 openpyxl
from .area_chart import AreaChart, AreaChart3D
from .bar_chart import BarChart, BarChart3D
from .bubble_chart import BubbleChart
from .line_chart import LineChart, LineChart3D
from .pie_chart import (
PieChart,
PieChart3D,
DoughnutChart,
ProjectedPieChart
)
from .radar_chart import RadarChart
from .scatter_chart import ScatterChart
from .stock_chart import StockChart
from .surface_chart import SurfaceChart, SurfaceChart3D
from .series_factory import SeriesFactory as Series
from .reference import Reference

View File

@ -0,0 +1,196 @@
# Copyright (c) 2010-2022 openpyxl
from collections import OrderedDict
from operator import attrgetter
from openpyxl.descriptors import (
Typed,
Integer,
Alias,
MinMax,
Bool,
Set,
)
from openpyxl.descriptors.sequence import ValueSequence
from openpyxl.descriptors.serialisable import Serialisable
from ._3d import _3DBase
from .data_source import AxDataSource, NumRef
from .layout import Layout
from .legend import Legend
from .reference import Reference
from .series_factory import SeriesFactory
from .series import attribute_mapping
from .shapes import GraphicalProperties
from .title import TitleDescriptor
class AxId(Serialisable):
val = Integer()
def __init__(self, val):
self.val = val
def PlotArea():
from .chartspace import PlotArea
return PlotArea()
class ChartBase(Serialisable):
"""
Base class for all charts
"""
legend = Typed(expected_type=Legend, allow_none=True)
layout = Typed(expected_type=Layout, allow_none=True)
roundedCorners = Bool(allow_none=True)
axId = ValueSequence(expected_type=int)
visible_cells_only = Bool(allow_none=True)
display_blanks = Set(values=['span', 'gap', 'zero'])
_series_type = ""
ser = ()
series = Alias('ser')
title = TitleDescriptor()
anchor = "E15" # default anchor position
width = 15 # in cm, approx 5 rows
height = 7.5 # in cm, approx 14 rows
_id = 1
_path = "/xl/charts/chart{0}.xml"
style = MinMax(allow_none=True, min=1, max=48)
mime_type = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"
graphical_properties = Typed(expected_type=GraphicalProperties, allow_none=True)
__elements__ = ()
def __init__(self, axId=(), **kw):
self._charts = [self]
self.title = None
self.layout = None
self.roundedCorners = None
self.legend = Legend()
self.graphical_properties = None
self.style = None
self.plot_area = PlotArea()
self.axId = axId
self.display_blanks = 'gap'
self.pivotSource = None
self.pivotFormats = ()
self.visible_cells_only = True
self.idx_base = 0
super(ChartBase, self).__init__()
def __hash__(self):
"""
Just need to check for identity
"""
return id(self)
def __iadd__(self, other):
"""
Combine the chart with another one
"""
if not isinstance(other, ChartBase):
raise TypeError("Only other charts can be added")
self._charts.append(other)
return self
def to_tree(self, namespace=None, tagname=None, idx=None):
self.axId = [id for id in self._axes]
if self.ser is not None:
for s in self.ser:
s.__elements__ = attribute_mapping[self._series_type]
return super(ChartBase, self).to_tree(tagname, idx)
def _reindex(self):
"""
Normalise and rebase series: sort by order and then rebase order
"""
# sort data series in order and rebase
ds = sorted(self.series, key=attrgetter("order"))
for idx, s in enumerate(ds):
s.order = idx
self.series = ds
def _write(self):
from .chartspace import ChartSpace, ChartContainer
self.plot_area.layout = self.layout
idx_base = self.idx_base
for chart in self._charts:
if chart not in self.plot_area._charts:
chart.idx_base = idx_base
idx_base += len(chart.series)
self.plot_area._charts = self._charts
container = ChartContainer(plotArea=self.plot_area, legend=self.legend, title=self.title)
if isinstance(chart, _3DBase):
container.view3D = chart.view3D
container.floor = chart.floor
container.sideWall = chart.sideWall
container.backWall = chart.backWall
container.plotVisOnly = self.visible_cells_only
container.dispBlanksAs = self.display_blanks
container.pivotFmts = self.pivotFormats
cs = ChartSpace(chart=container)
cs.style = self.style
cs.roundedCorners = self.roundedCorners
cs.pivotSource = self.pivotSource
return cs.to_tree()
@property
def _axes(self):
x = getattr(self, "x_axis", None)
y = getattr(self, "y_axis", None)
z = getattr(self, "z_axis", None)
return OrderedDict([(axis.axId, axis) for axis in (x, y, z) if axis])
def set_categories(self, labels):
"""
Set the categories / x-axis values
"""
if not isinstance(labels, Reference):
labels = Reference(range_string=labels)
for s in self.ser:
s.cat = AxDataSource(numRef=NumRef(f=labels))
def add_data(self, data, from_rows=False, titles_from_data=False):
"""
Add a range of data in a single pass.
The default is to treat each column as a data series.
"""
if not isinstance(data, Reference):
data = Reference(range_string=data)
if from_rows:
values = data.rows
else:
values = data.cols
for ref in values:
series = SeriesFactory(ref, title_from_data=titles_from_data)
self.series.append(series)
def append(self, value):
"""Append a data series to the chart"""
l = self.series[:]
l.append(value)
self.series = l
@property
def path(self):
return self._path.format(self._id)

View File

@ -0,0 +1,106 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Set,
Bool,
Integer,
Sequence,
Alias,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
NestedMinMax,
NestedSet,
NestedBool,
)
from ._chart import ChartBase
from .descriptors import NestedGapAmount
from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines
from .label import DataLabelList
from .series import Series
class _AreaChartBase(ChartBase):
grouping = NestedSet(values=(['percentStacked', 'standard', 'stacked']))
varyColors = NestedBool(nested=True, allow_none=True)
ser = Sequence(expected_type=Series, allow_none=True)
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
dataLabels = Alias("dLbls")
dropLines = Typed(expected_type=ChartLines, allow_none=True)
_series_type = "area"
__elements__ = ('grouping', 'varyColors', 'ser', 'dLbls', 'dropLines')
def __init__(self,
grouping="standard",
varyColors=None,
ser=(),
dLbls=None,
dropLines=None,
):
self.grouping = grouping
self.varyColors = varyColors
self.ser = ser
self.dLbls = dLbls
self.dropLines = dropLines
super(_AreaChartBase, self).__init__()
class AreaChart(_AreaChartBase):
tagname = "areaChart"
grouping = _AreaChartBase.grouping
varyColors = _AreaChartBase.varyColors
ser = _AreaChartBase.ser
dLbls = _AreaChartBase.dLbls
dropLines = _AreaChartBase.dropLines
# chart properties actually used by containing classes
x_axis = Typed(expected_type=TextAxis)
y_axis = Typed(expected_type=NumericAxis)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = _AreaChartBase.__elements__ + ('axId',)
def __init__(self,
axId=None,
extLst=None,
**kw
):
self.x_axis = TextAxis()
self.y_axis = NumericAxis()
super(AreaChart, self).__init__(**kw)
class AreaChart3D(AreaChart):
tagname = "area3DChart"
grouping = _AreaChartBase.grouping
varyColors = _AreaChartBase.varyColors
ser = _AreaChartBase.ser
dLbls = _AreaChartBase.dLbls
dropLines = _AreaChartBase.dropLines
gapDepth = NestedGapAmount()
x_axis = Typed(expected_type=TextAxis)
y_axis = Typed(expected_type=NumericAxis)
z_axis = Typed(expected_type=SeriesAxis, allow_none=True)
__elements__ = AreaChart.__elements__ + ('gapDepth', )
def __init__(self, gapDepth=None, **kw):
self.gapDepth = gapDepth
super(AreaChart3D, self).__init__(**kw)
self.x_axis = TextAxis()
self.y_axis = NumericAxis()
self.z_axis = SeriesAxis()

View File

@ -0,0 +1,401 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Float,
NoneSet,
Bool,
Integer,
MinMax,
NoneSet,
Set,
String,
Alias,
)
from openpyxl.descriptors.excel import (
ExtensionList,
Percentage,
_explicit_none,
)
from openpyxl.descriptors.nested import (
NestedValue,
NestedSet,
NestedBool,
NestedNoneSet,
NestedFloat,
NestedInteger,
NestedMinMax,
)
from openpyxl.xml.constants import CHART_NS
from .descriptors import NumberFormatDescriptor
from .layout import Layout
from .text import Text, RichText
from .shapes import GraphicalProperties
from .title import Title, TitleDescriptor
class ChartLines(Serialisable):
tagname = "chartLines"
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias('spPr')
def __init__(self, spPr=None):
self.spPr = spPr
class Scaling(Serialisable):
tagname = "scaling"
logBase = NestedFloat(allow_none=True)
orientation = NestedSet(values=(['maxMin', 'minMax']))
max = NestedFloat(allow_none=True)
min = NestedFloat(allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('logBase', 'orientation', 'max', 'min',)
def __init__(self,
logBase=None,
orientation="minMax",
max=None,
min=None,
extLst=None,
):
self.logBase = logBase
self.orientation = orientation
self.max = max
self.min = min
class _BaseAxis(Serialisable):
axId = NestedInteger(expected_type=int)
scaling = Typed(expected_type=Scaling)
delete = NestedBool(allow_none=True)
axPos = NestedSet(values=(['b', 'l', 'r', 't']))
majorGridlines = Typed(expected_type=ChartLines, allow_none=True)
minorGridlines = Typed(expected_type=ChartLines, allow_none=True)
title = TitleDescriptor()
numFmt = NumberFormatDescriptor()
number_format = Alias("numFmt")
majorTickMark = NestedNoneSet(values=(['cross', 'in', 'out']), to_tree=_explicit_none)
minorTickMark = NestedNoneSet(values=(['cross', 'in', 'out']), to_tree=_explicit_none)
tickLblPos = NestedNoneSet(values=(['high', 'low', 'nextTo']))
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias('spPr')
txPr = Typed(expected_type=RichText, allow_none=True)
textProperties = Alias('txPr')
crossAx = NestedInteger(expected_type=int) # references other axis
crosses = NestedNoneSet(values=(['autoZero', 'max', 'min']))
crossesAt = NestedFloat(allow_none=True)
# crosses & crossesAt are mutually exclusive
__elements__ = ('axId', 'scaling', 'delete', 'axPos', 'majorGridlines',
'minorGridlines', 'title', 'numFmt', 'majorTickMark', 'minorTickMark',
'tickLblPos', 'spPr', 'txPr', 'crossAx', 'crosses', 'crossesAt')
def __init__(self,
axId=None,
scaling=None,
delete=None,
axPos='l',
majorGridlines=None,
minorGridlines=None,
title=None,
numFmt=None,
majorTickMark=None,
minorTickMark=None,
tickLblPos=None,
spPr=None,
txPr= None,
crossAx=None,
crosses=None,
crossesAt=None,
):
self.axId = axId
if scaling is None:
scaling = Scaling()
self.scaling = scaling
self.delete = delete
self.axPos = axPos
self.majorGridlines = majorGridlines
self.minorGridlines = minorGridlines
self.title = title
self.numFmt = numFmt
self.majorTickMark = majorTickMark
self.minorTickMark = minorTickMark
self.tickLblPos = tickLblPos
self.spPr = spPr
self.txPr = txPr
self.crossAx = crossAx
self.crosses = crosses
self.crossesAt = crossesAt
class DisplayUnitsLabel(Serialisable):
tagname = "dispUnitsLbl"
layout = Typed(expected_type=Layout, allow_none=True)
tx = Typed(expected_type=Text, allow_none=True)
text = Alias("tx")
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias("spPr")
txPr = Typed(expected_type=RichText, allow_none=True)
textPropertes = Alias("txPr")
__elements__ = ('layout', 'tx', 'spPr', 'txPr')
def __init__(self,
layout=None,
tx=None,
spPr=None,
txPr=None,
):
self.layout = layout
self.tx = tx
self.spPr = spPr
self.txPr = txPr
class DisplayUnitsLabelList(Serialisable):
tagname = "dispUnits"
custUnit = NestedFloat(allow_none=True)
builtInUnit = NestedNoneSet(values=(['hundreds', 'thousands',
'tenThousands', 'hundredThousands', 'millions', 'tenMillions',
'hundredMillions', 'billions', 'trillions']))
dispUnitsLbl = Typed(expected_type=DisplayUnitsLabel, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('custUnit', 'builtInUnit', 'dispUnitsLbl',)
def __init__(self,
custUnit=None,
builtInUnit=None,
dispUnitsLbl=None,
extLst=None,
):
self.custUnit = custUnit
self.builtInUnit = builtInUnit
self.dispUnitsLbl = dispUnitsLbl
class NumericAxis(_BaseAxis):
tagname = "valAx"
axId = _BaseAxis.axId
scaling = _BaseAxis.scaling
delete = _BaseAxis.delete
axPos = _BaseAxis.axPos
majorGridlines = _BaseAxis.majorGridlines
minorGridlines = _BaseAxis.minorGridlines
title = _BaseAxis.title
numFmt = _BaseAxis.numFmt
majorTickMark = _BaseAxis.majorTickMark
minorTickMark = _BaseAxis.minorTickMark
tickLblPos = _BaseAxis.tickLblPos
spPr = _BaseAxis.spPr
txPr = _BaseAxis.txPr
crossAx = _BaseAxis.crossAx
crosses = _BaseAxis.crosses
crossesAt = _BaseAxis.crossesAt
crossBetween = NestedNoneSet(values=(['between', 'midCat']))
majorUnit = NestedFloat(allow_none=True)
minorUnit = NestedFloat(allow_none=True)
dispUnits = Typed(expected_type=DisplayUnitsLabelList, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = _BaseAxis.__elements__ + ('crossBetween', 'majorUnit',
'minorUnit', 'dispUnits',)
def __init__(self,
crossBetween=None,
majorUnit=None,
minorUnit=None,
dispUnits=None,
extLst=None,
**kw
):
self.crossBetween = crossBetween
self.majorUnit = majorUnit
self.minorUnit = minorUnit
self.dispUnits = dispUnits
kw.setdefault('majorGridlines', ChartLines())
kw.setdefault('axId', 100)
kw.setdefault('crossAx', 10)
super(NumericAxis, self).__init__(**kw)
@classmethod
def from_tree(cls, node):
"""
Special case value axes with no gridlines
"""
self = super(NumericAxis, cls).from_tree(node)
gridlines = node.find("{%s}majorGridlines" % CHART_NS)
if gridlines is None:
self.majorGridlines = None
return self
class TextAxis(_BaseAxis):
tagname = "catAx"
axId = _BaseAxis.axId
scaling = _BaseAxis.scaling
delete = _BaseAxis.delete
axPos = _BaseAxis.axPos
majorGridlines = _BaseAxis.majorGridlines
minorGridlines = _BaseAxis.minorGridlines
title = _BaseAxis.title
numFmt = _BaseAxis.numFmt
majorTickMark = _BaseAxis.majorTickMark
minorTickMark = _BaseAxis.minorTickMark
tickLblPos = _BaseAxis.tickLblPos
spPr = _BaseAxis.spPr
txPr = _BaseAxis.txPr
crossAx = _BaseAxis.crossAx
crosses = _BaseAxis.crosses
crossesAt = _BaseAxis.crossesAt
auto = NestedBool(allow_none=True)
lblAlgn = NestedNoneSet(values=(['ctr', 'l', 'r']))
lblOffset = NestedMinMax(min=0, max=1000)
tickLblSkip = NestedInteger(allow_none=True)
tickMarkSkip = NestedInteger(allow_none=True)
noMultiLvlLbl = NestedBool(allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = _BaseAxis.__elements__ + ('auto', 'lblAlgn', 'lblOffset',
'tickLblSkip', 'tickMarkSkip', 'noMultiLvlLbl')
def __init__(self,
auto=None,
lblAlgn=None,
lblOffset=100,
tickLblSkip=None,
tickMarkSkip=None,
noMultiLvlLbl=None,
extLst=None,
**kw
):
self.auto = auto
self.lblAlgn = lblAlgn
self.lblOffset = lblOffset
self.tickLblSkip = tickLblSkip
self.tickMarkSkip = tickMarkSkip
self.noMultiLvlLbl = noMultiLvlLbl
kw.setdefault('axId', 10)
kw.setdefault('crossAx', 100)
super(TextAxis, self).__init__(**kw)
class DateAxis(TextAxis):
tagname = "dateAx"
axId = _BaseAxis.axId
scaling = _BaseAxis.scaling
delete = _BaseAxis.delete
axPos = _BaseAxis.axPos
majorGridlines = _BaseAxis.majorGridlines
minorGridlines = _BaseAxis.minorGridlines
title = _BaseAxis.title
numFmt = _BaseAxis.numFmt
majorTickMark = _BaseAxis.majorTickMark
minorTickMark = _BaseAxis.minorTickMark
tickLblPos = _BaseAxis.tickLblPos
spPr = _BaseAxis.spPr
txPr = _BaseAxis.txPr
crossAx = _BaseAxis.crossAx
crosses = _BaseAxis.crosses
crossesAt = _BaseAxis.crossesAt
auto = NestedBool(allow_none=True)
lblOffset = NestedInteger(allow_none=True)
baseTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
majorUnit = NestedFloat(allow_none=True)
majorTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
minorUnit = NestedFloat(allow_none=True)
minorTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = _BaseAxis.__elements__ + ('auto', 'lblOffset',
'baseTimeUnit', 'majorUnit', 'majorTimeUnit', 'minorUnit',
'minorTimeUnit')
def __init__(self,
auto=None,
lblOffset=None,
baseTimeUnit=None,
majorUnit=None,
majorTimeUnit=None,
minorUnit=None,
minorTimeUnit=None,
extLst=None,
**kw
):
self.auto = auto
self.lblOffset = lblOffset
self.baseTimeUnit = baseTimeUnit
self.majorUnit = majorUnit
self.majorTimeUnit = majorTimeUnit
self.minorUnit = minorUnit
self.minorTimeUnit = minorTimeUnit
kw.setdefault('axId', 500)
kw.setdefault('lblOffset', lblOffset)
super(DateAxis, self).__init__(**kw)
class SeriesAxis(_BaseAxis):
tagname = "serAx"
axId = _BaseAxis.axId
scaling = _BaseAxis.scaling
delete = _BaseAxis.delete
axPos = _BaseAxis.axPos
majorGridlines = _BaseAxis.majorGridlines
minorGridlines = _BaseAxis.minorGridlines
title = _BaseAxis.title
numFmt = _BaseAxis.numFmt
majorTickMark = _BaseAxis.majorTickMark
minorTickMark = _BaseAxis.minorTickMark
tickLblPos = _BaseAxis.tickLblPos
spPr = _BaseAxis.spPr
txPr = _BaseAxis.txPr
crossAx = _BaseAxis.crossAx
crosses = _BaseAxis.crosses
crossesAt = _BaseAxis.crossesAt
tickLblSkip = NestedInteger(allow_none=True)
tickMarkSkip = NestedInteger(allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = _BaseAxis.__elements__ + ('tickLblSkip', 'tickMarkSkip')
def __init__(self,
tickLblSkip=None,
tickMarkSkip=None,
extLst=None,
**kw
):
self.tickLblSkip = tickLblSkip
self.tickMarkSkip = tickMarkSkip
kw.setdefault('axId', 1000)
kw.setdefault('crossAx', 10)
super(SeriesAxis, self).__init__(**kw)

View File

@ -0,0 +1,144 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Bool,
Integer,
Sequence,
Alias,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
NestedNoneSet,
NestedSet,
NestedBool,
NestedInteger,
NestedMinMax,
)
from .descriptors import (
NestedGapAmount,
NestedOverlap,
)
from ._chart import ChartBase
from ._3d import _3DBase
from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines
from .shapes import GraphicalProperties
from .series import Series
from .legend import Legend
from .label import DataLabelList
class _BarChartBase(ChartBase):
barDir = NestedSet(values=(['bar', 'col']))
type = Alias("barDir")
grouping = NestedSet(values=(['percentStacked', 'clustered', 'standard',
'stacked']))
varyColors = NestedBool(nested=True, allow_none=True)
ser = Sequence(expected_type=Series, allow_none=True)
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
dataLabels = Alias("dLbls")
__elements__ = ('barDir', 'grouping', 'varyColors', 'ser', 'dLbls')
_series_type = "bar"
def __init__(self,
barDir="col",
grouping="clustered",
varyColors=None,
ser=(),
dLbls=None,
**kw
):
self.barDir = barDir
self.grouping = grouping
self.varyColors = varyColors
self.ser = ser
self.dLbls = dLbls
super(_BarChartBase, self).__init__(**kw)
class BarChart(_BarChartBase):
tagname = "barChart"
barDir = _BarChartBase.barDir
grouping = _BarChartBase.grouping
varyColors = _BarChartBase.varyColors
ser = _BarChartBase.ser
dLbls = _BarChartBase.dLbls
gapWidth = NestedGapAmount()
overlap = NestedOverlap()
serLines = Typed(expected_type=ChartLines, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
# chart properties actually used by containing classes
x_axis = Typed(expected_type=TextAxis)
y_axis = Typed(expected_type=NumericAxis)
__elements__ = _BarChartBase.__elements__ + ('gapWidth', 'overlap', 'serLines', 'axId')
def __init__(self,
gapWidth=150,
overlap=None,
serLines=None,
extLst=None,
**kw
):
self.gapWidth = gapWidth
self.overlap = overlap
self.serLines = serLines
self.x_axis = TextAxis()
self.y_axis = NumericAxis()
self.legend = Legend()
super(BarChart, self).__init__(**kw)
class BarChart3D(_BarChartBase, _3DBase):
tagname = "bar3DChart"
barDir = _BarChartBase.barDir
grouping = _BarChartBase.grouping
varyColors = _BarChartBase.varyColors
ser = _BarChartBase.ser
dLbls = _BarChartBase.dLbls
view3D = _3DBase.view3D
floor = _3DBase.floor
sideWall = _3DBase.sideWall
backWall = _3DBase.backWall
gapWidth = NestedGapAmount()
gapDepth = NestedGapAmount()
shape = NestedNoneSet(values=(['cone', 'coneToMax', 'box', 'cylinder', 'pyramid', 'pyramidToMax']))
serLines = Typed(expected_type=ChartLines, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
x_axis = Typed(expected_type=TextAxis)
y_axis = Typed(expected_type=NumericAxis)
z_axis = Typed(expected_type=SeriesAxis, allow_none=True)
__elements__ = _BarChartBase.__elements__ + ('gapWidth', 'gapDepth', 'shape', 'serLines', 'axId')
def __init__(self,
gapWidth=150,
gapDepth=150,
shape=None,
serLines=None,
extLst=None,
**kw
):
self.gapWidth = gapWidth
self.gapDepth = gapDepth
self.shape = shape
self.serLines = serLines
self.x_axis = TextAxis()
self.y_axis = NumericAxis()
self.z_axis = SeriesAxis()
super(BarChart3D, self).__init__(**kw)

View File

@ -0,0 +1,67 @@
#Autogenerated schema
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Set,
MinMax,
Bool,
Integer,
Alias,
Sequence,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
NestedNoneSet,
NestedMinMax,
NestedBool,
)
from ._chart import ChartBase
from .axis import TextAxis, NumericAxis
from .series import XYSeries
from .label import DataLabelList
class BubbleChart(ChartBase):
tagname = "bubbleChart"
varyColors = NestedBool(allow_none=True)
ser = Sequence(expected_type=XYSeries, allow_none=True)
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
dataLabels = Alias("dLbls")
bubble3D = NestedBool(allow_none=True)
bubbleScale = NestedMinMax(min=0, max=300, allow_none=True)
showNegBubbles = NestedBool(allow_none=True)
sizeRepresents = NestedNoneSet(values=(['area', 'w']))
extLst = Typed(expected_type=ExtensionList, allow_none=True)
x_axis = Typed(expected_type=NumericAxis)
y_axis = Typed(expected_type=NumericAxis)
_series_type = "bubble"
__elements__ = ('varyColors', 'ser', 'dLbls', 'bubble3D', 'bubbleScale',
'showNegBubbles', 'sizeRepresents', 'axId')
def __init__(self,
varyColors=None,
ser=(),
dLbls=None,
bubble3D=None,
bubbleScale=None,
showNegBubbles=None,
sizeRepresents=None,
extLst=None,
**kw
):
self.varyColors = varyColors
self.ser = ser
self.dLbls = dLbls
self.bubble3D = bubble3D
self.bubbleScale = bubbleScale
self.showNegBubbles = showNegBubbles
self.sizeRepresents = sizeRepresents
self.x_axis = NumericAxis(axId=10, crossAx=20)
self.y_axis = NumericAxis(axId=20, crossAx=10)
super(BubbleChart, self).__init__(**kw)

View File

@ -0,0 +1,195 @@
# Copyright (c) 2010-2022 openpyxl
"""
Enclosing chart object. The various chart types are actually child objects.
Will probably need to call this indirectly
"""
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
String,
Alias,
)
from openpyxl.descriptors.excel import (
ExtensionList,
Relation
)
from openpyxl.descriptors.nested import (
NestedBool,
NestedNoneSet,
NestedString,
NestedMinMax,
)
from openpyxl.descriptors.sequence import NestedSequence
from openpyxl.xml.constants import CHART_NS
from openpyxl.drawing.colors import ColorMapping
from .text import RichText
from .shapes import GraphicalProperties
from .legend import Legend
from ._3d import _3DBase
from .plotarea import PlotArea
from .title import Title
from .pivot import (
PivotFormat,
PivotSource,
)
from .print_settings import PrintSettings
class ChartContainer(Serialisable):
tagname = "chart"
title = Typed(expected_type=Title, allow_none=True)
autoTitleDeleted = NestedBool(allow_none=True)
pivotFmts = NestedSequence(expected_type=PivotFormat)
view3D = _3DBase.view3D
floor = _3DBase.floor
sideWall = _3DBase.sideWall
backWall = _3DBase.backWall
plotArea = Typed(expected_type=PlotArea, )
legend = Typed(expected_type=Legend, allow_none=True)
plotVisOnly = NestedBool()
dispBlanksAs = NestedNoneSet(values=(['span', 'gap', 'zero']))
showDLblsOverMax = NestedBool(allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('title', 'autoTitleDeleted', 'pivotFmts', 'view3D',
'floor', 'sideWall', 'backWall', 'plotArea', 'legend', 'plotVisOnly',
'dispBlanksAs', 'showDLblsOverMax')
def __init__(self,
title=None,
autoTitleDeleted=None,
pivotFmts=(),
view3D=None,
floor=None,
sideWall=None,
backWall=None,
plotArea=None,
legend=None,
plotVisOnly=True,
dispBlanksAs="gap",
showDLblsOverMax=None,
extLst=None,
):
self.title = title
self.autoTitleDeleted = autoTitleDeleted
self.pivotFmts = pivotFmts
self.view3D = view3D
self.floor = floor
self.sideWall = sideWall
self.backWall = backWall
if plotArea is None:
plotArea = PlotArea()
self.plotArea = plotArea
self.legend = legend
self.plotVisOnly = plotVisOnly
self.dispBlanksAs = dispBlanksAs
self.showDLblsOverMax = showDLblsOverMax
class Protection(Serialisable):
tagname = "protection"
chartObject = NestedBool(allow_none=True)
data = NestedBool(allow_none=True)
formatting = NestedBool(allow_none=True)
selection = NestedBool(allow_none=True)
userInterface = NestedBool(allow_none=True)
__elements__ = ("chartObject", "data", "formatting", "selection", "userInterface")
def __init__(self,
chartObject=None,
data=None,
formatting=None,
selection=None,
userInterface=None,
):
self.chartObject = chartObject
self.data = data
self.formatting = formatting
self.selection = selection
self.userInterface = userInterface
class ExternalData(Serialisable):
tagname = "externalData"
autoUpdate = NestedBool(allow_none=True)
id = String() # Needs namespace
def __init__(self,
autoUpdate=None,
id=None
):
self.autoUpdate = autoUpdate
self.id = id
class ChartSpace(Serialisable):
tagname = "chartSpace"
date1904 = NestedBool(allow_none=True)
lang = NestedString(allow_none=True)
roundedCorners = NestedBool(allow_none=True)
style = NestedMinMax(allow_none=True, min=1, max=48)
clrMapOvr = Typed(expected_type=ColorMapping, allow_none=True)
pivotSource = Typed(expected_type=PivotSource, allow_none=True)
protection = Typed(expected_type=Protection, allow_none=True)
chart = Typed(expected_type=ChartContainer)
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias("spPr")
txPr = Typed(expected_type=RichText, allow_none=True)
textProperties = Alias("txPr")
externalData = Typed(expected_type=ExternalData, allow_none=True)
printSettings = Typed(expected_type=PrintSettings, allow_none=True)
userShapes = Relation()
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('date1904', 'lang', 'roundedCorners', 'style',
'clrMapOvr', 'pivotSource', 'protection', 'chart', 'spPr', 'txPr',
'externalData', 'printSettings', 'userShapes')
def __init__(self,
date1904=None,
lang=None,
roundedCorners=None,
style=None,
clrMapOvr=None,
pivotSource=None,
protection=None,
chart=None,
spPr=None,
txPr=None,
externalData=None,
printSettings=None,
userShapes=None,
extLst=None,
):
self.date1904 = date1904
self.lang = lang
self.roundedCorners = roundedCorners
self.style = style
self.clrMapOvr = clrMapOvr
self.pivotSource = pivotSource
self.protection = protection
self.chart = chart
self.spPr = spPr
self.txPr = txPr
self.externalData = externalData
self.printSettings = printSettings
self.userShapes = userShapes
def to_tree(self, tagname=None, idx=None, namespace=None):
tree = super(ChartSpace, self).to_tree()
tree.set("xmlns", CHART_NS)
return tree

View File

@ -0,0 +1,246 @@
"""
Collection of utility primitives for charts.
"""
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Bool,
Typed,
Alias,
String,
Integer,
Sequence,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
NestedString,
NestedText,
NestedInteger,
)
class NumFmt(Serialisable):
formatCode = String()
sourceLinked = Bool()
def __init__(self,
formatCode=None,
sourceLinked=False
):
self.formatCode = formatCode
self.sourceLinked = sourceLinked
class NumberValueDescriptor(NestedText):
"""
Data should be numerical but isn't always :-/
"""
allow_none = True
def __set__(self, instance, value):
if value == "#N/A":
self.expected_type = str
else:
self.expected_type = float
super(NumberValueDescriptor, self).__set__(instance, value)
class NumVal(Serialisable):
idx = Integer()
formatCode = NestedText(allow_none=True, expected_type=str)
v = NumberValueDescriptor()
def __init__(self,
idx=None,
formatCode=None,
v=None,
):
self.idx = idx
self.formatCode = formatCode
self.v = v
class NumData(Serialisable):
formatCode = NestedText(expected_type=str, allow_none=True)
ptCount = NestedInteger(allow_none=True)
pt = Sequence(expected_type=NumVal)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('formatCode', 'ptCount', 'pt')
def __init__(self,
formatCode=None,
ptCount=None,
pt=(),
extLst=None,
):
self.formatCode = formatCode
self.ptCount = ptCount
self.pt = pt
class NumRef(Serialisable):
f = NestedText(expected_type=str)
ref = Alias('f')
numCache = Typed(expected_type=NumData, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('f', 'numCache')
def __init__(self,
f=None,
numCache=None,
extLst=None,
):
self.f = f
self.numCache = numCache
class StrVal(Serialisable):
tagname = "strVal"
idx = Integer()
v = NestedText(expected_type=str)
def __init__(self,
idx=0,
v=None,
):
self.idx = idx
self.v = v
class StrData(Serialisable):
tagname = "strData"
ptCount = NestedInteger(allow_none=True)
pt = Sequence(expected_type=StrVal)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('ptCount', 'pt')
def __init__(self,
ptCount=None,
pt=(),
extLst=None,
):
self.ptCount = ptCount
self.pt = pt
class StrRef(Serialisable):
tagname = "strRef"
f = NestedText(expected_type=str, allow_none=True)
strCache = Typed(expected_type=StrData, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('f', 'strCache')
def __init__(self,
f=None,
strCache=None,
extLst=None,
):
self.f = f
self.strCache = strCache
class NumDataSource(Serialisable):
numRef = Typed(expected_type=NumRef, allow_none=True)
numLit = Typed(expected_type=NumData, allow_none=True)
def __init__(self,
numRef=None,
numLit=None,
):
self.numRef = numRef
self.numLit = numLit
class Level(Serialisable):
tagname = "lvl"
pt = Sequence(expected_type=StrVal)
__elements__ = ('pt',)
def __init__(self,
pt=(),
):
self.pt = pt
class MultiLevelStrData(Serialisable):
tagname = "multiLvlStrData"
ptCount = Integer(allow_none=True)
lvl = Sequence(expected_type=Level)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('ptCount', 'lvl',)
def __init__(self,
ptCount=None,
lvl=(),
extLst=None,
):
self.ptCount = ptCount
self.lvl = lvl
class MultiLevelStrRef(Serialisable):
tagname = "multiLvlStrRef"
f = NestedText(expected_type=str)
multiLvlStrCache = Typed(expected_type=MultiLevelStrData, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('multiLvlStrCache', 'f')
def __init__(self,
f=None,
multiLvlStrCache=None,
extLst=None,
):
self.f = f
self.multiLvlStrCache = multiLvlStrCache
class AxDataSource(Serialisable):
tagname = "cat"
numRef = Typed(expected_type=NumRef, allow_none=True)
numLit = Typed(expected_type=NumData, allow_none=True)
strRef = Typed(expected_type=StrRef, allow_none=True)
strLit = Typed(expected_type=StrData, allow_none=True)
multiLvlStrRef = Typed(expected_type=MultiLevelStrRef, allow_none=True)
def __init__(self,
numRef=None,
numLit=None,
strRef=None,
strLit=None,
multiLvlStrRef=None,
):
if not any([numLit, numRef, strRef, strLit, multiLvlStrRef]):
raise TypeError("A data source must be provided")
self.numRef = numRef
self.numLit = numLit
self.strRef = strRef
self.strLit = strLit
self.multiLvlStrRef = multiLvlStrRef

View File

@ -0,0 +1,43 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.nested import (
NestedMinMax
)
from openpyxl.descriptors import Typed
from .data_source import NumFmt
"""
Utility descriptors for the chart module.
For convenience but also clarity.
"""
class NestedGapAmount(NestedMinMax):
allow_none = True
min = 0
max = 500
class NestedOverlap(NestedMinMax):
allow_none = True
min = -100
max = 100
class NumberFormatDescriptor(Typed):
"""
Allow direct assignment of format code
"""
expected_type = NumFmt
allow_none = True
def __set__(self, instance, value):
if isinstance(value, str):
value = NumFmt(value)
super(NumberFormatDescriptor, self).__set__(instance, value)

View File

@ -0,0 +1,62 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Float,
Set,
Alias
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
NestedNoneSet,
NestedSet,
NestedBool,
NestedFloat,
)
from .data_source import NumDataSource
from .shapes import GraphicalProperties
class ErrorBars(Serialisable):
tagname = "errBars"
errDir = NestedNoneSet(values=(['x', 'y']))
direction = Alias("errDir")
errBarType = NestedSet(values=(['both', 'minus', 'plus']))
style = Alias("errBarType")
errValType = NestedSet(values=(['cust', 'fixedVal', 'percentage', 'stdDev', 'stdErr']))
size = Alias("errValType")
noEndCap = NestedBool(nested=True, allow_none=True)
plus = Typed(expected_type=NumDataSource, allow_none=True)
minus = Typed(expected_type=NumDataSource, allow_none=True)
val = NestedFloat(allow_none=True)
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias("spPr")
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('errDir','errBarType', 'errValType', 'noEndCap','minus', 'plus', 'val', 'spPr')
def __init__(self,
errDir=None,
errBarType="both",
errValType="fixedVal",
noEndCap=None,
plus=None,
minus=None,
val=None,
spPr=None,
extLst=None,
):
self.errDir = errDir
self.errBarType = errBarType
self.errValType = errValType
self.noEndCap = noEndCap
self.plus = plus
self.minus = minus
self.val = val
self.spPr = spPr

View File

@ -0,0 +1,127 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Sequence,
Alias,
Typed
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
NestedNoneSet,
NestedBool,
NestedString,
NestedInteger,
)
from .shapes import GraphicalProperties
from .text import RichText
class _DataLabelBase(Serialisable):
numFmt = NestedString(allow_none=True, attribute="formatCode")
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias('spPr')
txPr = Typed(expected_type=RichText, allow_none=True)
textProperties = Alias('txPr')
dLblPos = NestedNoneSet(values=['bestFit', 'b', 'ctr', 'inBase', 'inEnd',
'l', 'outEnd', 'r', 't'])
position = Alias('dLblPos')
showLegendKey = NestedBool(allow_none=True)
showVal = NestedBool(allow_none=True)
showCatName = NestedBool(allow_none=True)
showSerName = NestedBool(allow_none=True)
showPercent = NestedBool(allow_none=True)
showBubbleSize = NestedBool(allow_none=True)
showLeaderLines = NestedBool(allow_none=True)
separator = NestedString(allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ("numFmt", "spPr", "txPr", "dLblPos", "showLegendKey",
"showVal", "showCatName", "showSerName", "showPercent", "showBubbleSize",
"showLeaderLines", "separator")
def __init__(self,
numFmt=None,
spPr=None,
txPr=None,
dLblPos=None,
showLegendKey=None,
showVal=None,
showCatName=None,
showSerName=None,
showPercent=None,
showBubbleSize=None,
showLeaderLines=None,
separator=None,
extLst=None,
):
self.numFmt = numFmt
self.spPr = spPr
self.txPr = txPr
self.dLblPos = dLblPos
self.showLegendKey = showLegendKey
self.showVal = showVal
self.showCatName = showCatName
self.showSerName = showSerName
self.showPercent = showPercent
self.showBubbleSize = showBubbleSize
self.showLeaderLines = showLeaderLines
self.separator = separator
class DataLabel(_DataLabelBase):
tagname = "dLbl"
idx = NestedInteger()
numFmt = _DataLabelBase.numFmt
spPr = _DataLabelBase.spPr
txPr = _DataLabelBase.txPr
dLblPos = _DataLabelBase.dLblPos
showLegendKey = _DataLabelBase.showLegendKey
showVal = _DataLabelBase.showVal
showCatName = _DataLabelBase.showCatName
showSerName = _DataLabelBase.showSerName
showPercent = _DataLabelBase.showPercent
showBubbleSize = _DataLabelBase.showBubbleSize
showLeaderLines = _DataLabelBase.showLeaderLines
separator = _DataLabelBase.separator
extLst = _DataLabelBase.extLst
__elements__ = ("idx",) + _DataLabelBase.__elements__
def __init__(self, idx=0, **kw ):
self.idx = idx
super(DataLabel, self).__init__(**kw)
class DataLabelList(_DataLabelBase):
tagname = "dLbls"
dLbl = Sequence(expected_type=DataLabel, allow_none=True)
delete = NestedBool(allow_none=True)
numFmt = _DataLabelBase.numFmt
spPr = _DataLabelBase.spPr
txPr = _DataLabelBase.txPr
dLblPos = _DataLabelBase.dLblPos
showLegendKey = _DataLabelBase.showLegendKey
showVal = _DataLabelBase.showVal
showCatName = _DataLabelBase.showCatName
showSerName = _DataLabelBase.showSerName
showPercent = _DataLabelBase.showPercent
showBubbleSize = _DataLabelBase.showBubbleSize
showLeaderLines = _DataLabelBase.showLeaderLines
separator = _DataLabelBase.separator
extLst = _DataLabelBase.extLst
__elements__ = ("delete", "dLbl",) + _DataLabelBase.__elements__
def __init__(self, dLbl=(), delete=None, **kw):
self.dLbl = dLbl
self.delete = delete
super(DataLabelList, self).__init__(**kw)

View File

@ -0,0 +1,74 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
NoneSet,
Float,
Typed,
Alias,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
NestedNoneSet,
NestedSet,
NestedMinMax,
)
class ManualLayout(Serialisable):
tagname = "manualLayout"
layoutTarget = NestedNoneSet(values=(['inner', 'outer']))
xMode = NestedNoneSet(values=(['edge', 'factor']))
yMode = NestedNoneSet(values=(['edge', 'factor']))
wMode = NestedSet(values=(['edge', 'factor']))
hMode = NestedSet(values=(['edge', 'factor']))
x = NestedMinMax(min=-1, max=1, allow_none=True)
y = NestedMinMax(min=-1, max=1, allow_none=True)
w = NestedMinMax(min=0, max=1, allow_none=True)
width = Alias('w')
h = NestedMinMax(min=0, max=1, allow_none=True)
height = Alias('h')
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('layoutTarget', 'xMode', 'yMode', 'wMode', 'hMode', 'x',
'y', 'w', 'h')
def __init__(self,
layoutTarget=None,
xMode=None,
yMode=None,
wMode="factor",
hMode="factor",
x=None,
y=None,
w=None,
h=None,
extLst=None,
):
self.layoutTarget = layoutTarget
self.xMode = xMode
self.yMode = yMode
self.wMode = wMode
self.hMode = hMode
self.x = x
self.y = y
self.w = w
self.h = h
class Layout(Serialisable):
tagname = "layout"
manualLayout = Typed(expected_type=ManualLayout, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('manualLayout',)
def __init__(self,
manualLayout=None,
extLst=None,
):
self.manualLayout = manualLayout

View File

@ -0,0 +1,75 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Integer,
Alias,
Sequence,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
NestedBool,
NestedSet,
NestedInteger
)
from .layout import Layout
from .shapes import GraphicalProperties
from .text import RichText
class LegendEntry(Serialisable):
tagname = "legendEntry"
idx = NestedInteger()
delete = NestedBool()
txPr = Typed(expected_type=RichText, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('idx', 'delete', 'txPr')
def __init__(self,
idx=0,
delete=False,
txPr=None,
extLst=None,
):
self.idx = idx
self.delete = delete
self.txPr = txPr
class Legend(Serialisable):
tagname = "legend"
legendPos = NestedSet(values=(['b', 'tr', 'l', 'r', 't']))
position = Alias('legendPos')
legendEntry = Sequence(expected_type=LegendEntry)
layout = Typed(expected_type=Layout, allow_none=True)
overlay = NestedBool(allow_none=True)
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias('spPr')
txPr = Typed(expected_type=RichText, allow_none=True)
textProperties = Alias('txPr')
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('legendPos', 'legendEntry', 'layout', 'overlay', 'spPr', 'txPr',)
def __init__(self,
legendPos="r",
legendEntry=(),
layout=None,
overlay=None,
spPr=None,
txPr=None,
extLst=None,
):
self.legendPos = legendPos
self.legendEntry = legendEntry
self.layout = layout
self.overlay = overlay
self.spPr = spPr
self.txPr = txPr

View File

@ -0,0 +1,129 @@
#Autogenerated schema
from openpyxl.descriptors import (
Typed,
Sequence,
Alias,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
NestedSet,
NestedBool,
)
from ._chart import ChartBase
from .updown_bars import UpDownBars
from .descriptors import NestedGapAmount
from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines, _BaseAxis
from .label import DataLabelList
from .series import Series
class _LineChartBase(ChartBase):
grouping = NestedSet(values=(['percentStacked', 'standard', 'stacked']))
varyColors = NestedBool(allow_none=True)
ser = Sequence(expected_type=Series, allow_none=True)
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
dataLabels = Alias("dLbls")
dropLines = Typed(expected_type=ChartLines, allow_none=True)
_series_type = "line"
__elements__ = ('grouping', 'varyColors', 'ser', 'dLbls', 'dropLines')
def __init__(self,
grouping="standard",
varyColors=None,
ser=(),
dLbls=None,
dropLines=None,
**kw
):
self.grouping = grouping
self.varyColors = varyColors
self.ser = ser
self.dLbls = dLbls
self.dropLines = dropLines
super(_LineChartBase, self).__init__(**kw)
class LineChart(_LineChartBase):
tagname = "lineChart"
grouping = _LineChartBase.grouping
varyColors = _LineChartBase.varyColors
ser = _LineChartBase.ser
dLbls = _LineChartBase.dLbls
dropLines =_LineChartBase.dropLines
hiLowLines = Typed(expected_type=ChartLines, allow_none=True)
upDownBars = Typed(expected_type=UpDownBars, allow_none=True)
marker = NestedBool(allow_none=True)
smooth = NestedBool(allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
x_axis = Typed(expected_type=_BaseAxis)
y_axis = Typed(expected_type=NumericAxis)
__elements__ = _LineChartBase.__elements__ + ('hiLowLines', 'upDownBars', 'marker', 'smooth', 'axId')
def __init__(self,
hiLowLines=None,
upDownBars=None,
marker=None,
smooth=None,
extLst=None,
**kw
):
self.hiLowLines = hiLowLines
self.upDownBars = upDownBars
self.marker = marker
self.smooth = smooth
self.x_axis = TextAxis()
self.y_axis = NumericAxis()
super(LineChart, self).__init__(**kw)
class LineChart3D(_LineChartBase):
tagname = "line3DChart"
grouping = _LineChartBase.grouping
varyColors = _LineChartBase.varyColors
ser = _LineChartBase.ser
dLbls = _LineChartBase.dLbls
dropLines =_LineChartBase.dropLines
gapDepth = NestedGapAmount()
hiLowLines = Typed(expected_type=ChartLines, allow_none=True)
upDownBars = Typed(expected_type=UpDownBars, allow_none=True)
marker = NestedBool(allow_none=True)
smooth = NestedBool(allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
x_axis = Typed(expected_type=TextAxis)
y_axis = Typed(expected_type=NumericAxis)
z_axis = Typed(expected_type=SeriesAxis)
__elements__ = _LineChartBase.__elements__ + ('gapDepth', 'hiLowLines',
'upDownBars', 'marker', 'smooth', 'axId')
def __init__(self,
gapDepth=None,
hiLowLines=None,
upDownBars=None,
marker=None,
smooth=None,
**kw
):
self.gapDepth = gapDepth
self.hiLowLines = hiLowLines
self.upDownBars = upDownBars
self.marker = marker
self.smooth = smooth
self.x_axis = TextAxis()
self.y_axis = NumericAxis()
self.z_axis = SeriesAxis()
super(LineChart3D, self).__init__(**kw)

View File

@ -0,0 +1,90 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Alias,
)
from openpyxl.descriptors.excel import(
ExtensionList,
_explicit_none,
)
from openpyxl.descriptors.nested import (
NestedBool,
NestedInteger,
NestedMinMax,
NestedNoneSet,
)
from .layout import Layout
from .picture import PictureOptions
from .shapes import *
from .text import *
from .error_bar import *
class Marker(Serialisable):
tagname = "marker"
symbol = NestedNoneSet(values=(['circle', 'dash', 'diamond', 'dot', 'picture',
'plus', 'square', 'star', 'triangle', 'x', 'auto']),
to_tree=_explicit_none)
size = NestedMinMax(min=2, max=72, allow_none=True)
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias('spPr')
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('symbol', 'size', 'spPr')
def __init__(self,
symbol=None,
size=None,
spPr=None,
extLst=None,
):
self.symbol = symbol
self.size = size
if spPr is None:
spPr = GraphicalProperties()
self.spPr = spPr
class DataPoint(Serialisable):
tagname = "dPt"
idx = NestedInteger()
invertIfNegative = NestedBool(allow_none=True)
marker = Typed(expected_type=Marker, allow_none=True)
bubble3D = NestedBool(allow_none=True)
explosion = NestedInteger(allow_none=True)
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias('spPr')
pictureOptions = Typed(expected_type=PictureOptions, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('idx', 'invertIfNegative', 'marker', 'bubble3D',
'explosion', 'spPr', 'pictureOptions')
def __init__(self,
idx=None,
invertIfNegative=None,
marker=None,
bubble3D=None,
explosion=None,
spPr=None,
pictureOptions=None,
extLst=None,
):
self.idx = idx
self.invertIfNegative = invertIfNegative
self.marker = marker
self.bubble3D = bubble3D
self.explosion = explosion
if spPr is None:
spPr = GraphicalProperties()
self.spPr = spPr
self.pictureOptions = pictureOptions

View File

@ -0,0 +1,35 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors.nested import (
NestedBool,
NestedFloat,
NestedMinMax,
NestedNoneSet,
)
class PictureOptions(Serialisable):
tagname = "pictureOptions"
applyToFront = NestedBool(allow_none=True, nested=True)
applyToSides = NestedBool(allow_none=True, nested=True)
applyToEnd = NestedBool(allow_none=True, nested=True)
pictureFormat = NestedNoneSet(values=(['stretch', 'stack', 'stackScale']), nested=True)
pictureStackUnit = NestedFloat(allow_none=True, nested=True)
__elements__ = ('applyToFront', 'applyToSides', 'applyToEnd', 'pictureFormat', 'pictureStackUnit')
def __init__(self,
applyToFront=None,
applyToSides=None,
applyToEnd=None,
pictureFormat=None,
pictureStackUnit=None,
):
self.applyToFront = applyToFront
self.applyToSides = applyToSides
self.applyToEnd = applyToEnd
self.pictureFormat = pictureFormat
self.pictureStackUnit = pictureStackUnit

View File

@ -0,0 +1,177 @@
#Autogenerated schema
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Bool,
MinMax,
Integer,
NoneSet,
Float,
Alias,
Sequence,
)
from openpyxl.descriptors.excel import ExtensionList, Percentage
from openpyxl.descriptors.nested import (
NestedBool,
NestedMinMax,
NestedInteger,
NestedFloat,
NestedNoneSet,
NestedSet,
)
from openpyxl.descriptors.sequence import ValueSequence
from ._chart import ChartBase
from .axis import ChartLines
from .descriptors import NestedGapAmount
from .series import Series
from .label import DataLabelList
class _PieChartBase(ChartBase):
varyColors = NestedBool(allow_none=True)
ser = Sequence(expected_type=Series, allow_none=True)
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
dataLabels = Alias("dLbls")
_series_type = "pie"
__elements__ = ('varyColors', 'ser', 'dLbls')
def __init__(self,
varyColors=True,
ser=(),
dLbls=None,
):
self.varyColors = varyColors
self.ser = ser
self.dLbls = dLbls
super(_PieChartBase, self).__init__()
class PieChart(_PieChartBase):
tagname = "pieChart"
varyColors = _PieChartBase.varyColors
ser = _PieChartBase.ser
dLbls = _PieChartBase.dLbls
firstSliceAng = NestedMinMax(min=0, max=360)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = _PieChartBase.__elements__ + ('firstSliceAng', )
def __init__(self,
firstSliceAng=0,
extLst=None,
**kw
):
self.firstSliceAng = firstSliceAng
super(PieChart, self).__init__(**kw)
class PieChart3D(_PieChartBase):
tagname = "pie3DChart"
varyColors = _PieChartBase.varyColors
ser = _PieChartBase.ser
dLbls = _PieChartBase.dLbls
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = _PieChartBase.__elements__
class DoughnutChart(_PieChartBase):
tagname = "doughnutChart"
varyColors = _PieChartBase.varyColors
ser = _PieChartBase.ser
dLbls = _PieChartBase.dLbls
firstSliceAng = NestedMinMax(min=0, max=360)
holeSize = NestedMinMax(min=1, max=90, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = _PieChartBase.__elements__ + ('firstSliceAng', 'holeSize')
def __init__(self,
firstSliceAng=0,
holeSize=10,
extLst=None,
**kw
):
self.firstSliceAng = firstSliceAng
self.holeSize = holeSize
super(DoughnutChart, self).__init__(**kw)
class CustomSplit(Serialisable):
tagname = "custSplit"
secondPiePt = ValueSequence(expected_type=int)
__elements__ = ('secondPiePt',)
def __init__(self,
secondPiePt=(),
):
self.secondPiePt = secondPiePt
class ProjectedPieChart(_PieChartBase):
"""
From the spec 21.2.2.126
This element contains the pie of pie or bar of pie series on this
chart. Only the first series shall be displayed. The splitType element
shall determine whether the splitPos and custSplit elements apply.
"""
tagname = "ofPieChart"
varyColors = _PieChartBase.varyColors
ser = _PieChartBase.ser
dLbls = _PieChartBase.dLbls
ofPieType = NestedSet(values=(['pie', 'bar']))
type = Alias('ofPieType')
gapWidth = NestedGapAmount()
splitType = NestedNoneSet(values=(['auto', 'cust', 'percent', 'pos', 'val']))
splitPos = NestedFloat(allow_none=True)
custSplit = Typed(expected_type=CustomSplit, allow_none=True)
secondPieSize = NestedMinMax(min=5, max=200, allow_none=True)
serLines = Typed(expected_type=ChartLines, allow_none=True)
join_lines = Alias('serLines')
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = _PieChartBase.__elements__ + ('ofPieType', 'gapWidth',
'splitType', 'splitPos', 'custSplit', 'secondPieSize', 'serLines')
def __init__(self,
ofPieType="pie",
gapWidth=None,
splitType="auto",
splitPos=None,
custSplit=None,
secondPieSize=75,
serLines=None,
extLst=None,
**kw
):
self.ofPieType = ofPieType
self.gapWidth = gapWidth
self.splitType = splitType
self.splitPos = splitPos
self.custSplit = custSplit
self.secondPieSize = secondPieSize
if serLines is None:
self.serLines = ChartLines()
super(ProjectedPieChart, self).__init__(**kw)

View File

@ -0,0 +1,65 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Alias,
Typed,
)
from openpyxl.descriptors.nested import NestedInteger, NestedText
from openpyxl.descriptors.excel import ExtensionList
from .label import DataLabel
from .marker import Marker
from .shapes import GraphicalProperties
from .text import RichText
class PivotSource(Serialisable):
tagname = "pivotSource"
name = NestedText(expected_type=str)
fmtId = NestedInteger(expected_type=int)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('name', 'fmtId')
def __init__(self,
name=None,
fmtId=None,
extLst=None,
):
self.name = name
self.fmtId = fmtId
class PivotFormat(Serialisable):
tagname = "pivotFmt"
idx = NestedInteger(nested=True)
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias("spPr")
txPr = Typed(expected_type=RichText, allow_none=True)
TextBody = Alias("txPr")
marker = Typed(expected_type=Marker, allow_none=True)
dLbl = Typed(expected_type=DataLabel, allow_none=True)
DataLabel = Alias("dLbl")
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('idx', 'spPr', 'txPr', 'marker', 'dLbl')
def __init__(self,
idx=0,
spPr=None,
txPr=None,
marker=None,
dLbl=None,
extLst=None,
):
self.idx = idx
self.spPr = spPr
self.txPr = txPr
self.marker = marker
self.dLbl = dLbl

View File

@ -0,0 +1,162 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Alias,
)
from openpyxl.descriptors.excel import (
ExtensionList,
)
from openpyxl.descriptors.sequence import (
MultiSequence,
MultiSequencePart,
)
from openpyxl.descriptors.nested import (
NestedBool,
)
from ._3d import _3DBase
from .area_chart import AreaChart, AreaChart3D
from .bar_chart import BarChart, BarChart3D
from .bubble_chart import BubbleChart
from .line_chart import LineChart, LineChart3D
from .pie_chart import PieChart, PieChart3D, ProjectedPieChart, DoughnutChart
from .radar_chart import RadarChart
from .scatter_chart import ScatterChart
from .stock_chart import StockChart
from .surface_chart import SurfaceChart, SurfaceChart3D
from .layout import Layout
from .shapes import GraphicalProperties
from .text import RichText
from .axis import (
NumericAxis,
TextAxis,
SeriesAxis,
DateAxis,
)
class DataTable(Serialisable):
tagname = "dTable"
showHorzBorder = NestedBool(allow_none=True)
showVertBorder = NestedBool(allow_none=True)
showOutline = NestedBool(allow_none=True)
showKeys = NestedBool(allow_none=True)
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias('spPr')
txPr = Typed(expected_type=RichText, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('showHorzBorder', 'showVertBorder', 'showOutline',
'showKeys', 'spPr', 'txPr')
def __init__(self,
showHorzBorder=None,
showVertBorder=None,
showOutline=None,
showKeys=None,
spPr=None,
txPr=None,
extLst=None,
):
self.showHorzBorder = showHorzBorder
self.showVertBorder = showVertBorder
self.showOutline = showOutline
self.showKeys = showKeys
self.spPr = spPr
self.txPr = txPr
class PlotArea(Serialisable):
tagname = "plotArea"
layout = Typed(expected_type=Layout, allow_none=True)
dTable = Typed(expected_type=DataTable, allow_none=True)
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias("spPr")
extLst = Typed(expected_type=ExtensionList, allow_none=True)
# at least one chart
_charts = MultiSequence()
areaChart = MultiSequencePart(expected_type=AreaChart, store="_charts")
area3DChart = MultiSequencePart(expected_type=AreaChart3D, store="_charts")
lineChart = MultiSequencePart(expected_type=LineChart, store="_charts")
line3DChart = MultiSequencePart(expected_type=LineChart3D, store="_charts")
stockChart = MultiSequencePart(expected_type=StockChart, store="_charts")
radarChart = MultiSequencePart(expected_type=RadarChart, store="_charts")
scatterChart = MultiSequencePart(expected_type=ScatterChart, store="_charts")
pieChart = MultiSequencePart(expected_type=PieChart, store="_charts")
pie3DChart = MultiSequencePart(expected_type=PieChart3D, store="_charts")
doughnutChart = MultiSequencePart(expected_type=DoughnutChart, store="_charts")
barChart = MultiSequencePart(expected_type=BarChart, store="_charts")
bar3DChart = MultiSequencePart(expected_type=BarChart3D, store="_charts")
ofPieChart = MultiSequencePart(expected_type=ProjectedPieChart, store="_charts")
surfaceChart = MultiSequencePart(expected_type=SurfaceChart, store="_charts")
surface3DChart = MultiSequencePart(expected_type=SurfaceChart3D, store="_charts")
bubbleChart = MultiSequencePart(expected_type=BubbleChart, store="_charts")
# axes
_axes = MultiSequence()
valAx = MultiSequencePart(expected_type=NumericAxis, store="_axes")
catAx = MultiSequencePart(expected_type=TextAxis, store="_axes")
dateAx = MultiSequencePart(expected_type=DateAxis, store="_axes")
serAx = MultiSequencePart(expected_type=SeriesAxis, store="_axes")
__elements__ = ('layout', '_charts', '_axes', 'dTable', 'spPr')
def __init__(self,
layout=None,
dTable=None,
spPr=None,
_charts=(),
_axes=(),
extLst=None,
):
self.layout = layout
self.dTable = dTable
self.spPr = spPr
self._charts = _charts
self._axes = _axes
def to_tree(self, tagname=None, idx=None, namespace=None):
axIds = {ax.axId for ax in self._axes}
for chart in self._charts:
for id, axis in chart._axes.items():
if id not in axIds:
setattr(self, axis.tagname, axis)
axIds.add(id)
return super(PlotArea, self).to_tree(tagname)
@classmethod
def from_tree(cls, node):
self = super(PlotArea, cls).from_tree(node)
axes = dict((axis.axId, axis) for axis in self._axes)
for chart in self._charts:
if isinstance(chart, (ScatterChart, BubbleChart)):
x, y = (axes[axId] for axId in chart.axId)
chart.x_axis = x
chart.y_axis = y
continue
for axId in chart.axId:
axis = axes.get(axId)
if axis is None and isinstance(chart, _3DBase):
# Series Axis can be optional
chart.z_axis = None
continue
if axis.tagname in ("catAx", "dateAx"):
chart.x_axis = axis
elif axis.tagname == "valAx":
chart.y_axis = axis
elif axis.tagname == "serAx":
chart.z_axis = axis
return self

View File

@ -0,0 +1,57 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Float,
Typed,
Alias,
)
from openpyxl.worksheet.page import PrintPageSetup
from openpyxl.worksheet.header_footer import HeaderFooter
class PageMargins(Serialisable):
"""
Identical to openpyxl.worksheet.page.Pagemargins but element names are different :-/
"""
tagname = "pageMargins"
l = Float()
left = Alias('l')
r = Float()
right = Alias('r')
t = Float()
top = Alias('t')
b = Float()
bottom = Alias('b')
header = Float()
footer = Float()
def __init__(self, l=0.75, r=0.75, t=1, b=1, header=0.5, footer=0.5):
self.l = l
self.r = r
self.t = t
self.b = b
self.header = header
self.footer = footer
class PrintSettings(Serialisable):
tagname = "printSettings"
headerFooter = Typed(expected_type=HeaderFooter, allow_none=True)
pageMargins = Typed(expected_type=PageMargins, allow_none=True)
pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True)
__elements__ = ("headerFooter", "pageMargins", "pageMargins")
def __init__(self,
headerFooter=None,
pageMargins=None,
pageSetup=None,
):
self.headerFooter = headerFooter
self.pageMargins = pageMargins
self.pageSetup = pageSetup

View File

@ -0,0 +1,55 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Sequence,
Typed,
Alias,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
NestedBool,
NestedInteger,
NestedSet
)
from ._chart import ChartBase
from .axis import TextAxis, NumericAxis
from .series import Series
from .label import DataLabelList
class RadarChart(ChartBase):
tagname = "radarChart"
radarStyle = NestedSet(values=(['standard', 'marker', 'filled']))
type = Alias("radarStyle")
varyColors = NestedBool(nested=True, allow_none=True)
ser = Sequence(expected_type=Series, allow_none=True)
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
dataLabels = Alias("dLbls")
extLst = Typed(expected_type=ExtensionList, allow_none=True)
_series_type = "radar"
x_axis = Typed(expected_type=TextAxis)
y_axis = Typed(expected_type=NumericAxis)
__elements__ = ('radarStyle', 'varyColors', 'ser', 'dLbls', 'axId')
def __init__(self,
radarStyle="standard",
varyColors=None,
ser=(),
dLbls=None,
extLst=None,
**kw
):
self.radarStyle = radarStyle
self.varyColors = varyColors
self.ser = ser
self.dLbls = dLbls
self.x_axis = TextAxis()
self.y_axis = NumericAxis()
super(RadarChart, self).__init__(**kw)

View File

@ -0,0 +1,29 @@
# Copyright (c) 2010-2022 openpyxl
"""
Read a chart
"""
def read_chart(chartspace):
cs = chartspace
plot = cs.chart.plotArea
chart = plot._charts[0]
chart._charts = plot._charts
chart.title = cs.chart.title
chart.display_blanks = cs.chart.dispBlanksAs
chart.visible_cells_only = cs.chart.plotVisOnly
chart.layout = plot.layout
chart.legend = cs.chart.legend
# 3d attributes
chart.floor = cs.chart.floor
chart.sideWall = cs.chart.sideWall
chart.backWall = cs.chart.backWall
chart.pivotSource = cs.pivotSource
chart.pivotFormats = cs.chart.pivotFmts
chart.idx_base = min((s.idx for s in chart.series), default=0)
chart._reindex()
return chart

View File

@ -0,0 +1,124 @@
# Copyright (c) 2010-2022 openpyxl
from itertools import chain
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
MinMax,
Typed,
String,
Strict,
)
from openpyxl.worksheet.worksheet import Worksheet
from openpyxl.utils import (
get_column_letter,
range_to_tuple,
quote_sheetname
)
class DummyWorksheet:
def __init__(self, title):
self.title = title
class Reference(Strict):
"""
Normalise cell range references
"""
min_row = MinMax(min=1, max=1000000, expected_type=int)
max_row = MinMax(min=1, max=1000000, expected_type=int)
min_col = MinMax(min=1, max=16384, expected_type=int)
max_col = MinMax(min=1, max=16384, expected_type=int)
range_string = String(allow_none=True)
def __init__(self,
worksheet=None,
min_col=None,
min_row=None,
max_col=None,
max_row=None,
range_string=None
):
if range_string is not None:
sheetname, boundaries = range_to_tuple(range_string)
min_col, min_row, max_col, max_row = boundaries
worksheet = DummyWorksheet(sheetname)
self.worksheet = worksheet
self.min_col = min_col
self.min_row = min_row
if max_col is None:
max_col = min_col
self.max_col = max_col
if max_row is None:
max_row = min_row
self.max_row = max_row
def __repr__(self):
return str(self)
def __str__(self):
fmt = u"{0}!${1}${2}:${3}${4}"
if (self.min_col == self.max_col
and self.min_row == self.max_row):
fmt = u"{0}!${1}${2}"
return fmt.format(self.sheetname,
get_column_letter(self.min_col), self.min_row,
get_column_letter(self.max_col), self.max_row
)
__str__ = __str__
def __len__(self):
if self.min_row == self.max_row:
return 1 + self.max_col - self.min_col
return 1 + self.max_row - self.min_row
def __eq__(self, other):
return str(self) == str(other)
@property
def rows(self):
"""
Return all rows in the range
"""
for row in range(self.min_row, self.max_row+1):
yield Reference(self.worksheet, self.min_col, row, self.max_col, row)
@property
def cols(self):
"""
Return all columns in the range
"""
for col in range(self.min_col, self.max_col+1):
yield Reference(self.worksheet, col, self.min_row, col, self.max_row)
def pop(self):
"""
Return and remove the first cell
"""
cell = "{0}{1}".format(get_column_letter(self.min_col), self.min_row)
if self.min_row == self.max_row:
self.min_col += 1
else:
self.min_row += 1
return cell
@property
def sheetname(self):
return quote_sheetname(self.worksheet.title)

View File

@ -0,0 +1,53 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Sequence,
Alias
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
NestedNoneSet,
NestedBool,
)
from ._chart import ChartBase
from .axis import NumericAxis
from .series import XYSeries
from .label import DataLabelList
class ScatterChart(ChartBase):
tagname = "scatterChart"
scatterStyle = NestedNoneSet(values=(['line', 'lineMarker', 'marker', 'smooth', 'smoothMarker']))
varyColors = NestedBool(allow_none=True)
ser = Sequence(expected_type=XYSeries, allow_none=True)
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
dataLabels = Alias("dLbls")
extLst = Typed(expected_type=ExtensionList, allow_none=True)
x_axis = Typed(expected_type=NumericAxis)
y_axis = Typed(expected_type=NumericAxis)
_series_type = "scatter"
__elements__ = ('scatterStyle', 'varyColors', 'ser', 'dLbls', 'axId',)
def __init__(self,
scatterStyle=None,
varyColors=None,
ser=(),
dLbls=None,
extLst=None,
**kw
):
self.scatterStyle = scatterStyle
self.varyColors = varyColors
self.ser = ser
self.dLbls = dLbls
self.x_axis = NumericAxis(axId=10, crossAx=20)
self.y_axis = NumericAxis(axId=20, crossAx=10)
super(ScatterChart, self).__init__(**kw)

View File

@ -0,0 +1,197 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
String,
Integer,
Bool,
Alias,
Sequence,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
NestedInteger,
NestedBool,
NestedNoneSet,
NestedText,
)
from .shapes import GraphicalProperties
from .data_source import (
AxDataSource,
NumDataSource,
NumRef,
StrRef,
)
from .error_bar import ErrorBars
from .label import DataLabelList
from .marker import DataPoint, PictureOptions, Marker
from .trendline import Trendline
attribute_mapping = {
'area': ('idx', 'order', 'tx', 'spPr', 'pictureOptions', 'dPt', 'dLbls', 'errBars',
'trendline', 'cat', 'val',),
'bar':('idx', 'order','tx', 'spPr', 'invertIfNegative', 'pictureOptions', 'dPt',
'dLbls', 'trendline', 'errBars', 'cat', 'val', 'shape'),
'bubble':('idx','order', 'tx', 'spPr', 'invertIfNegative', 'dPt', 'dLbls',
'trendline', 'errBars', 'xVal', 'yVal', 'bubbleSize', 'bubble3D'),
'line':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'trendline',
'errBars', 'cat', 'val', 'smooth'),
'pie':('idx', 'order', 'tx', 'spPr', 'explosion', 'dPt', 'dLbls', 'cat', 'val'),
'radar':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'cat', 'val'),
'scatter':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'trendline',
'errBars', 'xVal', 'yVal', 'smooth'),
'surface':('idx', 'order', 'tx', 'spPr', 'cat', 'val'),
}
class SeriesLabel(Serialisable):
tagname = "tx"
strRef = Typed(expected_type=StrRef, allow_none=True)
v = NestedText(expected_type=str, allow_none=True)
value = Alias('v')
__elements__ = ('strRef', 'v')
def __init__(self,
strRef=None,
v=None):
self.strRef = strRef
self.v = v
class Series(Serialisable):
"""
Generic series object. Should not be instantiated directly.
User the chart.Series factory instead.
"""
tagname = "ser"
idx = NestedInteger()
order = NestedInteger()
tx = Typed(expected_type=SeriesLabel, allow_none=True)
title = Alias('tx')
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias('spPr')
# area chart
pictureOptions = Typed(expected_type=PictureOptions, allow_none=True)
dPt = Sequence(expected_type=DataPoint, allow_none=True)
data_points = Alias("dPt")
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
labels = Alias("dLbls")
trendline = Typed(expected_type=Trendline, allow_none=True)
errBars = Typed(expected_type=ErrorBars, allow_none=True)
cat = Typed(expected_type=AxDataSource, allow_none=True)
identifiers = Alias("cat")
val = Typed(expected_type=NumDataSource, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
#bar chart
invertIfNegative = NestedBool(allow_none=True)
shape = NestedNoneSet(values=(['cone', 'coneToMax', 'box', 'cylinder', 'pyramid', 'pyramidToMax']))
#bubble chart
xVal = Typed(expected_type=AxDataSource, allow_none=True)
yVal = Typed(expected_type=NumDataSource, allow_none=True)
bubbleSize = Typed(expected_type=NumDataSource, allow_none=True)
zVal = Alias("bubbleSize")
bubble3D = NestedBool(allow_none=True)
#line chart
marker = Typed(expected_type=Marker, allow_none=True)
smooth = NestedBool(allow_none=True)
#pie chart
explosion = NestedInteger(allow_none=True)
__elements__ = ()
def __init__(self,
idx=0,
order=0,
tx=None,
spPr=None,
pictureOptions=None,
dPt=(),
dLbls=None,
trendline=None,
errBars=None,
cat=None,
val=None,
invertIfNegative=None,
shape=None,
xVal=None,
yVal=None,
bubbleSize=None,
bubble3D=None,
marker=None,
smooth=None,
explosion=None,
extLst=None,
):
self.idx = idx
self.order = order
self.tx = tx
if spPr is None:
spPr = GraphicalProperties()
self.spPr = spPr
self.pictureOptions = pictureOptions
self.dPt = dPt
self.dLbls = dLbls
self.trendline = trendline
self.errBars = errBars
self.cat = cat
self.val = val
self.invertIfNegative = invertIfNegative
self.shape = shape
self.xVal = xVal
self.yVal = yVal
self.bubbleSize = bubbleSize
self.bubble3D = bubble3D
if marker is None:
marker = Marker()
self.marker = marker
self.smooth = smooth
self.explosion = explosion
def to_tree(self, tagname=None, idx=None):
"""The index can need rebasing"""
if idx is not None:
if self.order == self.idx:
self.order = idx # rebase the order if the index has been rebased
self.idx = idx
return super(Series, self).to_tree(tagname)
class XYSeries(Series):
"""Dedicated series for charts that have x and y series"""
idx = Series.idx
order = Series.order
tx = Series.tx
spPr = Series.spPr
dPt = Series.dPt
dLbls = Series.dLbls
trendline = Series.trendline
errBars = Series.errBars
xVal = Series.xVal
yVal = Series.yVal
invertIfNegative = Series.invertIfNegative
bubbleSize = Series.bubbleSize
bubble3D = Series.bubble3D
marker = Series.marker
smooth = Series.smooth

View File

@ -0,0 +1,41 @@
# Copyright (c) 2010-2022 openpyxl
from .data_source import NumDataSource, NumRef, AxDataSource
from .reference import Reference
from .series import Series, XYSeries, SeriesLabel, StrRef
from openpyxl.utils import rows_from_range, quote_sheetname
def SeriesFactory(values, xvalues=None, zvalues=None, title=None, title_from_data=False):
"""
Convenience Factory for creating chart data series.
"""
if not isinstance(values, Reference):
values = Reference(range_string=values)
if title_from_data:
cell = values.pop()
title = u"{0}!{1}".format(values.sheetname, cell)
title = SeriesLabel(strRef=StrRef(title))
elif title is not None:
title = SeriesLabel(v=title)
source = NumDataSource(numRef=NumRef(f=values))
if xvalues is not None:
if not isinstance(xvalues, Reference):
xvalues = Reference(range_string=xvalues)
series = XYSeries()
series.yVal = source
series.xVal = AxDataSource(numRef=NumRef(f=xvalues))
if zvalues is not None:
if not isinstance(zvalues, Reference):
zvalues = Reference(range_string=zvalues)
series.zVal = NumDataSource(NumRef(f=zvalues))
else:
series = Series()
series.val = source
if title is not None:
series.title = title
return series

View File

@ -0,0 +1,89 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Alias
)
from openpyxl.descriptors.nested import (
EmptyTag
)
from openpyxl.drawing.colors import ColorChoiceDescriptor
from openpyxl.drawing.fill import *
from openpyxl.drawing.line import LineProperties
from openpyxl.drawing.geometry import (
Shape3D,
Scene3D,
Transform2D,
CustomGeometry2D,
PresetGeometry2D,
)
class GraphicalProperties(Serialisable):
"""
Somewhat vaguely 21.2.2.197 says this:
This element specifies the formatting for the parent chart element. The
custGeom, prstGeom, scene3d, and xfrm elements are not supported. The
bwMode attribute is not supported.
This doesn't leave much. And the element is used in different places.
"""
tagname = "spPr"
bwMode = NoneSet(values=(['clr', 'auto', 'gray', 'ltGray', 'invGray',
'grayWhite', 'blackGray', 'blackWhite', 'black', 'white', 'hidden']
)
)
xfrm = Typed(expected_type=Transform2D, allow_none=True)
transform = Alias('xfrm')
custGeom = Typed(expected_type=CustomGeometry2D, allow_none=True) # either or
prstGeom = Typed(expected_type=PresetGeometry2D, allow_none=True)
# fills one of
noFill = EmptyTag(namespace=DRAWING_NS)
solidFill = ColorChoiceDescriptor()
gradFill = Typed(expected_type=GradientFillProperties, allow_none=True)
pattFill = Typed(expected_type=PatternFillProperties, allow_none=True)
ln = Typed(expected_type=LineProperties, allow_none=True)
line = Alias('ln')
scene3d = Typed(expected_type=Scene3D, allow_none=True)
sp3d = Typed(expected_type=Shape3D, allow_none=True)
shape3D = Alias('sp3d')
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
__elements__ = ('xfrm', 'prstGeom', 'noFill', 'solidFill', 'gradFill', 'pattFill',
'ln', 'scene3d', 'sp3d')
def __init__(self,
bwMode=None,
xfrm=None,
noFill=None,
solidFill=None,
gradFill=None,
pattFill=None,
ln=None,
scene3d=None,
custGeom=None,
prstGeom=None,
sp3d=None,
extLst=None,
):
self.bwMode = bwMode
self.xfrm = xfrm
self.noFill = noFill
self.solidFill = solidFill
self.gradFill = gradFill
self.pattFill = pattFill
if ln is None:
ln = LineProperties()
self.ln = ln
self.custGeom = custGeom
self.prstGeom = prstGeom
self.scene3d = scene3d
self.sp3d = sp3d

View File

@ -0,0 +1,54 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Sequence,
Alias,
)
from openpyxl.descriptors.excel import ExtensionList
from ._chart import ChartBase
from .axis import TextAxis, NumericAxis, ChartLines
from .updown_bars import UpDownBars
from .label import DataLabelList
from .series import Series
class StockChart(ChartBase):
tagname = "stockChart"
ser = Sequence(expected_type=Series) #min 3, max4
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
dataLabels = Alias('dLbls')
dropLines = Typed(expected_type=ChartLines, allow_none=True)
hiLowLines = Typed(expected_type=ChartLines, allow_none=True)
upDownBars = Typed(expected_type=UpDownBars, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
x_axis = Typed(expected_type=TextAxis)
y_axis = Typed(expected_type=NumericAxis)
_series_type = "line"
__elements__ = ('ser', 'dLbls', 'dropLines', 'hiLowLines', 'upDownBars',
'axId')
def __init__(self,
ser=(),
dLbls=None,
dropLines=None,
hiLowLines=None,
upDownBars=None,
extLst=None,
**kw
):
self.ser = ser
self.dLbls = dLbls
self.dropLines = dropLines
self.hiLowLines = hiLowLines
self.upDownBars = upDownBars
self.x_axis = TextAxis()
self.y_axis = NumericAxis()
super(StockChart, self).__init__(**kw)

View File

@ -0,0 +1,119 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Integer,
Bool,
Alias,
Sequence,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
NestedInteger,
NestedBool,
)
from ._chart import ChartBase
from ._3d import _3DBase
from .axis import TextAxis, NumericAxis, SeriesAxis
from .shapes import GraphicalProperties
from .series import Series
class BandFormat(Serialisable):
tagname = "bandFmt"
idx = NestedInteger()
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias("spPr")
__elements__ = ('idx', 'spPr')
def __init__(self,
idx=0,
spPr=None,
):
self.idx = idx
self.spPr = spPr
class BandFormatList(Serialisable):
tagname = "bandFmts"
bandFmt = Sequence(expected_type=BandFormat, allow_none=True)
__elements__ = ('bandFmt',)
def __init__(self,
bandFmt=(),
):
self.bandFmt = bandFmt
class _SurfaceChartBase(ChartBase):
wireframe = NestedBool(allow_none=True)
ser = Sequence(expected_type=Series, allow_none=True)
bandFmts = Typed(expected_type=BandFormatList, allow_none=True)
_series_type = "surface"
__elements__ = ('wireframe', 'ser', 'bandFmts')
def __init__(self,
wireframe=None,
ser=(),
bandFmts=None,
**kw
):
self.wireframe = wireframe
self.ser = ser
self.bandFmts = bandFmts
super(_SurfaceChartBase, self).__init__(**kw)
class SurfaceChart3D(_SurfaceChartBase, _3DBase):
tagname = "surface3DChart"
wireframe = _SurfaceChartBase.wireframe
ser = _SurfaceChartBase.ser
bandFmts = _SurfaceChartBase.bandFmts
extLst = Typed(expected_type=ExtensionList, allow_none=True)
x_axis = Typed(expected_type=TextAxis)
y_axis = Typed(expected_type=NumericAxis)
z_axis = Typed(expected_type=SeriesAxis)
__elements__ = _SurfaceChartBase.__elements__ + ('axId',)
def __init__(self, **kw):
self.x_axis = TextAxis()
self.y_axis = NumericAxis()
self.z_axis = SeriesAxis()
super(SurfaceChart3D, self).__init__(**kw)
class SurfaceChart(SurfaceChart3D):
tagname = "surfaceChart"
wireframe = _SurfaceChartBase.wireframe
ser = _SurfaceChartBase.ser
bandFmts = _SurfaceChartBase.bandFmts
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = SurfaceChart3D.__elements__
def __init__(self, **kw):
super(SurfaceChart, self).__init__(**kw)
self.y_axis.delete = True
self.view3D.x_rotation = 90
self.view3D.y_rotation = 0
self.view3D.perspective = False
self.view3D.right_angle_axes = False

View File

@ -0,0 +1,78 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Alias,
Sequence,
)
from openpyxl.drawing.text import (
RichTextProperties,
ListStyle,
Paragraph,
)
from .data_source import StrRef
class RichText(Serialisable):
"""
From the specification: 21.2.2.216
This element specifies text formatting. The lstStyle element is not supported.
"""
tagname = "rich"
bodyPr = Typed(expected_type=RichTextProperties)
properties = Alias("bodyPr")
lstStyle = Typed(expected_type=ListStyle, allow_none=True)
p = Sequence(expected_type=Paragraph)
paragraphs = Alias('p')
__elements__ = ("bodyPr", "lstStyle", "p")
def __init__(self,
bodyPr=None,
lstStyle=None,
p=None,
):
if bodyPr is None:
bodyPr = RichTextProperties()
self.bodyPr = bodyPr
self.lstStyle = lstStyle
if p is None:
p = [Paragraph()]
self.p = p
class Text(Serialisable):
"""
The value can be either a cell reference or a text element
If both are present then the reference will be used.
"""
tagname = "tx"
strRef = Typed(expected_type=StrRef, allow_none=True)
rich = Typed(expected_type=RichText, allow_none=True)
__elements__ = ("strRef", "rich")
def __init__(self,
strRef=None,
rich=None
):
self.strRef = strRef
if rich is None:
rich = RichText()
self.rich = rich
def to_tree(self, tagname=None, idx=None, namespace=None):
if self.strRef and self.rich:
self.rich = None # can only have one
return super(Text, self).to_tree(tagname, idx, namespace)

View File

@ -0,0 +1,76 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Alias,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import NestedBool
from .text import Text, RichText
from .layout import Layout
from .shapes import GraphicalProperties
from openpyxl.drawing.text import (
Paragraph,
RegularTextRun,
LineBreak,
ParagraphProperties,
CharacterProperties,
)
class Title(Serialisable):
tagname = "title"
tx = Typed(expected_type=Text, allow_none=True)
text = Alias('tx')
layout = Typed(expected_type=Layout, allow_none=True)
overlay = NestedBool(allow_none=True)
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias('spPr')
txPr = Typed(expected_type=RichText, allow_none=True)
body = Alias('txPr')
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('tx', 'layout', 'overlay', 'spPr', 'txPr')
def __init__(self,
tx=None,
layout=None,
overlay=None,
spPr=None,
txPr=None,
extLst=None,
):
if tx is None:
tx = Text()
self.tx = tx
self.layout = layout
self.overlay = overlay
self.spPr = spPr
self.txPr = txPr
def title_maker(text):
title = Title()
paraprops = ParagraphProperties()
paraprops.defRPr = CharacterProperties()
paras = [Paragraph(r=[RegularTextRun(t=s)], pPr=paraprops) for s in text.split("\n")]
title.tx.rich.paragraphs = paras
return title
class TitleDescriptor(Typed):
expected_type = Title
allow_none = True
def __set__(self, instance, value):
if isinstance(value, str):
value = title_maker(value)
super(TitleDescriptor, self).__set__(instance, value)

View File

@ -0,0 +1,98 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
String,
Alias
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
NestedBool,
NestedInteger,
NestedFloat,
NestedSet
)
from .data_source import NumFmt
from .shapes import GraphicalProperties
from .text import RichText, Text
from .layout import Layout
class TrendlineLabel(Serialisable):
tagname = "trendlineLbl"
layout = Typed(expected_type=Layout, allow_none=True)
tx = Typed(expected_type=Text, allow_none=True)
numFmt = Typed(expected_type=NumFmt, allow_none=True)
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias("spPr")
txPr = Typed(expected_type=RichText, allow_none=True)
textProperties = Alias("txPr")
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('layout', 'tx', 'numFmt', 'spPr', 'txPr')
def __init__(self,
layout=None,
tx=None,
numFmt=None,
spPr=None,
txPr=None,
extLst=None,
):
self.layout = layout
self.tx = tx
self.numFmt = numFmt
self.spPr = spPr
self.txPr = txPr
class Trendline(Serialisable):
tagname = "trendline"
name = String(allow_none=True)
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
graphicalProperties = Alias('spPr')
trendlineType = NestedSet(values=(['exp', 'linear', 'log', 'movingAvg', 'poly', 'power']))
order = NestedInteger(allow_none=True)
period = NestedInteger(allow_none=True)
forward = NestedFloat(allow_none=True)
backward = NestedFloat(allow_none=True)
intercept = NestedFloat(allow_none=True)
dispRSqr = NestedBool(allow_none=True)
dispEq = NestedBool(allow_none=True)
trendlineLbl = Typed(expected_type=TrendlineLabel, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('name', 'spPr', 'trendlineType', 'order', 'period',
'forward', 'backward', 'intercept', 'dispRSqr', 'dispEq', 'trendlineLbl')
def __init__(self,
name=None,
spPr=None,
trendlineType='linear',
order=None,
period=None,
forward=None,
backward=None,
intercept=None,
dispRSqr=None,
dispEq=None,
trendlineLbl=None,
extLst=None,
):
self.name = name
self.spPr = spPr
self.trendlineType = trendlineType
self.order = order
self.period = period
self.forward = forward
self.backward = backward
self.intercept = intercept
self.dispRSqr = dispRSqr
self.dispEq = dispEq
self.trendlineLbl = trendlineLbl

View File

@ -0,0 +1,31 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import Typed
from openpyxl.descriptors.excel import ExtensionList
from .shapes import GraphicalProperties
from .axis import ChartLines
from .descriptors import NestedGapAmount
class UpDownBars(Serialisable):
tagname = "upbars"
gapWidth = NestedGapAmount()
upBars = Typed(expected_type=ChartLines, allow_none=True)
downBars = Typed(expected_type=ChartLines, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('gapWidth', 'upBars', 'downBars')
def __init__(self,
gapWidth=150,
upBars=None,
downBars=None,
extLst=None,
):
self.gapWidth = gapWidth
self.upBars = upBars
self.downBars = downBars

View File

@ -0,0 +1,3 @@
# Copyright (c) 2010-2022 openpyxl
from .chartsheet import Chartsheet

View File

@ -0,0 +1,109 @@
# Copyright (c) 2010-2022 openpyxl
from weakref import ref
from openpyxl.descriptors import Typed, Set, Alias
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.drawing.spreadsheet_drawing import (
AbsoluteAnchor,
SpreadsheetDrawing,
)
from openpyxl.worksheet.page import (
PageMargins,
PrintPageSetup
)
from openpyxl.packaging.relationship import Relationship, RelationshipList
from openpyxl.worksheet.drawing import Drawing
from openpyxl.worksheet.header_footer import HeaderFooter
from openpyxl.workbook.child import _WorkbookChild
from openpyxl.xml.constants import SHEET_MAIN_NS, REL_NS
from .relation import DrawingHF, SheetBackgroundPicture
from .properties import ChartsheetProperties
from .protection import ChartsheetProtection
from .views import ChartsheetViewList
from .custom import CustomChartsheetViews
from .publish import WebPublishItems
class Chartsheet(_WorkbookChild, Serialisable):
tagname = "chartsheet"
_default_title = "Chart"
_rel_type = "chartsheet"
_path = "/xl/chartsheets/sheet{0}.xml"
mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"
sheetPr = Typed(expected_type=ChartsheetProperties, allow_none=True)
sheetViews = Typed(expected_type=ChartsheetViewList)
sheetProtection = Typed(expected_type=ChartsheetProtection, allow_none=True)
customSheetViews = Typed(expected_type=CustomChartsheetViews, allow_none=True)
pageMargins = Typed(expected_type=PageMargins, allow_none=True)
pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True)
drawing = Typed(expected_type=Drawing, allow_none=True)
drawingHF = Typed(expected_type=DrawingHF, allow_none=True)
picture = Typed(expected_type=SheetBackgroundPicture, allow_none=True)
webPublishItems = Typed(expected_type=WebPublishItems, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
sheet_state = Set(values=('visible', 'hidden', 'veryHidden'))
headerFooter = Typed(expected_type=HeaderFooter)
HeaderFooter = Alias('headerFooter')
__elements__ = (
'sheetPr', 'sheetViews', 'sheetProtection', 'customSheetViews',
'pageMargins', 'pageSetup', 'headerFooter', 'drawing', 'drawingHF',
'picture', 'webPublishItems')
__attrs__ = ()
def __init__(self,
sheetPr=None,
sheetViews=None,
sheetProtection=None,
customSheetViews=None,
pageMargins=None,
pageSetup=None,
headerFooter=None,
drawing=None,
drawingHF=None,
picture=None,
webPublishItems=None,
extLst=None,
parent=None,
title="",
sheet_state='visible',
):
super(Chartsheet, self).__init__(parent, title)
self._charts = []
self.sheetPr = sheetPr
if sheetViews is None:
sheetViews = ChartsheetViewList()
self.sheetViews = sheetViews
self.sheetProtection = sheetProtection
self.customSheetViews = customSheetViews
self.pageMargins = pageMargins
self.pageSetup = pageSetup
if headerFooter is not None:
self.headerFooter = headerFooter
self.drawing = Drawing("rId1")
self.drawingHF = drawingHF
self.picture = picture
self.webPublishItems = webPublishItems
self.sheet_state = sheet_state
def add_chart(self, chart):
chart.anchor = AbsoluteAnchor()
self._charts.append(chart)
def to_tree(self):
self._drawing = SpreadsheetDrawing()
self._drawing.charts = self._charts
tree = super(Chartsheet, self).to_tree()
if not self.headerFooter:
el = tree.find('headerFooter')
tree.remove(el)
tree.set("xmlns", SHEET_MAIN_NS)
return tree

View File

@ -0,0 +1,61 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.worksheet.header_footer import HeaderFooter
from openpyxl.descriptors import (
Bool,
Integer,
Set,
Typed,
Sequence
)
from openpyxl.descriptors.excel import Guid
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.worksheet.page import (
PageMargins,
PrintPageSetup
)
class CustomChartsheetView(Serialisable):
tagname = "customSheetView"
guid = Guid()
scale = Integer()
state = Set(values=(['visible', 'hidden', 'veryHidden']))
zoomToFit = Bool(allow_none=True)
pageMargins = Typed(expected_type=PageMargins, allow_none=True)
pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True)
headerFooter = Typed(expected_type=HeaderFooter, allow_none=True)
__elements__ = ('pageMargins', 'pageSetup', 'headerFooter')
def __init__(self,
guid=None,
scale=None,
state='visible',
zoomToFit=None,
pageMargins=None,
pageSetup=None,
headerFooter=None,
):
self.guid = guid
self.scale = scale
self.state = state
self.zoomToFit = zoomToFit
self.pageMargins = pageMargins
self.pageSetup = pageSetup
self.headerFooter = headerFooter
class CustomChartsheetViews(Serialisable):
tagname = "customSheetViews"
customSheetView = Sequence(expected_type=CustomChartsheetView, allow_none=True)
__elements__ = ('customSheetView',)
def __init__(self,
customSheetView=None,
):
self.customSheetView = customSheetView

View File

@ -0,0 +1,28 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors import (
Bool,
String,
Typed
)
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.styles import Color
class ChartsheetProperties(Serialisable):
tagname = "sheetPr"
published = Bool(allow_none=True)
codeName = String(allow_none=True)
tabColor = Typed(expected_type=Color, allow_none=True)
__elements__ = ('tabColor',)
def __init__(self,
published=None,
codeName=None,
tabColor=None,
):
self.published = published
self.codeName = codeName
self.tabColor = tabColor

View File

@ -0,0 +1,41 @@
import hashlib
from openpyxl.descriptors import (Bool, Integer, String)
from openpyxl.descriptors.excel import Base64Binary
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.worksheet.protection import (
hash_password,
_Protected
)
class ChartsheetProtection(Serialisable, _Protected):
tagname = "sheetProtection"
algorithmName = String(allow_none=True)
hashValue = Base64Binary(allow_none=True)
saltValue = Base64Binary(allow_none=True)
spinCount = Integer(allow_none=True)
content = Bool(allow_none=True)
objects = Bool(allow_none=True)
__attrs__ = ("content", "objects", "password", "hashValue", "spinCount", "saltValue", "algorithmName")
def __init__(self,
content=None,
objects=None,
hashValue=None,
spinCount=None,
saltValue=None,
algorithmName=None,
password=None,
):
self.content = content
self.objects = objects
self.hashValue = hashValue
self.spinCount = spinCount
self.saltValue = saltValue
self.algorithmName = algorithmName
if password is not None:
self.password = password

View File

@ -0,0 +1,58 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors import (
Bool,
Integer,
String,
Set,
Sequence
)
from openpyxl.descriptors.serialisable import Serialisable
class WebPublishItem(Serialisable):
tagname = "webPublishItem"
id = Integer()
divId = String()
sourceType = Set(values=(['sheet', 'printArea', 'autoFilter', 'range', 'chart', 'pivotTable', 'query', 'label']))
sourceRef = String()
sourceObject = String(allow_none=True)
destinationFile = String()
title = String(allow_none=True)
autoRepublish = Bool(allow_none=True)
def __init__(self,
id=None,
divId=None,
sourceType=None,
sourceRef=None,
sourceObject=None,
destinationFile=None,
title=None,
autoRepublish=None,
):
self.id = id
self.divId = divId
self.sourceType = sourceType
self.sourceRef = sourceRef
self.sourceObject = sourceObject
self.destinationFile = destinationFile
self.title = title
self.autoRepublish = autoRepublish
class WebPublishItems(Serialisable):
tagname = "WebPublishItems"
count = Integer(allow_none=True)
webPublishItem = Sequence(expected_type=WebPublishItem, )
__elements__ = ('webPublishItem',)
def __init__(self,
count=None,
webPublishItem=None,
):
self.count = len(webPublishItem)
self.webPublishItem = webPublishItem

View File

@ -0,0 +1,97 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors import (
Integer,
Alias
)
from openpyxl.descriptors.excel import Relation
from openpyxl.descriptors.serialisable import Serialisable
class SheetBackgroundPicture(Serialisable):
tagname = "picture"
id = Relation()
def __init__(self, id):
self.id = id
class DrawingHF(Serialisable):
id = Relation()
lho = Integer(allow_none=True)
leftHeaderOddPages = Alias('lho')
lhe = Integer(allow_none=True)
leftHeaderEvenPages = Alias('lhe')
lhf = Integer(allow_none=True)
leftHeaderFirstPage = Alias('lhf')
cho = Integer(allow_none=True)
centerHeaderOddPages = Alias('cho')
che = Integer(allow_none=True)
centerHeaderEvenPages = Alias('che')
chf = Integer(allow_none=True)
centerHeaderFirstPage = Alias('chf')
rho = Integer(allow_none=True)
rightHeaderOddPages = Alias('rho')
rhe = Integer(allow_none=True)
rightHeaderEvenPages = Alias('rhe')
rhf = Integer(allow_none=True)
rightHeaderFirstPage = Alias('rhf')
lfo = Integer(allow_none=True)
leftFooterOddPages = Alias('lfo')
lfe = Integer(allow_none=True)
leftFooterEvenPages = Alias('lfe')
lff = Integer(allow_none=True)
leftFooterFirstPage = Alias('lff')
cfo = Integer(allow_none=True)
centerFooterOddPages = Alias('cfo')
cfe = Integer(allow_none=True)
centerFooterEvenPages = Alias('cfe')
cff = Integer(allow_none=True)
centerFooterFirstPage = Alias('cff')
rfo = Integer(allow_none=True)
rightFooterOddPages = Alias('rfo')
rfe = Integer(allow_none=True)
rightFooterEvenPages = Alias('rfe')
rff = Integer(allow_none=True)
rightFooterFirstPage = Alias('rff')
def __init__(self,
id=None,
lho=None,
lhe=None,
lhf=None,
cho=None,
che=None,
chf=None,
rho=None,
rhe=None,
rhf=None,
lfo=None,
lfe=None,
lff=None,
cfo=None,
cfe=None,
cff=None,
rfo=None,
rfe=None,
rff=None,
):
self.id = id
self.lho = lho
self.lhe = lhe
self.lhf = lhf
self.cho = cho
self.che = che
self.chf = chf
self.rho = rho
self.rhe = rhe
self.rhf = rhf
self.lfo = lfo
self.lfe = lfe
self.lff = lff
self.cfo = cfo
self.cfe = cfe
self.cff = cff
self.rfo = rfo
self.rfe = rfe
self.rff = rff

View File

@ -0,0 +1,51 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors import (
Bool,
Integer,
Typed,
Sequence
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.serialisable import Serialisable
class ChartsheetView(Serialisable):
tagname = "sheetView"
tabSelected = Bool(allow_none=True)
zoomScale = Integer(allow_none=True)
workbookViewId = Integer()
zoomToFit = Bool(allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ()
def __init__(self,
tabSelected=None,
zoomScale=None,
workbookViewId=0,
zoomToFit=None,
extLst=None,
):
self.tabSelected = tabSelected
self.zoomScale = zoomScale
self.workbookViewId = workbookViewId
self.zoomToFit = zoomToFit
class ChartsheetViewList(Serialisable):
tagname = "sheetViews"
sheetView = Sequence(expected_type=ChartsheetView, )
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ('sheetView',)
def __init__(self,
sheetView=None,
extLst=None,
):
if sheetView is None:
sheetView = [ChartsheetView()]
self.sheetView = sheetView

View File

@ -0,0 +1,4 @@
# Copyright (c) 2010-2022 openpyxl
from .comments import Comment

View File

@ -0,0 +1,21 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Sequence,
Alias
)
class AuthorList(Serialisable):
tagname = "authors"
author = Sequence(expected_type=str)
authors = Alias("author")
def __init__(self,
author=(),
):
self.author = author

View File

@ -0,0 +1,214 @@
# Copyright (c) 2010-2022 openpyxl
## Incomplete!
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Float,
Integer,
Set,
String,
Bool,
)
from openpyxl.descriptors.excel import Guid, ExtensionList
from openpyxl.descriptors.sequence import NestedSequence
from openpyxl.utils.indexed_list import IndexedList
from openpyxl.xml.constants import SHEET_MAIN_NS
from openpyxl.xml.functions import tostring
from openpyxl.cell.text import Text
#from openpyxl.worksheet.ole import ObjectAnchor
from .author import AuthorList
from .comments import Comment
from .shape_writer import ShapeWriter
class Properties(Serialisable):
locked = Bool(allow_none=True)
defaultSize = Bool(allow_none=True)
_print = Bool(allow_none=True)
disabled = Bool(allow_none=True)
uiObject = Bool(allow_none=True)
autoFill = Bool(allow_none=True)
autoLine = Bool(allow_none=True)
altText = String(allow_none=True)
textHAlign = Set(values=(['left', 'center', 'right', 'justify', 'distributed']))
textVAlign = Set(values=(['top', 'center', 'bottom', 'justify', 'distributed']))
lockText = Bool(allow_none=True)
justLastX = Bool(allow_none=True)
autoScale = Bool(allow_none=True)
rowHidden = Bool(allow_none=True)
colHidden = Bool(allow_none=True)
#anchor = Typed(expected_type=ObjectAnchor, )
__elements__ = ('anchor',)
def __init__(self,
locked=None,
defaultSize=None,
_print=None,
disabled=None,
uiObject=None,
autoFill=None,
autoLine=None,
altText=None,
textHAlign=None,
textVAlign=None,
lockText=None,
justLastX=None,
autoScale=None,
rowHidden=None,
colHidden=None,
anchor=None,
):
self.locked = locked
self.defaultSize = defaultSize
self._print = _print
self.disabled = disabled
self.uiObject = uiObject
self.autoFill = autoFill
self.autoLine = autoLine
self.altText = altText
self.textHAlign = textHAlign
self.textVAlign = textVAlign
self.lockText = lockText
self.justLastX = justLastX
self.autoScale = autoScale
self.rowHidden = rowHidden
self.colHidden = colHidden
self.anchor = anchor
class CommentRecord(Serialisable):
tagname = "comment"
ref = String()
authorId = Integer()
guid = Guid(allow_none=True)
shapeId = Integer(allow_none=True)
text = Typed(expected_type=Text)
commentPr = Typed(expected_type=Properties, allow_none=True)
author = String(allow_none=True)
__elements__ = ('text', 'commentPr')
__attrs__ = ('ref', 'authorId', 'guid', 'shapeId')
def __init__(self,
ref="",
authorId=0,
guid=None,
shapeId=0,
text=None,
commentPr=None,
author=None,
height=79,
width=144
):
self.ref = ref
self.authorId = authorId
self.guid = guid
self.shapeId = shapeId
if text is None:
text = Text()
self.text = text
self.commentPr = commentPr
self.author = author
self.height = height
self.width = width
@classmethod
def from_cell(cls, cell):
"""
Class method to convert cell comment
"""
comment = cell._comment
ref = cell.coordinate
self = cls(ref=ref, author=comment.author)
self.text.t = comment.content
self.height = comment.height
self.width = comment.width
return self
@property
def content(self):
"""
Remove all inline formatting and stuff
"""
return self.text.content
class CommentSheet(Serialisable):
tagname = "comments"
authors = Typed(expected_type=AuthorList)
commentList = NestedSequence(expected_type=CommentRecord, count=0)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
_id = None
_path = "/xl/comments/comment{0}.xml"
mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
_rel_type = "comments"
_rel_id = None
__elements__ = ('authors', 'commentList')
def __init__(self,
authors=None,
commentList=None,
extLst=None,
):
self.authors = authors
self.commentList = commentList
def to_tree(self):
tree = super(CommentSheet, self).to_tree()
tree.set("xmlns", SHEET_MAIN_NS)
return tree
@property
def comments(self):
"""
Return a dictionary of comments keyed by coord
"""
authors = self.authors.author
for c in self.commentList:
yield c.ref, Comment(c.content, authors[c.authorId], c.height, c.width)
@classmethod
def from_comments(cls, comments):
"""
Create a comment sheet from a list of comments for a particular worksheet
"""
authors = IndexedList()
# dedupe authors and get indexes
for comment in comments:
comment.authorId = authors.add(comment.author)
return cls(authors=AuthorList(authors), commentList=comments)
def write_shapes(self, vml=None):
"""
Create the VML for comments
"""
sw = ShapeWriter(self.comments)
return sw.write(vml)
@property
def path(self):
"""
Return path within the archive
"""
return self._path.format(self._id)

View File

@ -0,0 +1,62 @@
# Copyright (c) 2010-2022 openpyxl
class Comment(object):
_parent = None
def __init__(self, text, author, height=79, width=144):
self.content = text
self.author = author
self.height = height
self.width = width
@property
def parent(self):
return self._parent
def __eq__(self, other):
return (
self.content == other.content
and self.author == other.author
)
def __repr__(self):
return "Comment: {0} by {1}".format(self.content, self.author)
def __copy__(self):
"""Create a detached copy of this comment."""
clone = self.__class__(self.content, self.author, self.height, self.width)
return clone
def bind(self, cell):
"""
Bind comment to a particular cell
"""
if cell is not None and self._parent is not None and self._parent != cell:
fmt = "Comment already assigned to {0} in worksheet {1}. Cannot assign a comment to more than one cell"
raise AttributeError(fmt.format(cell.coordinate, cell.parent.title))
self._parent = cell
def unbind(self):
"""
Unbind a comment from a cell
"""
self._parent = None
@property
def text(self):
"""
Any comment text stripped of all formatting.
"""
return self.content
@text.setter
def text(self, value):
self.content = value

View File

@ -0,0 +1,116 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.xml.functions import (
Element,
SubElement,
tostring,
fromstring,
)
from openpyxl.utils import (
column_index_from_string,
coordinate_to_tuple,
)
vmlns = "urn:schemas-microsoft-com:vml"
officens = "urn:schemas-microsoft-com:office:office"
excelns = "urn:schemas-microsoft-com:office:excel"
class ShapeWriter(object):
"""
Create VML for comments
"""
vml = None
vml_path = None
def __init__(self, comments):
self.comments = comments
def add_comment_shapetype(self, root):
shape_layout = SubElement(root, "{%s}shapelayout" % officens,
{"{%s}ext" % vmlns: "edit"})
SubElement(shape_layout,
"{%s}idmap" % officens,
{"{%s}ext" % vmlns: "edit", "data": "1"})
shape_type = SubElement(root,
"{%s}shapetype" % vmlns,
{"id": "_x0000_t202",
"coordsize": "21600,21600",
"{%s}spt" % officens: "202",
"path": "m,l,21600r21600,l21600,xe"})
SubElement(shape_type, "{%s}stroke" % vmlns, {"joinstyle": "miter"})
SubElement(shape_type,
"{%s}path" % vmlns,
{"gradientshapeok": "t",
"{%s}connecttype" % officens: "rect"})
def add_comment_shape(self, root, idx, coord, height, width):
row, col = coordinate_to_tuple(coord)
row -= 1
col -= 1
shape = _shape_factory(row, col, height, width)
shape.set('id', "_x0000_s%04d" % idx)
root.append(shape)
def write(self, root):
if not hasattr(root, "findall"):
root = Element("xml")
# Remove any existing comment shapes
comments = root.findall("{%s}shape[@type='#_x0000_t202']" % vmlns)
for c in comments:
root.remove(c)
# check whether comments shape type already exists
shape_types = root.find("{%s}shapetype[@id='_x0000_t202']" % vmlns)
if not shape_types:
self.add_comment_shapetype(root)
for idx, (coord, comment) in enumerate(self.comments, 1026):
self.add_comment_shape(root, idx, coord, comment.height, comment.width)
return tostring(root)
def _shape_factory(row, column, height, width):
style = ("position:absolute; "
"margin-left:59.25pt;"
"margin-top:1.5pt;"
"width:{width}px;"
"height:{height}px;"
"z-index:1;"
"visibility:hidden").format(height=height,
width=width)
attrs = {
"type": "#_x0000_t202",
"style": style,
"fillcolor": "#ffffe1",
"{%s}insetmode" % officens: "auto"
}
shape = Element("{%s}shape" % vmlns, attrs)
SubElement(shape, "{%s}fill" % vmlns,
{"color2": "#ffffe1"})
SubElement(shape, "{%s}shadow" % vmlns,
{"color": "black", "obscured": "t"})
SubElement(shape, "{%s}path" % vmlns,
{"{%s}connecttype" % officens: "none"})
textbox = SubElement(shape, "{%s}textbox" % vmlns,
{"style": "mso-direction-alt:auto"})
SubElement(textbox, "div", {"style": "text-align:left"})
client_data = SubElement(shape, "{%s}ClientData" % excelns,
{"ObjectType": "Note"})
SubElement(client_data, "{%s}MoveWithCells" % excelns)
SubElement(client_data, "{%s}SizeWithCells" % excelns)
SubElement(client_data, "{%s}AutoFill" % excelns).text = "False"
SubElement(client_data, "{%s}Row" % excelns).text = str(row)
SubElement(client_data, "{%s}Column" % excelns).text = str(column)
return shape

View File

@ -0,0 +1,54 @@
# Copyright (c) 2010-2022 openpyxl
from .numbers import NUMERIC_TYPES
from .strings import safe_string
import warnings
from functools import wraps
import inspect
class DummyCode:
pass
# from https://github.com/tantale/deprecated/blob/master/deprecated/__init__.py
# with an enhancement to update docstrings of deprecated functions
string_types = (type(b''), type(u''))
def deprecated(reason):
if isinstance(reason, string_types):
def decorator(func1):
if inspect.isclass(func1):
fmt1 = "Call to deprecated class {name} ({reason})."
else:
fmt1 = "Call to deprecated function {name} ({reason})."
@wraps(func1)
def new_func1(*args, **kwargs):
#warnings.simplefilter('default', DeprecationWarning)
warnings.warn(
fmt1.format(name=func1.__name__, reason=reason),
category=DeprecationWarning,
stacklevel=2
)
return func1(*args, **kwargs)
# Enhance docstring with a deprecation note
deprecationNote = "\n\n.. note::\n Deprecated: " + reason
if new_func1.__doc__:
new_func1.__doc__ += deprecationNote
else:
new_func1.__doc__ = deprecationNote
return new_func1
return decorator
elif inspect.isclass(reason) or inspect.isfunction(reason):
raise TypeError("Reason for deprecation must be supplied")
else:
raise TypeError(repr(type(reason)))

View File

@ -0,0 +1,8 @@
# Copyright (c) 2010-2022 openpyxl
try:
from abc import ABC
except ImportError:
from abc import ABCMeta
ABC = ABCMeta('ABC', (object, ), {})

View File

@ -0,0 +1,43 @@
# Copyright (c) 2010-2022 openpyxl
from decimal import Decimal
NUMERIC_TYPES = (int, float, Decimal)
try:
import numpy
NUMPY = True
except ImportError:
NUMPY = False
if NUMPY:
NUMERIC_TYPES = NUMERIC_TYPES + (numpy.short,
numpy.ushort,
numpy.intc,
numpy.uintc,
numpy.int_,
numpy.uint,
numpy.longlong,
numpy.ulonglong,
numpy.half,
numpy.float16,
numpy.single,
numpy.double,
numpy.longdouble,
numpy.int8,
numpy.int16,
numpy.int32,
numpy.int64,
numpy.uint8,
numpy.uint16,
numpy.uint32,
numpy.uint64,
numpy.intp,
numpy.uintp,
numpy.float32,
numpy.float64,
numpy.bool_,
numpy.floating,
numpy.integer)

View File

@ -0,0 +1,17 @@
# Copyright (c) 2010-2022 openpyxl
"""
math.prod equivalent for < Python 3.8
"""
import functools
import operator
def product(sequence):
return functools.reduce(operator.mul, sequence)
try:
from math import prod
except ImportError:
prod = product

View File

@ -0,0 +1,40 @@
# Copyright (c) 2010-2022 openpyxl
import weakref
class Singleton(type):
"""
Singleton metaclass
Based on Python Cookbook 3rd Edition Recipe 9.13
Only one instance of a class can exist. Does not work with __slots__
"""
def __init__(self, *args, **kw):
super(Singleton, self).__init__(*args, **kw)
self.__instance = None
def __call__(self, *args, **kw):
if self.__instance is None:
self.__instance = super(Singleton, self).__call__(*args, **kw)
return self.__instance
class Cached(type):
"""
Caching metaclass
Child classes will only create new instances of themselves if
one doesn't already exist. Does not work with __slots__
"""
def __init__(self, *args, **kw):
super(Singleton, self).__init__(*args, **kw)
self.__cache = weakref.WeakValueDictionary()
def __call__(self, *args):
if args in self.__cache:
return self.__cache[args]
obj = super(Singleton, self).__call__(*args)
self.__cache[args] = obj
return obj

View File

@ -0,0 +1,25 @@
# Copyright (c) 2010-2022 openpyxl
from datetime import datetime
from math import isnan, isinf
import sys
VER = sys.version_info
from .numbers import NUMERIC_TYPES
def safe_string(value):
"""Safely and consistently format numeric values"""
if isinstance(value, NUMERIC_TYPES):
if isnan(value) or isinf(value):
value = ""
else:
value = "%.16g" % value
elif value is None:
value = "none"
elif isinstance(value, datetime):
value = value.isoformat()
elif not isinstance(value, str):
value = str(value)
return value

View File

@ -0,0 +1,57 @@
# Copyright (c) 2010-2022 openpyxl
from .base import *
from .sequence import Sequence
class MetaStrict(type):
def __new__(cls, clsname, bases, methods):
for k, v in methods.items():
if isinstance(v, Descriptor):
v.name = k
return type.__new__(cls, clsname, bases, methods)
class MetaSerialisable(type):
def __new__(cls, clsname, bases, methods):
attrs = []
nested = []
elements = []
namespaced = []
for k, v in methods.items():
if isinstance(v, Descriptor):
ns= getattr(v, 'namespace', None)
if ns:
namespaced.append((k, "{%s}%s" % (ns, k)))
if getattr(v, 'nested', False):
nested.append(k)
elements.append(k)
elif isinstance(v, Sequence):
elements.append(k)
elif isinstance(v, Typed):
if hasattr(v.expected_type, 'to_tree'):
elements.append(k)
else:
attrs.append(k)
else:
if not isinstance(v, Alias):
attrs.append(k)
if methods.get('__attrs__') is None:
methods['__attrs__'] = tuple(attrs)
methods['__namespaced__'] = tuple(namespaced)
if methods.get('__nested__') is None:
methods['__nested__'] = tuple(sorted(nested))
if methods.get('__elements__') is None:
methods['__elements__'] = tuple(sorted(elements))
return MetaStrict.__new__(cls, clsname, bases, methods)
Strict = MetaStrict('Strict', (object,), {})
_Serialiasable = MetaSerialisable('_Serialisable', (object,), {})
#del MetaStrict
#del MetaSerialisable

View File

@ -0,0 +1,268 @@
# Copyright (c) 2010-2022 openpyxl
"""
Based on Python Cookbook 3rd Edition, 8.13
http://chimera.labs.oreilly.com/books/1230000000393/ch08.html#_discussiuncion_130
"""
import datetime
import re
from openpyxl.utils.datetime import from_ISO8601
from .namespace import namespaced
class Descriptor(object):
def __init__(self, name=None, **kw):
self.name = name
for k, v in kw.items():
setattr(self, k, v)
def __set__(self, instance, value):
instance.__dict__[self.name] = value
class Typed(Descriptor):
"""Values must of a particular type"""
expected_type = type(None)
allow_none = False
nested = False
def __init__(self, *args, **kw):
super(Typed, self).__init__(*args, **kw)
self.__doc__ = "Values must be of type {0}".format(self.expected_type)
def __set__(self, instance, value):
if not isinstance(value, self.expected_type):
if (not self.allow_none
or (self.allow_none and value is not None)):
raise TypeError('expected ' + str(self.expected_type))
super(Typed, self).__set__(instance, value)
def __repr__(self):
return self.__doc__
def _convert(expected_type, value):
"""
Check value is of or can be converted to expected type.
"""
if not isinstance(value, expected_type):
try:
value = expected_type(value)
except:
raise TypeError('expected ' + str(expected_type))
return value
class Convertible(Typed):
"""Values must be convertible to a particular type"""
def __set__(self, instance, value):
if ((self.allow_none and value is not None)
or not self.allow_none):
value = _convert(self.expected_type, value)
super(Convertible, self).__set__(instance, value)
class Max(Convertible):
"""Values must be less than a `max` value"""
expected_type = float
allow_none = False
def __init__(self, **kw):
if 'max' not in kw and not hasattr(self, 'max'):
raise TypeError('missing max value')
super(Max, self).__init__(**kw)
def __set__(self, instance, value):
if ((self.allow_none and value is not None)
or not self.allow_none):
value = _convert(self.expected_type, value)
if value > self.max:
raise ValueError('Max value is {0}'.format(self.max))
super(Max, self).__set__(instance, value)
class Min(Convertible):
"""Values must be greater than a `min` value"""
expected_type = float
allow_none = False
def __init__(self, **kw):
if 'min' not in kw and not hasattr(self, 'min'):
raise TypeError('missing min value')
super(Min, self).__init__(**kw)
def __set__(self, instance, value):
if ((self.allow_none and value is not None)
or not self.allow_none):
value = _convert(self.expected_type, value)
if value < self.min:
raise ValueError('Min value is {0}'.format(self.min))
super(Min, self).__set__(instance, value)
class MinMax(Min, Max):
"""Values must be greater than `min` value and less than a `max` one"""
pass
class Set(Descriptor):
"""Value can only be from a set of know values"""
def __init__(self, name=None, **kw):
if not 'values' in kw:
raise TypeError("missing set of values")
kw['values'] = set(kw['values'])
super(Set, self).__init__(name, **kw)
self.__doc__ = "Value must be one of {0}".format(self.values)
def __set__(self, instance, value):
if value not in self.values:
raise ValueError(self.__doc__)
super(Set, self).__set__(instance, value)
class NoneSet(Set):
"""'none' will be treated as None"""
def __init__(self, name=None, **kw):
super(NoneSet, self).__init__(name, **kw)
self.values.add(None)
def __set__(self, instance, value):
if value == 'none':
value = None
super(NoneSet, self).__set__(instance, value)
class Integer(Convertible):
expected_type = int
class Float(Convertible):
expected_type = float
class Bool(Convertible):
expected_type = bool
def __set__(self, instance, value):
if isinstance(value, str):
if value in ('false', 'f', '0'):
value = False
super(Bool, self).__set__(instance, value)
class String(Typed):
expected_type = str
class Text(String, Convertible):
pass
class ASCII(Typed):
expected_type = bytes
class Tuple(Typed):
expected_type = tuple
class Length(Descriptor):
def __init__(self, name=None, **kw):
if "length" not in kw:
raise TypeError("value length must be supplied")
super(Length, self).__init__(**kw)
def __set__(self, instance, value):
if len(value) != self.length:
raise ValueError("Value must be length {0}".format(self.length))
super(Length, self).__set__(instance, value)
class Default(Typed):
"""
When called returns an instance of the expected type.
Additional default values can be passed in to the descriptor
"""
def __init__(self, name=None, **kw):
if "defaults" not in kw:
kw['defaults'] = {}
super(Default, self).__init__(**kw)
def __call__(self):
return self.expected_type()
class Alias(Descriptor):
"""
Aliases can be used when either the desired attribute name is not allowed
or confusing in Python (eg. "type") or a more descriptve name is desired
(eg. "underline" for "u")
"""
def __init__(self, alias):
self.alias = alias
def __set__(self, instance, value):
setattr(instance, self.alias, value)
def __get__(self, instance, cls):
return getattr(instance, self.alias)
class MatchPattern(Descriptor):
"""Values must match a regex pattern """
allow_none = False
def __init__(self, name=None, **kw):
if 'pattern' not in kw and not hasattr(self, 'pattern'):
raise TypeError('missing pattern value')
super(MatchPattern, self).__init__(name, **kw)
self.test_pattern = re.compile(self.pattern, re.VERBOSE)
def __set__(self, instance, value):
if value is None and not self.allow_none:
raise ValueError("Value must not be none")
if ((self.allow_none and value is not None)
or not self.allow_none):
if not self.test_pattern.match(value):
raise ValueError('Value does not match pattern {0}'.format(self.pattern))
super(MatchPattern, self).__set__(instance, value)
class DateTime(Typed):
expected_type = datetime.datetime
def __set__(self, instance, value):
if value is not None and isinstance(value, str):
try:
value = from_ISO8601(value)
except ValueError:
raise ValueError("Value must be ISO datetime format")
super(DateTime, self).__set__(instance, value)

View File

@ -0,0 +1,112 @@
#copyright openpyxl 2010-2018
"""
Excel specific descriptors
"""
from openpyxl.xml.constants import REL_NS
from openpyxl.compat import safe_string
from openpyxl.xml.functions import Element
from . import (
MatchPattern,
MinMax,
Integer,
String,
Sequence,
)
from .serialisable import Serialisable
class HexBinary(MatchPattern):
pattern = "[0-9a-fA-F]+$"
class UniversalMeasure(MatchPattern):
pattern = r"[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"
class TextPoint(MinMax):
"""
Size in hundredths of points.
In theory other units of measurement can be used but these are unbounded
"""
expected_type = int
min = -400000
max = 400000
Coordinate = Integer
class Percentage(MinMax):
pattern = r"((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%" # strict
min = -1000000
max = 1000000
def __set__(self, instance, value):
if isinstance(value, str) and "%" in value:
value = value.replace("%", "")
value = int(float(value) * 1000)
super(Percentage, self).__set__(instance, value)
class Extension(Serialisable):
uri = String()
def __init__(self,
uri=None,
):
self.uri = uri
class ExtensionList(Serialisable):
ext = Sequence(expected_type=Extension)
def __init__(self,
ext=(),
):
self.ext = ext
class Relation(String):
namespace = REL_NS
allow_none = True
class Base64Binary(MatchPattern):
# http://www.w3.org/TR/xmlschema11-2/#nt-Base64Binary
pattern = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$"
class Guid(MatchPattern):
# https://msdn.microsoft.com/en-us/library/dd946381(v=office.12).aspx
pattern = r"{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"
class CellRange(MatchPattern):
pattern = r"^[$]?([A-Za-z]{1,3})[$]?(\d+)(:[$]?([A-Za-z]{1,3})[$]?(\d+)?)?$|^[A-Za-z]{1,3}:[A-Za-z]{1,3}$"
allow_none = True
def __set__(self, instance, value):
if value is not None:
value = value.upper()
super(CellRange, self).__set__(instance, value)
def _explicit_none(tagname, value, namespace=None):
"""
Override serialisation because explicit none required
"""
if namespace is not None:
tagname = "{%s}%s" % (namespace, tagname)
return Element(tagname, val=safe_string(value))

View File

@ -0,0 +1,12 @@
# copyright openpyxl 2010-2015
def namespaced(obj, tagname, namespace=None):
"""
Utility to create a namespaced tag for an object
"""
namespace = getattr(obj, "namespace", None) or namespace
if namespace is not None:
tagname = "{%s}%s" % (namespace, tagname)
return tagname

View File

@ -0,0 +1,131 @@
#copyright openpyxl 2010-2015
"""
Generic serialisable classes
"""
from .base import (
Convertible,
Bool,
Descriptor,
NoneSet,
MinMax,
Set,
Float,
Integer,
String,
Text,
)
from .sequence import Sequence
from openpyxl.compat import safe_string
from openpyxl.xml.functions import Element, localname, whitespace
class Nested(Descriptor):
nested = True
attribute = "val"
def __set__(self, instance, value):
if hasattr(value, "tag"):
tag = localname(value)
if tag != self.name:
raise ValueError("Tag does not match attribute")
value = self.from_tree(value)
super(Nested, self).__set__(instance, value)
def from_tree(self, node):
return node.get(self.attribute)
def to_tree(self, tagname=None, value=None, namespace=None):
namespace = getattr(self, "namespace", namespace)
if value is not None:
if namespace is not None:
tagname = "{%s}%s" % (namespace, tagname)
value = safe_string(value)
return Element(tagname, {self.attribute:value})
class NestedValue(Nested, Convertible):
"""
Nested tag storing the value on the 'val' attribute
"""
pass
class NestedText(NestedValue):
"""
Represents any nested tag with the value as the contents of the tag
"""
def from_tree(self, node):
return node.text
def to_tree(self, tagname=None, value=None, namespace=None):
namespace = getattr(self, "namespace", namespace)
if value is not None:
if namespace is not None:
tagname = "{%s}%s" % (namespace, tagname)
el = Element(tagname)
el.text = safe_string(value)
whitespace(el)
return el
class NestedFloat(NestedValue, Float):
pass
class NestedInteger(NestedValue, Integer):
pass
class NestedString(NestedValue, String):
pass
class NestedBool(NestedValue, Bool):
def from_tree(self, node):
return node.get("val", True)
class NestedNoneSet(Nested, NoneSet):
pass
class NestedSet(Nested, Set):
pass
class NestedMinMax(Nested, MinMax):
pass
class EmptyTag(Nested, Bool):
"""
Boolean if a tag exists or not.
"""
def from_tree(self, node):
return True
def to_tree(self, tagname=None, value=None, namespace=None):
if value:
namespace = getattr(self, "namespace", namespace)
if namespace is not None:
tagname = "{%s}%s" % (namespace, tagname)
return Element(tagname)

View File

@ -0,0 +1,127 @@
# copyright openpyxl 2010-2015
from openpyxl.compat import safe_string
from openpyxl.xml.functions import Element
from openpyxl.utils.indexed_list import IndexedList
from .base import Descriptor, Alias, _convert
from .namespace import namespaced
class Sequence(Descriptor):
"""
A sequence (list or tuple) that may only contain objects of the declared
type
"""
expected_type = type(None)
seq_types = (list, tuple)
idx_base = 0
unique = False
def __set__(self, instance, seq):
if not isinstance(seq, self.seq_types):
raise TypeError("Value must be a sequence")
seq = [_convert(self.expected_type, value) for value in seq]
if self.unique:
seq = IndexedList(seq)
super(Sequence, self).__set__(instance, seq)
def to_tree(self, tagname, obj, namespace=None):
"""
Convert the sequence represented by the descriptor to an XML element
"""
for idx, v in enumerate(obj, self.idx_base):
if hasattr(v, "to_tree"):
el = v.to_tree(tagname, idx)
else:
tagname = namespaced(obj, tagname, namespace)
el = Element(tagname)
el.text = safe_string(v)
yield el
class ValueSequence(Sequence):
"""
A sequence of primitive types that are stored as a single attribute.
"val" is the default attribute
"""
attribute = "val"
def to_tree(self, tagname, obj, namespace=None):
tagname = namespaced(self, tagname, namespace)
for v in obj:
yield Element(tagname, {self.attribute:safe_string(v)})
def from_tree(self, node):
return node.get(self.attribute)
class NestedSequence(Sequence):
"""
Wrap a sequence in an containing object
"""
count = False
def to_tree(self, tagname, obj, namespace=None):
tagname = namespaced(self, tagname, namespace)
container = Element(tagname)
if self.count:
container.set('count', str(len(obj)))
for v in obj:
container.append(v.to_tree())
return container
def from_tree(self, node):
return [self.expected_type.from_tree(el) for el in node]
class MultiSequence(Sequence):
"""
Sequences can contain objects with different tags
"""
def __set__(self, instance, seq):
if not isinstance(seq, (tuple, list)):
raise ValueError("Value must be a sequence")
seq = list(seq)
Descriptor.__set__(self, instance, seq)
def to_tree(self, tagname, obj, namespace=None):
"""
Convert the sequence represented by the descriptor to an XML element
"""
for v in obj:
el = v.to_tree(namespace=namespace)
yield el
class MultiSequencePart(Alias):
"""
Allow a multisequence to be built up from parts
Excluded from the instance __elements__ or __attrs__ as is effectively an Alias
"""
def __init__(self, expected_type, store):
self.expected_type = expected_type
self.store = store
def __set__(self, instance, value):
value = _convert(self.expected_type, value)
instance.__dict__[self.store].append(value)
def __get__(self, instance, cls):
return self

View File

@ -0,0 +1,240 @@
# copyright openpyxl 2010-2015
from copy import copy
from keyword import kwlist
KEYWORDS = frozenset(kwlist)
from . import Descriptor
from . import _Serialiasable
from .sequence import (
Sequence,
NestedSequence,
MultiSequencePart,
)
from .namespace import namespaced
from openpyxl.compat import safe_string
from openpyxl.xml.functions import (
Element,
localname,
)
seq_types = (list, tuple)
class Serialisable(_Serialiasable):
"""
Objects can serialise to XML their attributes and child objects.
The following class attributes are created by the metaclass at runtime:
__attrs__ = attributes
__nested__ = single-valued child treated as an attribute
__elements__ = child elements
"""
__attrs__ = None
__nested__ = None
__elements__ = None
__namespaced__ = None
idx_base = 0
@property
def tagname(self):
raise(NotImplementedError)
namespace = None
@classmethod
def from_tree(cls, node):
"""
Create object from XML
"""
# strip known namespaces from attributes
attrib = dict(node.attrib)
for key, ns in cls.__namespaced__:
if ns in attrib:
attrib[key] = attrib[ns]
del attrib[ns]
# strip attributes with unknown namespaces
for key in list(attrib):
if key.startswith('{'):
del attrib[key]
elif key in KEYWORDS:
attrib["_" + key] = attrib[key]
del attrib[key]
elif "-" in key:
n = key.replace("-", "_")
attrib[n] = attrib[key]
del attrib[key]
if node.text and "attr_text" in cls.__attrs__:
attrib["attr_text"] = node.text
for el in node:
tag = localname(el)
if tag in KEYWORDS:
tag = "_" + tag
desc = getattr(cls, tag, None)
if desc is None or isinstance(desc, property):
continue
if hasattr(desc, 'from_tree'):
#descriptor manages conversion
obj = desc.from_tree(el)
else:
if hasattr(desc.expected_type, "from_tree"):
#complex type
obj = desc.expected_type.from_tree(el)
else:
#primitive
obj = el.text
if isinstance(desc, NestedSequence):
attrib[tag] = obj
elif isinstance(desc, Sequence):
attrib.setdefault(tag, [])
attrib[tag].append(obj)
elif isinstance(desc, MultiSequencePart):
attrib.setdefault(desc.store, [])
attrib[desc.store].append(obj)
else:
attrib[tag] = obj
return cls(**attrib)
def to_tree(self, tagname=None, idx=None, namespace=None):
if tagname is None:
tagname = self.tagname
# keywords have to be masked
if tagname.startswith("_"):
tagname = tagname[1:]
tagname = namespaced(self, tagname, namespace)
namespace = getattr(self, "namespace", namespace)
attrs = dict(self)
for key, ns in self.__namespaced__:
if key in attrs:
attrs[ns] = attrs[key]
del attrs[key]
el = Element(tagname, attrs)
if "attr_text" in self.__attrs__:
el.text = safe_string(getattr(self, "attr_text"))
for child_tag in self.__elements__:
desc = getattr(self.__class__, child_tag, None)
obj = getattr(self, child_tag)
if hasattr(desc, "namespace") and hasattr(obj, 'namespace'):
obj.namespace = desc.namespace
if isinstance(obj, seq_types):
if isinstance(desc, NestedSequence):
# wrap sequence in container
if not obj:
continue
nodes = [desc.to_tree(child_tag, obj, namespace)]
elif isinstance(desc, Sequence):
# sequence
desc.idx_base = self.idx_base
nodes = (desc.to_tree(child_tag, obj, namespace))
else: # property
nodes = (v.to_tree(child_tag, namespace) for v in obj)
for node in nodes:
el.append(node)
else:
if child_tag in self.__nested__:
node = desc.to_tree(child_tag, obj, namespace)
elif obj is None:
continue
else:
node = obj.to_tree(child_tag)
if node is not None:
el.append(node)
return el
def __iter__(self):
for attr in self.__attrs__:
value = getattr(self, attr)
if attr.startswith("_"):
attr = attr[1:]
elif attr != "attr_text" and "_" in attr:
desc = getattr(self.__class__, attr)
if getattr(desc, "hyphenated", False):
attr = attr.replace("_", "-")
if attr != "attr_text" and value is not None:
yield attr, safe_string(value)
def __eq__(self, other):
if not self.__class__ == other.__class__:
return False
elif not dict(self) == dict(other):
return False
for el in self.__elements__:
if getattr(self, el) != getattr(other, el):
return False
return True
def __ne__(self, other):
return not self == other
def __repr__(self):
s = u"<{0}.{1} object>\nParameters:".format(
self.__module__,
self.__class__.__name__
)
args = []
for k in self.__attrs__ + self.__elements__:
v = getattr(self, k)
if isinstance(v, Descriptor):
v = None
args.append(u"{0}={1}".format(k, repr(v)))
args = u", ".join(args)
return u"\n".join([s, args])
def __hash__(self):
fields = []
for attr in self.__attrs__ + self.__elements__:
val = getattr(self, attr)
if isinstance(val, list):
val = tuple(val)
fields.append(val)
return hash(tuple(fields))
def __add__(self, other):
if type(self) != type(other):
raise TypeError("Cannot combine instances of different types")
vals = {}
for attr in self.__attrs__:
vals[attr] = getattr(self, attr) or getattr(other, attr)
for el in self.__elements__:
a = getattr(self, el)
b = getattr(other, el)
if a and b:
vals[el] = a + b
else:
vals[el] = a or b
return self.__class__(**vals)
def __copy__(self):
# serialise to xml and back to avoid shallow copies
xml = self.to_tree(tagname="dummy")
cp = self.__class__.from_tree(xml)
# copy any non-persisted attributed
for k in self.__dict__:
if k not in self.__attrs__ + self.__elements__:
v = copy(getattr(self, k))
setattr(cp, k, v)
return cp

View File

@ -0,0 +1,18 @@
# Metaclass for mixing slots and descriptors
# From "Programming in Python 3" by Mark Summerfield Ch.8 p. 383
class AutoSlotProperties(type):
def __new__(mcl, classname, bases, dictionary):
slots = list(dictionary.get("__slots__", []))
for getter_name in [key for key in dictionary if key.startswith("get_")]:
name = getter_name
slots.append("__" + name)
getter = dictionary.pop(getter_name)
setter = dictionary.get(setter_name, None)
if (setter is not None
and isinstance(setter, collections.Callable)):
del dictionary[setter_name]
dictionary[name] = property(getter. setter)
dictionary["__slots__"] = tuple(slots)
return super().__new__(mcl, classname, bases, dictionary)

View File

@ -0,0 +1,4 @@
# Copyright (c) 2010-2022 openpyxl
from .drawing import Drawing

View File

@ -0,0 +1,435 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Alias,
Typed,
Integer,
Set,
MinMax,
)
from openpyxl.descriptors.excel import Percentage
from openpyxl.descriptors.nested import (
NestedNoneSet,
NestedValue,
NestedInteger,
EmptyTag,
)
from openpyxl.styles.colors import RGB
from openpyxl.xml.constants import DRAWING_NS
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
PRESET_COLORS = [
'aliceBlue', 'antiqueWhite', 'aqua', 'aquamarine',
'azure', 'beige', 'bisque', 'black', 'blanchedAlmond', 'blue',
'blueViolet', 'brown', 'burlyWood', 'cadetBlue', 'chartreuse',
'chocolate', 'coral', 'cornflowerBlue', 'cornsilk', 'crimson', 'cyan',
'darkBlue', 'darkCyan', 'darkGoldenrod', 'darkGray', 'darkGrey',
'darkGreen', 'darkKhaki', 'darkMagenta', 'darkOliveGreen', 'darkOrange',
'darkOrchid', 'darkRed', 'darkSalmon', 'darkSeaGreen', 'darkSlateBlue',
'darkSlateGray', 'darkSlateGrey', 'darkTurquoise', 'darkViolet',
'dkBlue', 'dkCyan', 'dkGoldenrod', 'dkGray', 'dkGrey', 'dkGreen',
'dkKhaki', 'dkMagenta', 'dkOliveGreen', 'dkOrange', 'dkOrchid', 'dkRed',
'dkSalmon', 'dkSeaGreen', 'dkSlateBlue', 'dkSlateGray', 'dkSlateGrey',
'dkTurquoise', 'dkViolet', 'deepPink', 'deepSkyBlue', 'dimGray',
'dimGrey', 'dodgerBlue', 'firebrick', 'floralWhite', 'forestGreen',
'fuchsia', 'gainsboro', 'ghostWhite', 'gold', 'goldenrod', 'gray',
'grey', 'green', 'greenYellow', 'honeydew', 'hotPink', 'indianRed',
'indigo', 'ivory', 'khaki', 'lavender', 'lavenderBlush', 'lawnGreen',
'lemonChiffon', 'lightBlue', 'lightCoral', 'lightCyan',
'lightGoldenrodYellow', 'lightGray', 'lightGrey', 'lightGreen',
'lightPink', 'lightSalmon', 'lightSeaGreen', 'lightSkyBlue',
'lightSlateGray', 'lightSlateGrey', 'lightSteelBlue', 'lightYellow',
'ltBlue', 'ltCoral', 'ltCyan', 'ltGoldenrodYellow', 'ltGray', 'ltGrey',
'ltGreen', 'ltPink', 'ltSalmon', 'ltSeaGreen', 'ltSkyBlue',
'ltSlateGray', 'ltSlateGrey', 'ltSteelBlue', 'ltYellow', 'lime',
'limeGreen', 'linen', 'magenta', 'maroon', 'medAquamarine', 'medBlue',
'medOrchid', 'medPurple', 'medSeaGreen', 'medSlateBlue',
'medSpringGreen', 'medTurquoise', 'medVioletRed', 'mediumAquamarine',
'mediumBlue', 'mediumOrchid', 'mediumPurple', 'mediumSeaGreen',
'mediumSlateBlue', 'mediumSpringGreen', 'mediumTurquoise',
'mediumVioletRed', 'midnightBlue', 'mintCream', 'mistyRose', 'moccasin',
'navajoWhite', 'navy', 'oldLace', 'olive', 'oliveDrab', 'orange',
'orangeRed', 'orchid', 'paleGoldenrod', 'paleGreen', 'paleTurquoise',
'paleVioletRed', 'papayaWhip', 'peachPuff', 'peru', 'pink', 'plum',
'powderBlue', 'purple', 'red', 'rosyBrown', 'royalBlue', 'saddleBrown',
'salmon', 'sandyBrown', 'seaGreen', 'seaShell', 'sienna', 'silver',
'skyBlue', 'slateBlue', 'slateGray', 'slateGrey', 'snow', 'springGreen',
'steelBlue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet',
'wheat', 'white', 'whiteSmoke', 'yellow', 'yellowGreen'
]
SCHEME_COLORS= ['bg1', 'tx1', 'bg2', 'tx2', 'accent1', 'accent2', 'accent3',
'accent4', 'accent5', 'accent6', 'hlink', 'folHlink', 'phClr', 'dk1', 'lt1',
'dk2', 'lt2'
]
class Transform(Serialisable):
pass
class SystemColor(Serialisable):
tagname = "sysClr"
namespace = DRAWING_NS
# color transform options
tint = NestedInteger(allow_none=True)
shade = NestedInteger(allow_none=True)
comp = Typed(expected_type=Transform, allow_none=True)
inv = Typed(expected_type=Transform, allow_none=True)
gray = Typed(expected_type=Transform, allow_none=True)
alpha = NestedInteger(allow_none=True)
alphaOff = NestedInteger(allow_none=True)
alphaMod = NestedInteger(allow_none=True)
hue = NestedInteger(allow_none=True)
hueOff = NestedInteger(allow_none=True)
hueMod = NestedInteger(allow_none=True)
sat = NestedInteger(allow_none=True)
satOff = NestedInteger(allow_none=True)
satMod = NestedInteger(allow_none=True)
lum = NestedInteger(allow_none=True)
lumOff = NestedInteger(allow_none=True)
lumMod = NestedInteger(allow_none=True)
red = NestedInteger(allow_none=True)
redOff = NestedInteger(allow_none=True)
redMod = NestedInteger(allow_none=True)
green = NestedInteger(allow_none=True)
greenOff = NestedInteger(allow_none=True)
greenMod = NestedInteger(allow_none=True)
blue = NestedInteger(allow_none=True)
blueOff = NestedInteger(allow_none=True)
blueMod = NestedInteger(allow_none=True)
gamma = Typed(expected_type=Transform, allow_none=True)
invGamma = Typed(expected_type=Transform, allow_none=True)
val = Set(values=( ['scrollBar', 'background', 'activeCaption',
'inactiveCaption', 'menu', 'window', 'windowFrame', 'menuText',
'windowText', 'captionText', 'activeBorder', 'inactiveBorder',
'appWorkspace', 'highlight', 'highlightText', 'btnFace', 'btnShadow',
'grayText', 'btnText', 'inactiveCaptionText', 'btnHighlight',
'3dDkShadow', '3dLight', 'infoText', 'infoBk', 'hotLight',
'gradientActiveCaption', 'gradientInactiveCaption', 'menuHighlight',
'menuBar'] )
)
lastClr = RGB(allow_none=True)
__elements__ = ('tint', 'shade', 'comp', 'inv', 'gray', "alpha",
"alphaOff", "alphaMod", "hue", "hueOff", "hueMod", "hueOff", "sat",
"satOff", "satMod", "lum", "lumOff", "lumMod", "red", "redOff", "redMod",
"green", "greenOff", "greenMod", "blue", "blueOff", "blueMod", "gamma",
"invGamma")
def __init__(self,
val="windowText",
lastClr=None,
tint=None,
shade=None,
comp=None,
inv=None,
gray=None,
alpha=None,
alphaOff=None,
alphaMod=None,
hue=None,
hueOff=None,
hueMod=None,
sat=None,
satOff=None,
satMod=None,
lum=None,
lumOff=None,
lumMod=None,
red=None,
redOff=None,
redMod=None,
green=None,
greenOff=None,
greenMod=None,
blue=None,
blueOff=None,
blueMod=None,
gamma=None,
invGamma=None
):
self.val = val
self.lastClr = lastClr
self.tint = tint
self.shade = shade
self.comp = comp
self.inv = inv
self.gray = gray
self.alpha = alpha
self.alphaOff = alphaOff
self.alphaMod = alphaMod
self.hue = hue
self.hueOff = hueOff
self.hueMod = hueMod
self.sat = sat
self.satOff = satOff
self.satMod = satMod
self.lum = lum
self.lumOff = lumOff
self.lumMod = lumMod
self.red = red
self.redOff = redOff
self.redMod = redMod
self.green = green
self.greenOff = greenOff
self.greenMod = greenMod
self.blue = blue
self.blueOff = blueOff
self.blueMod = blueMod
self.gamma = gamma
self.invGamma = invGamma
class HSLColor(Serialisable):
tagname = "hslClr"
hue = Integer()
sat = MinMax(min=0, max=100)
lum = MinMax(min=0, max=100)
#TODO add color transform options
def __init__(self,
hue=None,
sat=None,
lum=None,
):
self.hue = hue
self.sat = sat
self.lum = lum
class RGBPercent(Serialisable):
tagname = "rgbClr"
r = MinMax(min=0, max=100)
g = MinMax(min=0, max=100)
b = MinMax(min=0, max=100)
#TODO add color transform options
def __init__(self,
r=None,
g=None,
b=None,
):
self.r = r
self.g = g
self.b = b
class SchemeColor(Serialisable):
tagname = "schemeClr"
namespace = DRAWING_NS
tint = NestedInteger(allow_none=True)
shade = NestedInteger(allow_none=True)
comp = EmptyTag(allow_none=True)
inv = NestedInteger(allow_none=True)
gray = NestedInteger(allow_none=True)
alpha = NestedInteger(allow_none=True)
alphaOff = NestedInteger(allow_none=True)
alphaMod = NestedInteger(allow_none=True)
hue = NestedInteger(allow_none=True)
hueOff = NestedInteger(allow_none=True)
hueMod = NestedInteger(allow_none=True)
sat = NestedInteger(allow_none=True)
satOff = NestedInteger(allow_none=True)
satMod = NestedInteger(allow_none=True)
lum = NestedInteger(allow_none=True)
lumOff = NestedInteger(allow_none=True)
lumMod = NestedInteger(allow_none=True)
red = NestedInteger(allow_none=True)
redOff = NestedInteger(allow_none=True)
redMod = NestedInteger(allow_none=True)
green = NestedInteger(allow_none=True)
greenOff = NestedInteger(allow_none=True)
greenMod = NestedInteger(allow_none=True)
blue = NestedInteger(allow_none=True)
blueOff = NestedInteger(allow_none=True)
blueMod = NestedInteger(allow_none=True)
gamma = EmptyTag(allow_none=True)
invGamma = EmptyTag(allow_none=True)
val = Set(values=(['bg1', 'tx1', 'bg2', 'tx2', 'accent1', 'accent2',
'accent3', 'accent4', 'accent5', 'accent6', 'hlink', 'folHlink', 'phClr',
'dk1', 'lt1', 'dk2', 'lt2']))
__elements__ = ('tint', 'shade', 'comp', 'inv', 'gray', 'alpha',
'alphaOff', 'alphaMod', 'hue', 'hueOff', 'hueMod', 'sat', 'satOff',
'satMod', 'lum', 'lumMod', 'lumOff', 'red', 'redOff', 'redMod', 'green',
'greenOff', 'greenMod', 'blue', 'blueOff', 'blueMod', 'gamma',
'invGamma')
def __init__(self,
tint=None,
shade=None,
comp=None,
inv=None,
gray=None,
alpha=None,
alphaOff=None,
alphaMod=None,
hue=None,
hueOff=None,
hueMod=None,
sat=None,
satOff=None,
satMod=None,
lum=None,
lumOff=None,
lumMod=None,
red=None,
redOff=None,
redMod=None,
green=None,
greenOff=None,
greenMod=None,
blue=None,
blueOff=None,
blueMod=None,
gamma=None,
invGamma=None,
val=None,
):
self.tint = tint
self.shade = shade
self.comp = comp
self.inv = inv
self.gray = gray
self.alpha = alpha
self.alphaOff = alphaOff
self.alphaMod = alphaMod
self.hue = hue
self.hueOff = hueOff
self.hueMod = hueMod
self.sat = sat
self.satOff = satOff
self.satMod = satMod
self.lum = lum
self.lumOff = lumOff
self.lumMod = lumMod
self.red = red
self.redOff = redOff
self.redMod = redMod
self.green = green
self.greenOff = greenOff
self.greenMod = greenMod
self.blue = blue
self.blueOff = blueOff
self.blueMod = blueMod
self.gamma = gamma
self.invGamma = invGamma
self.val = val
class ColorChoice(Serialisable):
tagname = "colorChoice"
namespace = DRAWING_NS
scrgbClr = Typed(expected_type=RGBPercent, allow_none=True)
RGBPercent = Alias('scrgbClr')
srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform
RGB = Alias('srgbClr')
hslClr = Typed(expected_type=HSLColor, allow_none=True)
sysClr = Typed(expected_type=SystemColor, allow_none=True)
schemeClr = Typed(expected_type=SchemeColor, allow_none=True)
prstClr = NestedNoneSet(values=PRESET_COLORS)
__elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
def __init__(self,
scrgbClr=None,
srgbClr=None,
hslClr=None,
sysClr=None,
schemeClr=None,
prstClr=None,
):
self.scrgbClr = scrgbClr
self.srgbClr = srgbClr
self.hslClr = hslClr
self.sysClr = sysClr
self.schemeClr = schemeClr
self.prstClr = prstClr
_COLOR_SET = ('dk1', 'lt1', 'dk2', 'lt2', 'accent1', 'accent2', 'accent3',
'accent4', 'accent5', 'accent6', 'hlink', 'folHlink')
class ColorMapping(Serialisable):
tagname = "clrMapOvr"
bg1 = Set(values=_COLOR_SET)
tx1 = Set(values=_COLOR_SET)
bg2 = Set(values=_COLOR_SET)
tx2 = Set(values=_COLOR_SET)
accent1 = Set(values=_COLOR_SET)
accent2 = Set(values=_COLOR_SET)
accent3 = Set(values=_COLOR_SET)
accent4 = Set(values=_COLOR_SET)
accent5 = Set(values=_COLOR_SET)
accent6 = Set(values=_COLOR_SET)
hlink = Set(values=_COLOR_SET)
folHlink = Set(values=_COLOR_SET)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
def __init__(self,
bg1="lt1",
tx1="dk1",
bg2="lt2",
tx2="dk2",
accent1="accent1",
accent2="accent2",
accent3="accent3",
accent4="accent4",
accent5="accent5",
accent6="accent6",
hlink="hlink",
folHlink="folHlink",
extLst=None,
):
self.bg1 = bg1
self.tx1 = tx1
self.bg2 = bg2
self.tx2 = tx2
self.accent1 = accent1
self.accent2 = accent2
self.accent3 = accent3
self.accent4 = accent4
self.accent5 = accent5
self.accent6 = accent6
self.hlink = hlink
self.folHlink = folHlink
self.extLst = extLst
class ColorChoiceDescriptor(Typed):
"""
Objects can choose from 7 different kinds of color system.
Assume RGBHex if a string is passed in.
"""
expected_type = ColorChoice
allow_none = True
def __set__(self, instance, value):
if isinstance(value, str):
value = ColorChoice(srgbClr=value)
else:
if hasattr(self, "namespace") and value is not None:
value.namespace = self.namespace
super(ColorChoiceDescriptor, self).__set__(instance, value)

View File

@ -0,0 +1,144 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Bool,
Integer,
String,
Alias,
)
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
from openpyxl.chart.shapes import GraphicalProperties
from openpyxl.chart.text import RichText
from .properties import (
NonVisualDrawingProps,
NonVisualDrawingShapeProps,
)
from .geometry import ShapeStyle
class Connection(Serialisable):
id = Integer()
idx = Integer()
def __init__(self,
id=None,
idx=None,
):
self.id = id
self.idx = idx
class ConnectorLocking(Serialisable):
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
def __init__(self,
extLst=None,
):
self.extLst = extLst
class NonVisualConnectorProperties(Serialisable):
cxnSpLocks = Typed(expected_type=ConnectorLocking, allow_none=True)
stCxn = Typed(expected_type=Connection, allow_none=True)
endCxn = Typed(expected_type=Connection, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
def __init__(self,
cxnSpLocks=None,
stCxn=None,
endCxn=None,
extLst=None,
):
self.cxnSpLocks = cxnSpLocks
self.stCxn = stCxn
self.endCxn = endCxn
self.extLst = extLst
class ConnectorNonVisual(Serialisable):
cNvPr = Typed(expected_type=NonVisualDrawingProps, )
cNvCxnSpPr = Typed(expected_type=NonVisualConnectorProperties, )
__elements__ = ("cNvPr", "cNvCxnSpPr",)
def __init__(self,
cNvPr=None,
cNvCxnSpPr=None,
):
self.cNvPr = cNvPr
self.cNvCxnSpPr = cNvCxnSpPr
class ConnectorShape(Serialisable):
tagname = "cxnSp"
nvCxnSpPr = Typed(expected_type=ConnectorNonVisual)
spPr = Typed(expected_type=GraphicalProperties)
style = Typed(expected_type=ShapeStyle, allow_none=True)
macro = String(allow_none=True)
fPublished = Bool(allow_none=True)
def __init__(self,
nvCxnSpPr=None,
spPr=None,
style=None,
macro=None,
fPublished=None,
):
self.nvCxnSpPr = nvCxnSpPr
self.spPr = spPr
self.style = style
self.macro = macro
self.fPublished = fPublished
class ShapeMeta(Serialisable):
tagname = "nvSpPr"
cNvPr = Typed(expected_type=NonVisualDrawingProps)
cNvSpPr = Typed(expected_type=NonVisualDrawingShapeProps)
def __init__(self, cNvPr=None, cNvSpPr=None):
self.cNvPr = cNvPr
self.cNvSpPr = cNvSpPr
class Shape(Serialisable):
macro = String(allow_none=True)
textlink = String(allow_none=True)
fPublished = Bool(allow_none=True)
fLocksText = Bool(allow_none=True)
nvSpPr = Typed(expected_type=ShapeMeta, allow_none=True)
meta = Alias("nvSpPr")
spPr = Typed(expected_type=GraphicalProperties)
graphicalProperties = Alias("spPr")
style = Typed(expected_type=ShapeStyle, allow_none=True)
txBody = Typed(expected_type=RichText, allow_none=True)
def __init__(self,
macro=None,
textlink=None,
fPublished=None,
fLocksText=None,
nvSpPr=None,
spPr=None,
style=None,
txBody=None,
):
self.macro = macro
self.textlink = textlink
self.fPublished = fPublished
self.fLocksText = fLocksText
self.nvSpPr = nvSpPr
self.spPr = spPr
self.style = style
self.txBody = txBody

View File

@ -0,0 +1,102 @@
from __future__ import division
# Copyright (c) 2010-2022 openpyxl
import math
from openpyxl.compat import deprecated
from openpyxl.styles.colors import Color, BLACK, WHITE
from openpyxl.utils.units import (
pixels_to_EMU,
EMU_to_pixels,
short_color,
)
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 = 21 # default in px
self._height = 192 #default in px
self.resize_proportional = False
self.rotation = 0
self.anchortype = "absolute"
self.anchorcol = 0 # left cell
self.anchorrow = 0 # top row
@property
def width(self):
return self._width
@width.setter
def width(self, w):
if self.resize_proportional and w:
ratio = self._height / self._width
self._height = round(ratio * w)
self._width = w
@property
def height(self):
return self._height
@height.setter
def height(self, h):
if self.resize_proportional and h:
ratio = self._width / self._height
self._width = round(ratio * h)
self._height = h
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 = w
else:
self._width = math.ceil(yratio * self._width)
self._height = h
@deprecated("Private method used when serialising")
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))
@property
def anchor(self):
from .spreadsheet_drawing import (
OneCellAnchor,
TwoCellAnchor,
AbsoluteAnchor)
if self.anchortype == "absolute":
anchor = AbsoluteAnchor()
anchor.pos.x = pixels_to_EMU(self.left)
anchor.pos.y = pixels_to_EMU(self.top)
elif self.anchortype == "oneCell":
anchor = OneCellAnchor()
anchor._from.col = self.anchorcol
anchor._from.row = self.anchorrow
anchor.ext.width = pixels_to_EMU(self._width)
anchor.ext.height = pixels_to_EMU(self._height)
return anchor

View File

@ -0,0 +1,409 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
String,
Set,
Bool,
Integer,
NoneSet,
Float,
)
from .colors import ColorChoice
class TintEffect(Serialisable):
tagname = "tint"
hue = Integer()
amt = Integer()
def __init__(self,
hue=0,
amt=0,
):
self.hue = hue
self.amt = amt
class LuminanceEffect(Serialisable):
tagname = "lum"
bright = Integer() #Pct ?
contrast = Integer() #Pct#
def __init__(self,
bright=0,
contrast=0,
):
self.bright = bright
self.contrast = contrast
class HSLEffect(Serialisable):
hue = Integer()
sat = Integer()
lum = Integer()
def __init__(self,
hue=None,
sat=None,
lum=None,
):
self.hue = hue
self.sat = sat
self.lum = lum
class GrayscaleEffect(Serialisable):
tagname = "grayscl"
class FillOverlayEffect(Serialisable):
blend = Set(values=(['over', 'mult', 'screen', 'darken', 'lighten']))
def __init__(self,
blend=None,
):
self.blend = blend
class DuotoneEffect(Serialisable):
pass
class ColorReplaceEffect(Serialisable):
pass
class Color(Serialisable):
pass
class ColorChangeEffect(Serialisable):
useA = Bool(allow_none=True)
clrFrom = Typed(expected_type=Color, )
clrTo = Typed(expected_type=Color, )
def __init__(self,
useA=None,
clrFrom=None,
clrTo=None,
):
self.useA = useA
self.clrFrom = clrFrom
self.clrTo = clrTo
class BlurEffect(Serialisable):
rad = Float()
grow = Bool(allow_none=True)
def __init__(self,
rad=None,
grow=None,
):
self.rad = rad
self.grow = grow
class BiLevelEffect(Serialisable):
thresh = Integer()
def __init__(self,
thresh=None,
):
self.thresh = thresh
class AlphaReplaceEffect(Serialisable):
a = Integer()
def __init__(self,
a=None,
):
self.a = a
class AlphaModulateFixedEffect(Serialisable):
amt = Integer()
def __init__(self,
amt=None,
):
self.amt = amt
class EffectContainer(Serialisable):
type = Set(values=(['sib', 'tree']))
name = String(allow_none=True)
def __init__(self,
type=None,
name=None,
):
self.type = type
self.name = name
class AlphaModulateEffect(Serialisable):
cont = Typed(expected_type=EffectContainer, )
def __init__(self,
cont=None,
):
self.cont = cont
class AlphaInverseEffect(Serialisable):
pass
class AlphaFloorEffect(Serialisable):
pass
class AlphaCeilingEffect(Serialisable):
pass
class AlphaBiLevelEffect(Serialisable):
thresh = Integer()
def __init__(self,
thresh=None,
):
self.thresh = thresh
class GlowEffect(ColorChoice):
rad = Float()
# uses element group EG_ColorChoice
scrgbClr = ColorChoice.scrgbClr
srgbClr = ColorChoice.srgbClr
hslClr = ColorChoice.hslClr
sysClr = ColorChoice.sysClr
schemeClr = ColorChoice.schemeClr
prstClr = ColorChoice.prstClr
__elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
def __init__(self,
rad=None,
**kw
):
self.rad = rad
super(GlowEffect, self).__init__(**kw)
class InnerShadowEffect(ColorChoice):
blurRad = Float()
dist = Float()
dir = Integer()
# uses element group EG_ColorChoice
scrgbClr = ColorChoice.scrgbClr
srgbClr = ColorChoice.srgbClr
hslClr = ColorChoice.hslClr
sysClr = ColorChoice.sysClr
schemeClr = ColorChoice.schemeClr
prstClr = ColorChoice.prstClr
__elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
def __init__(self,
blurRad=None,
dist=None,
dir=None,
**kw
):
self.blurRad = blurRad
self.dist = dist
self.dir = dir
super(InnerShadowEffect, self).__init__(**kw)
class OuterShadow(ColorChoice):
tagname = "outerShdw"
blurRad = Float(allow_none=True)
dist = Float(allow_none=True)
dir = Integer(allow_none=True)
sx = Integer(allow_none=True)
sy = Integer(allow_none=True)
kx = Integer(allow_none=True)
ky = Integer(allow_none=True)
algn = Set(values=['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br'])
rotWithShape = Bool(allow_none=True)
# uses element group EG_ColorChoice
scrgbClr = ColorChoice.scrgbClr
srgbClr = ColorChoice.srgbClr
hslClr = ColorChoice.hslClr
sysClr = ColorChoice.sysClr
schemeClr = ColorChoice.schemeClr
prstClr = ColorChoice.prstClr
__elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
def __init__(self,
blurRad=None,
dist=None,
dir=None,
sx=None,
sy=None,
kx=None,
ky=None,
algn=None,
rotWithShape=None,
**kw
):
self.blurRad = blurRad
self.dist = dist
self.dir = dir
self.sx = sx
self.sy = sy
self.kx = kx
self.ky = ky
self.algn = algn
self.rotWithShape = rotWithShape
super(OuterShadow, self).__init__(**kw)
class PresetShadowEffect(ColorChoice):
prst = Set(values=(['shdw1', 'shdw2', 'shdw3', 'shdw4', 'shdw5', 'shdw6',
'shdw7', 'shdw8', 'shdw9', 'shdw10', 'shdw11', 'shdw12', 'shdw13',
'shdw14', 'shdw15', 'shdw16', 'shdw17', 'shdw18', 'shdw19', 'shdw20']))
dist = Float()
dir = Integer()
# uses element group EG_ColorChoice
scrgbClr = ColorChoice.scrgbClr
srgbClr = ColorChoice.srgbClr
hslClr = ColorChoice.hslClr
sysClr = ColorChoice.sysClr
schemeClr = ColorChoice.schemeClr
prstClr = ColorChoice.prstClr
__elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
def __init__(self,
prst=None,
dist=None,
dir=None,
**kw
):
self.prst = prst
self.dist = dist
self.dir = dir
super(PresetShadowEffect, self).__init__(**kw)
class ReflectionEffect(Serialisable):
blurRad = Float()
stA = Integer()
stPos = Integer()
endA = Integer()
endPos = Integer()
dist = Float()
dir = Integer()
fadeDir = Integer()
sx = Integer()
sy = Integer()
kx = Integer()
ky = Integer()
algn = Set(values=(['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br']))
rotWithShape = Bool(allow_none=True)
def __init__(self,
blurRad=None,
stA=None,
stPos=None,
endA=None,
endPos=None,
dist=None,
dir=None,
fadeDir=None,
sx=None,
sy=None,
kx=None,
ky=None,
algn=None,
rotWithShape=None,
):
self.blurRad = blurRad
self.stA = stA
self.stPos = stPos
self.endA = endA
self.endPos = endPos
self.dist = dist
self.dir = dir
self.fadeDir = fadeDir
self.sx = sx
self.sy = sy
self.kx = kx
self.ky = ky
self.algn = algn
self.rotWithShape = rotWithShape
class SoftEdgesEffect(Serialisable):
rad = Float()
def __init__(self,
rad=None,
):
self.rad = rad
class EffectList(Serialisable):
blur = Typed(expected_type=BlurEffect, allow_none=True)
fillOverlay = Typed(expected_type=FillOverlayEffect, allow_none=True)
glow = Typed(expected_type=GlowEffect, allow_none=True)
innerShdw = Typed(expected_type=InnerShadowEffect, allow_none=True)
outerShdw = Typed(expected_type=OuterShadow, allow_none=True)
prstShdw = Typed(expected_type=PresetShadowEffect, allow_none=True)
reflection = Typed(expected_type=ReflectionEffect, allow_none=True)
softEdge = Typed(expected_type=SoftEdgesEffect, allow_none=True)
__elements__ = ('blur', 'fillOverlay', 'glow', 'innerShdw', 'outerShdw',
'prstShdw', 'reflection', 'softEdge')
def __init__(self,
blur=None,
fillOverlay=None,
glow=None,
innerShdw=None,
outerShdw=None,
prstShdw=None,
reflection=None,
softEdge=None,
):
self.blur = blur
self.fillOverlay = fillOverlay
self.glow = glow
self.innerShdw = innerShdw
self.outerShdw = outerShdw
self.prstShdw = prstShdw
self.reflection = reflection
self.softEdge = softEdge

View File

@ -0,0 +1,410 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Alias,
Bool,
Integer,
Set,
NoneSet,
Typed,
MinMax,
Sequence,
)
from openpyxl.descriptors.excel import (
Relation,
Percentage,
)
from openpyxl.descriptors.nested import NestedNoneSet, NestedValue
from openpyxl.descriptors.sequence import NestedSequence
from openpyxl.xml.constants import DRAWING_NS
from .colors import (
ColorChoice,
HSLColor,
SystemColor,
SchemeColor,
RGBPercent,
PRESET_COLORS,
)
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
from .effect import *
"""
Fill elements from drawing main schema
"""
class PatternFillProperties(Serialisable):
tagname = "pattFill"
namespace = DRAWING_NS
prst = NoneSet(values=(['pct5', 'pct10', 'pct20', 'pct25', 'pct30', 'pct40',
'pct50', 'pct60', 'pct70', 'pct75', 'pct80', 'pct90', 'horz', 'vert',
'ltHorz', 'ltVert', 'dkHorz', 'dkVert', 'narHorz', 'narVert', 'dashHorz',
'dashVert', 'cross', 'dnDiag', 'upDiag', 'ltDnDiag', 'ltUpDiag',
'dkDnDiag', 'dkUpDiag', 'wdDnDiag', 'wdUpDiag', 'dashDnDiag',
'dashUpDiag', 'diagCross', 'smCheck', 'lgCheck', 'smGrid', 'lgGrid',
'dotGrid', 'smConfetti', 'lgConfetti', 'horzBrick', 'diagBrick',
'solidDmnd', 'openDmnd', 'dotDmnd', 'plaid', 'sphere', 'weave', 'divot',
'shingle', 'wave', 'trellis', 'zigZag']))
preset = Alias("prst")
fgClr = Typed(expected_type=ColorChoice, allow_none=True)
foreground = Alias("fgClr")
bgClr = Typed(expected_type=ColorChoice, allow_none=True)
background = Alias("bgClr")
__elements__ = ("fgClr", "bgClr")
def __init__(self,
prst=None,
fgClr=None,
bgClr=None,
):
self.prst = prst
self.fgClr = fgClr
self.bgClr = bgClr
class RelativeRect(Serialisable):
tagname = "rect"
namespace = DRAWING_NS
l = Percentage(allow_none=True)
left = Alias('l')
t = Percentage(allow_none=True)
top = Alias('t')
r = Percentage(allow_none=True)
right = Alias('r')
b = Percentage(allow_none=True)
bottom = Alias('b')
def __init__(self,
l=None,
t=None,
r=None,
b=None,
):
self.l = l
self.t = t
self.r = r
self.b = b
class StretchInfoProperties(Serialisable):
tagname = "stretch"
namespace = DRAWING_NS
fillRect = Typed(expected_type=RelativeRect, allow_none=True)
def __init__(self,
fillRect=RelativeRect(),
):
self.fillRect = fillRect
class GradientStop(Serialisable):
tagname = "gs"
namespace = DRAWING_NS
pos = MinMax(min=0, max=100000, allow_none=True)
# Color Choice Group
scrgbClr = Typed(expected_type=RGBPercent, allow_none=True)
RGBPercent = Alias('scrgbClr')
srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform
RGB = Alias('srgbClr')
hslClr = Typed(expected_type=HSLColor, allow_none=True)
sysClr = Typed(expected_type=SystemColor, allow_none=True)
schemeClr = Typed(expected_type=SchemeColor, allow_none=True)
prstClr = NestedNoneSet(values=PRESET_COLORS)
__elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
def __init__(self,
pos=None,
scrgbClr=None,
srgbClr=None,
hslClr=None,
sysClr=None,
schemeClr=None,
prstClr=None,
):
if pos is None:
pos = 0
self.pos = pos
self.scrgbClr = scrgbClr
self.srgbClr = srgbClr
self.hslClr = hslClr
self.sysClr = sysClr
self.schemeClr = schemeClr
self.prstClr = prstClr
class LinearShadeProperties(Serialisable):
tagname = "lin"
namespace = DRAWING_NS
ang = Integer()
scaled = Bool(allow_none=True)
def __init__(self,
ang=None,
scaled=None,
):
self.ang = ang
self.scaled = scaled
class PathShadeProperties(Serialisable):
tagname = "path"
namespace = DRAWING_NS
path = Set(values=(['shape', 'circle', 'rect']))
fillToRect = Typed(expected_type=RelativeRect, allow_none=True)
def __init__(self,
path=None,
fillToRect=None,
):
self.path = path
self.fillToRect = fillToRect
class GradientFillProperties(Serialisable):
tagname = "gradFill"
namespace = DRAWING_NS
flip = NoneSet(values=(['x', 'y', 'xy']))
rotWithShape = Bool(allow_none=True)
gsLst = NestedSequence(expected_type=GradientStop, count=False)
stop_list = Alias("gsLst")
lin = Typed(expected_type=LinearShadeProperties, allow_none=True)
linear = Alias("lin")
path = Typed(expected_type=PathShadeProperties, allow_none=True)
tileRect = Typed(expected_type=RelativeRect, allow_none=True)
__elements__ = ('gsLst', 'lin', 'path', 'tileRect')
def __init__(self,
flip=None,
rotWithShape=None,
gsLst=(),
lin=None,
path=None,
tileRect=None,
):
self.flip = flip
self.rotWithShape = rotWithShape
self.gsLst = gsLst
self.lin = lin
self.path = path
self.tileRect = tileRect
class SolidColorFillProperties(Serialisable):
tagname = "solidFill"
# uses element group EG_ColorChoice
scrgbClr = Typed(expected_type=RGBPercent, allow_none=True)
RGBPercent = Alias('scrgbClr')
srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform
RGB = Alias('srgbClr')
hslClr = Typed(expected_type=HSLColor, allow_none=True)
sysClr = Typed(expected_type=SystemColor, allow_none=True)
schemeClr = Typed(expected_type=SchemeColor, allow_none=True)
prstClr = NestedNoneSet(values=PRESET_COLORS)
__elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
def __init__(self,
scrgbClr=None,
srgbClr=None,
hslClr=None,
sysClr=None,
schemeClr=None,
prstClr=None,
):
self.scrgbClr = scrgbClr
self.srgbClr = srgbClr
self.hslClr = hslClr
self.sysClr = sysClr
self.schemeClr = schemeClr
self.prstClr = prstClr
class Blip(Serialisable):
tagname = "blip"
namespace = DRAWING_NS
#Using attribute groupAG_Blob
cstate = NoneSet(values=(['email', 'screen', 'print', 'hqprint']))
embed = Relation() #rId
link = Relation() #hyperlink
noGrp = Bool(allow_none=True)
noSelect = Bool(allow_none=True)
noRot = Bool(allow_none=True)
noChangeAspect = Bool(allow_none=True)
noMove = Bool(allow_none=True)
noResize = Bool(allow_none=True)
noEditPoints = Bool(allow_none=True)
noAdjustHandles = Bool(allow_none=True)
noChangeArrowheads = Bool(allow_none=True)
noChangeShapeType = Bool(allow_none=True)
# some elements are choice
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
alphaBiLevel = Typed(expected_type=AlphaBiLevelEffect, allow_none=True)
alphaCeiling = Typed(expected_type=AlphaCeilingEffect, allow_none=True)
alphaFloor = Typed(expected_type=AlphaFloorEffect, allow_none=True)
alphaInv = Typed(expected_type=AlphaInverseEffect, allow_none=True)
alphaMod = Typed(expected_type=AlphaModulateEffect, allow_none=True)
alphaModFix = Typed(expected_type=AlphaModulateFixedEffect, allow_none=True)
alphaRepl = Typed(expected_type=AlphaReplaceEffect, allow_none=True)
biLevel = Typed(expected_type=BiLevelEffect, allow_none=True)
blur = Typed(expected_type=BlurEffect, allow_none=True)
clrChange = Typed(expected_type=ColorChangeEffect, allow_none=True)
clrRepl = Typed(expected_type=ColorReplaceEffect, allow_none=True)
duotone = Typed(expected_type=DuotoneEffect, allow_none=True)
fillOverlay = Typed(expected_type=FillOverlayEffect, allow_none=True)
grayscl = Typed(expected_type=GrayscaleEffect, allow_none=True)
hsl = Typed(expected_type=HSLEffect, allow_none=True)
lum = Typed(expected_type=LuminanceEffect, allow_none=True)
tint = Typed(expected_type=TintEffect, allow_none=True)
__elements__ = ('alphaBiLevel', 'alphaCeiling', 'alphaFloor', 'alphaInv',
'alphaMod', 'alphaModFix', 'alphaRepl', 'biLevel', 'blur', 'clrChange',
'clrRepl', 'duotone', 'fillOverlay', 'grayscl', 'hsl', 'lum', 'tint')
def __init__(self,
cstate=None,
embed=None,
link=None,
noGrp=None,
noSelect=None,
noRot=None,
noChangeAspect=None,
noMove=None,
noResize=None,
noEditPoints=None,
noAdjustHandles=None,
noChangeArrowheads=None,
noChangeShapeType=None,
extLst=None,
alphaBiLevel=None,
alphaCeiling=None,
alphaFloor=None,
alphaInv=None,
alphaMod=None,
alphaModFix=None,
alphaRepl=None,
biLevel=None,
blur=None,
clrChange=None,
clrRepl=None,
duotone=None,
fillOverlay=None,
grayscl=None,
hsl=None,
lum=None,
tint=None,
):
self.cstate = cstate
self.embed = embed
self.link = link
self.noGrp = noGrp
self.noSelect = noSelect
self.noRot = noRot
self.noChangeAspect = noChangeAspect
self.noMove = noMove
self.noResize = noResize
self.noEditPoints = noEditPoints
self.noAdjustHandles = noAdjustHandles
self.noChangeArrowheads = noChangeArrowheads
self.noChangeShapeType = noChangeShapeType
self.extLst = extLst
self.alphaBiLevel = alphaBiLevel
self.alphaCeiling = alphaCeiling
self.alphaFloor = alphaFloor
self.alphaInv = alphaInv
self.alphaMod = alphaMod
self.alphaModFix = alphaModFix
self.alphaRepl = alphaRepl
self.biLevel = biLevel
self.blur = blur
self.clrChange = clrChange
self.clrRepl = clrRepl
self.duotone = duotone
self.fillOverlay = fillOverlay
self.grayscl = grayscl
self.hsl = hsl
self.lum = lum
self.tint = tint
class TileInfoProperties(Serialisable):
tx = Integer(allow_none=True)
ty = Integer(allow_none=True)
sx = Integer(allow_none=True)
sy = Integer(allow_none=True)
flip = NoneSet(values=(['x', 'y', 'xy']))
algn = Set(values=(['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br']))
def __init__(self,
tx=None,
ty=None,
sx=None,
sy=None,
flip=None,
algn=None,
):
self.tx = tx
self.ty = ty
self.sx = sx
self.sy = sy
self.flip = flip
self.algn = algn
class BlipFillProperties(Serialisable):
tagname = "blipFill"
dpi = Integer(allow_none=True)
rotWithShape = Bool(allow_none=True)
blip = Typed(expected_type=Blip, allow_none=True)
srcRect = Typed(expected_type=RelativeRect, allow_none=True)
tile = Typed(expected_type=TileInfoProperties, allow_none=True)
stretch = Typed(expected_type=StretchInfoProperties, allow_none=True)
__elements__ = ("blip", "srcRect", "tile", "stretch")
def __init__(self,
dpi=None,
rotWithShape=None,
blip=None,
tile=None,
stretch=StretchInfoProperties(),
srcRect=None,
):
self.dpi = dpi
self.rotWithShape = rotWithShape
self.blip = blip
self.tile = tile
self.stretch = stretch
self.srcRect = srcRect

View File

@ -0,0 +1,595 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Float,
Integer,
Bool,
MinMax,
Set,
NoneSet,
String,
Alias,
)
from openpyxl.descriptors.excel import Coordinate, Percentage
from openpyxl.descriptors.nested import (
EmptyTag
)
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
from .colors import ColorChoiceDescriptor
from .fill import (
GradientFillProperties,
BlipFillProperties,
PatternFillProperties,
)
from .line import LineProperties
from openpyxl.styles.colors import Color
from openpyxl.xml.constants import DRAWING_NS
class Point2D(Serialisable):
tagname = "off"
namespace = DRAWING_NS
x = Coordinate()
y = Coordinate()
def __init__(self,
x=None,
y=None,
):
self.x = x
self.y = y
class PositiveSize2D(Serialisable):
tagname = "ext"
namespace = DRAWING_NS
"""
Dimensions in EMUs
"""
cx = Integer()
width = Alias('cx')
cy = Integer()
height = Alias('cy')
def __init__(self,
cx=None,
cy=None,
):
self.cx = cx
self.cy = cy
class Transform2D(Serialisable):
tagname = "xfrm"
namespace = DRAWING_NS
rot = Integer(allow_none=True)
flipH = Bool(allow_none=True)
flipV = Bool(allow_none=True)
off = Typed(expected_type=Point2D, allow_none=True)
ext = Typed(expected_type=PositiveSize2D, allow_none=True)
chOff = Typed(expected_type=Point2D, allow_none=True)
chExt = Typed(expected_type=PositiveSize2D, allow_none=True)
__elements__ = ('off', 'ext', 'chOff', 'chExt')
def __init__(self,
rot=None,
flipH=None,
flipV=None,
off=None,
ext=None,
chOff=None,
chExt=None,
):
self.rot = rot
self.flipH = flipH
self.flipV = flipV
self.off = off
self.ext = ext
self.chOff = chOff
self.chExt = chExt
class GroupTransform2D(Serialisable):
tagname = "xfrm"
namespace = DRAWING_NS
rot = Integer(allow_none=True)
flipH = Bool(allow_none=True)
flipV = Bool(allow_none=True)
off = Typed(expected_type=Point2D, allow_none=True)
ext = Typed(expected_type=PositiveSize2D, allow_none=True)
chOff = Typed(expected_type=Point2D, allow_none=True)
chExt = Typed(expected_type=PositiveSize2D, allow_none=True)
__elements__ = ("off", "ext", "chOff", "chExt")
def __init__(self,
rot=0,
flipH=None,
flipV=None,
off=None,
ext=None,
chOff=None,
chExt=None,
):
self.rot = rot
self.flipH = flipH
self.flipV = flipV
self.off = off
self.ext = ext
self.chOff = chOff
self.chExt = chExt
class SphereCoords(Serialisable):
tagname = "sphereCoords" # usually
lat = Integer()
lon = Integer()
rev = Integer()
def __init__(self,
lat=None,
lon=None,
rev=None,
):
self.lat = lat
self.lon = lon
self.rev = rev
class Camera(Serialisable):
tagname = "camera"
prst = Set(values=[
'legacyObliqueTopLeft', 'legacyObliqueTop', 'legacyObliqueTopRight', 'legacyObliqueLeft',
'legacyObliqueFront', 'legacyObliqueRight', 'legacyObliqueBottomLeft',
'legacyObliqueBottom', 'legacyObliqueBottomRight', 'legacyPerspectiveTopLeft',
'legacyPerspectiveTop', 'legacyPerspectiveTopRight', 'legacyPerspectiveLeft',
'legacyPerspectiveFront', 'legacyPerspectiveRight', 'legacyPerspectiveBottomLeft',
'legacyPerspectiveBottom', 'legacyPerspectiveBottomRight', 'orthographicFront',
'isometricTopUp', 'isometricTopDown', 'isometricBottomUp', 'isometricBottomDown',
'isometricLeftUp', 'isometricLeftDown', 'isometricRightUp', 'isometricRightDown',
'isometricOffAxis1Left', 'isometricOffAxis1Right', 'isometricOffAxis1Top',
'isometricOffAxis2Left', 'isometricOffAxis2Right', 'isometricOffAxis2Top',
'isometricOffAxis3Left', 'isometricOffAxis3Right', 'isometricOffAxis3Bottom',
'isometricOffAxis4Left', 'isometricOffAxis4Right', 'isometricOffAxis4Bottom',
'obliqueTopLeft', 'obliqueTop', 'obliqueTopRight', 'obliqueLeft', 'obliqueRight',
'obliqueBottomLeft', 'obliqueBottom', 'obliqueBottomRight', 'perspectiveFront',
'perspectiveLeft', 'perspectiveRight', 'perspectiveAbove', 'perspectiveBelow',
'perspectiveAboveLeftFacing', 'perspectiveAboveRightFacing',
'perspectiveContrastingLeftFacing', 'perspectiveContrastingRightFacing',
'perspectiveHeroicLeftFacing', 'perspectiveHeroicRightFacing',
'perspectiveHeroicExtremeLeftFacing', 'perspectiveHeroicExtremeRightFacing',
'perspectiveRelaxed', 'perspectiveRelaxedModerately'])
fov = Integer(allow_none=True)
zoom = Typed(expected_type=Percentage, allow_none=True)
rot = Typed(expected_type=SphereCoords, allow_none=True)
def __init__(self,
prst=None,
fov=None,
zoom=None,
rot=None,
):
self.prst = prst
self.fov = fov
self.zoom = zoom
self.rot = rot
class LightRig(Serialisable):
tagname = "lightRig"
rig = Set(values=['legacyFlat1', 'legacyFlat2', 'legacyFlat3', 'legacyFlat4', 'legacyNormal1',
'legacyNormal2', 'legacyNormal3', 'legacyNormal4', 'legacyHarsh1',
'legacyHarsh2', 'legacyHarsh3', 'legacyHarsh4', 'threePt', 'balanced',
'soft', 'harsh', 'flood', 'contrasting', 'morning', 'sunrise', 'sunset',
'chilly', 'freezing', 'flat', 'twoPt', 'glow', 'brightRoom']
)
dir = Set(values=(['tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br']))
rot = Typed(expected_type=SphereCoords, allow_none=True)
def __init__(self,
rig=None,
dir=None,
rot=None,
):
self.rig = rig
self.dir = dir
self.rot = rot
class Vector3D(Serialisable):
tagname = "vector"
dx = Integer() # can be in or universl measure :-/
dy = Integer()
dz = Integer()
def __init__(self,
dx=None,
dy=None,
dz=None,
):
self.dx = dx
self.dy = dy
self.dz = dz
class Point3D(Serialisable):
tagname = "anchor"
x = Integer()
y = Integer()
z = Integer()
def __init__(self,
x=None,
y=None,
z=None,
):
self.x = x
self.y = y
self.z = z
class Backdrop(Serialisable):
anchor = Typed(expected_type=Point3D, )
norm = Typed(expected_type=Vector3D, )
up = Typed(expected_type=Vector3D, )
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
def __init__(self,
anchor=None,
norm=None,
up=None,
extLst=None,
):
self.anchor = anchor
self.norm = norm
self.up = up
self.extLst = extLst
class Scene3D(Serialisable):
camera = Typed(expected_type=Camera, )
lightRig = Typed(expected_type=LightRig, )
backdrop = Typed(expected_type=Backdrop, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
def __init__(self,
camera=None,
lightRig=None,
backdrop=None,
extLst=None,
):
self.camera = camera
self.lightRig = lightRig
self.backdrop = backdrop
self.extLst = extLst
class Bevel(Serialisable):
tagname = "bevel"
w = Integer()
h = Integer()
prst = NoneSet(values=
['relaxedInset', 'circle', 'slope', 'cross', 'angle',
'softRound', 'convex', 'coolSlant', 'divot', 'riblet',
'hardEdge', 'artDeco']
)
def __init__(self,
w=None,
h=None,
prst=None,
):
self.w = w
self.h = h
self.prst = prst
class Shape3D(Serialisable):
namespace = DRAWING_NS
z = Typed(expected_type=Coordinate, allow_none=True)
extrusionH = Integer(allow_none=True)
contourW = Integer(allow_none=True)
prstMaterial = NoneSet(values=[
'legacyMatte','legacyPlastic', 'legacyMetal', 'legacyWireframe', 'matte', 'plastic',
'metal', 'warmMatte', 'translucentPowder', 'powder', 'dkEdge',
'softEdge', 'clear', 'flat', 'softmetal']
)
bevelT = Typed(expected_type=Bevel, allow_none=True)
bevelB = Typed(expected_type=Bevel, allow_none=True)
extrusionClr = Typed(expected_type=Color, allow_none=True)
contourClr = Typed(expected_type=Color, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
def __init__(self,
z=None,
extrusionH=None,
contourW=None,
prstMaterial=None,
bevelT=None,
bevelB=None,
extrusionClr=None,
contourClr=None,
extLst=None,
):
self.z = z
self.extrusionH = extrusionH
self.contourW = contourW
self.prstMaterial = prstMaterial
self.bevelT = bevelT
self.bevelB = bevelB
self.extrusionClr = extrusionClr
self.contourClr = contourClr
self.extLst = extLst
class Path2D(Serialisable):
w = Float()
h = Float()
fill = NoneSet(values=(['norm', 'lighten', 'lightenLess', 'darken', 'darkenLess']))
stroke = Bool(allow_none=True)
extrusionOk = Bool(allow_none=True)
def __init__(self,
w=None,
h=None,
fill=None,
stroke=None,
extrusionOk=None,
):
self.w = w
self.h = h
self.fill = fill
self.stroke = stroke
self.extrusionOk = extrusionOk
class Path2DList(Serialisable):
path = Typed(expected_type=Path2D, allow_none=True)
def __init__(self,
path=None,
):
self.path = path
class GeomRect(Serialisable):
l = Coordinate()
t = Coordinate()
r = Coordinate()
b = Coordinate()
def __init__(self,
l=None,
t=None,
r=None,
b=None,
):
self.l = l
self.t = t
self.r = r
self.b = b
class AdjPoint2D(Serialisable):
x = Coordinate()
y = Coordinate()
def __init__(self,
x=None,
y=None,
):
self.x = x
self.y = y
class ConnectionSite(Serialisable):
ang = MinMax(min=0, max=360) # guess work, can also be a name
pos = Typed(expected_type=AdjPoint2D, )
def __init__(self,
ang=None,
pos=None,
):
self.ang = ang
self.pos = pos
class ConnectionSiteList(Serialisable):
cxn = Typed(expected_type=ConnectionSite, allow_none=True)
def __init__(self,
cxn=None,
):
self.cxn = cxn
class AdjustHandleList(Serialisable):
pass
class GeomGuide(Serialisable):
name = String()
fmla = String()
def __init__(self,
name=None,
fmla=None,
):
self.name = name
self.fmla = fmla
class GeomGuideList(Serialisable):
gd = Typed(expected_type=GeomGuide, allow_none=True)
def __init__(self,
gd=None,
):
self.gd = gd
class CustomGeometry2D(Serialisable):
avLst = Typed(expected_type=GeomGuideList, allow_none=True)
gdLst = Typed(expected_type=GeomGuideList, allow_none=True)
ahLst = Typed(expected_type=AdjustHandleList, allow_none=True)
cxnLst = Typed(expected_type=ConnectionSiteList, allow_none=True)
#rect = Typed(expected_type=GeomRect, allow_none=True)
pathLst = Typed(expected_type=Path2DList, )
def __init__(self,
avLst=None,
gdLst=None,
ahLst=None,
cxnLst=None,
rect=None,
pathLst=None,
):
self.avLst = avLst
self.gdLst = gdLst
self.ahLst = ahLst
self.cxnLst = cxnLst
self.rect = None
self.pathLst = pathLst
class PresetGeometry2D(Serialisable):
namespace = DRAWING_NS
prst = Set(values=(
['line', 'lineInv', 'triangle', 'rtTriangle', 'rect',
'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']))
avLst = Typed(expected_type=GeomGuideList, allow_none=True)
def __init__(self,
prst=None,
avLst=None,
):
self.prst = prst
self.avLst = avLst
class FontReference(Serialisable):
idx = NoneSet(values=(['major', 'minor']))
def __init__(self,
idx=None,
):
self.idx = idx
class StyleMatrixReference(Serialisable):
idx = Integer()
def __init__(self,
idx=None,
):
self.idx = idx
class ShapeStyle(Serialisable):
lnRef = Typed(expected_type=StyleMatrixReference, )
fillRef = Typed(expected_type=StyleMatrixReference, )
effectRef = Typed(expected_type=StyleMatrixReference, )
fontRef = Typed(expected_type=FontReference, )
def __init__(self,
lnRef=None,
fillRef=None,
effectRef=None,
fontRef=None,
):
self.lnRef = lnRef
self.fillRef = fillRef
self.effectRef = effectRef
self.fontRef = fontRef

View File

@ -0,0 +1,186 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.xml.functions import NS_REGEX, Element
from openpyxl.xml.constants import CHART_NS, REL_NS, DRAWING_NS
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Bool,
NoneSet,
Integer,
Set,
String,
Alias,
)
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
from openpyxl.chart.shapes import GraphicalProperties
from openpyxl.chart.text import RichText
from .effect import *
from .fill import RelativeRect, BlipFillProperties
from .text import Hyperlink, EmbeddedWAVAudioFile
from .geometry import (
Scene3D,
ShapeStyle,
GroupTransform2D
)
from .picture import PictureFrame
from .properties import (
NonVisualDrawingProps,
NonVisualDrawingShapeProps,
NonVisualGroupDrawingShapeProps,
NonVisualGroupShape,
GroupShapeProperties,
)
from .relation import ChartRelation
from .xdr import XDRTransform2D
class GraphicFrameLocking(Serialisable):
noGrp = Bool(allow_none=True)
noDrilldown = Bool(allow_none=True)
noSelect = Bool(allow_none=True)
noChangeAspect = Bool(allow_none=True)
noMove = Bool(allow_none=True)
noResize = Bool(allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
def __init__(self,
noGrp=None,
noDrilldown=None,
noSelect=None,
noChangeAspect=None,
noMove=None,
noResize=None,
extLst=None,
):
self.noGrp = noGrp
self.noDrilldown = noDrilldown
self.noSelect = noSelect
self.noChangeAspect = noChangeAspect
self.noMove = noMove
self.noResize = noResize
self.extLst = extLst
class NonVisualGraphicFrameProperties(Serialisable):
tagname = "cNvGraphicFramePr"
graphicFrameLocks = Typed(expected_type=GraphicFrameLocking, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
def __init__(self,
graphicFrameLocks=None,
extLst=None,
):
self.graphicFrameLocks = graphicFrameLocks
self.extLst = extLst
class NonVisualGraphicFrame(Serialisable):
tagname = "nvGraphicFramePr"
cNvPr = Typed(expected_type=NonVisualDrawingProps)
cNvGraphicFramePr = Typed(expected_type=NonVisualGraphicFrameProperties)
__elements__ = ('cNvPr', 'cNvGraphicFramePr')
def __init__(self,
cNvPr=None,
cNvGraphicFramePr=None,
):
if cNvPr is None:
cNvPr = NonVisualDrawingProps(id=0, name="Chart 0")
self.cNvPr = cNvPr
if cNvGraphicFramePr is None:
cNvGraphicFramePr = NonVisualGraphicFrameProperties()
self.cNvGraphicFramePr = cNvGraphicFramePr
class GraphicData(Serialisable):
tagname = "graphicData"
namespace = DRAWING_NS
uri = String()
chart = Typed(expected_type=ChartRelation, allow_none=True)
def __init__(self,
uri=CHART_NS,
chart=None,
):
self.uri = uri
self.chart = chart
class GraphicObject(Serialisable):
tagname = "graphic"
namespace = DRAWING_NS
graphicData = Typed(expected_type=GraphicData)
def __init__(self,
graphicData=None,
):
if graphicData is None:
graphicData = GraphicData()
self.graphicData = graphicData
class GraphicFrame(Serialisable):
tagname = "graphicFrame"
nvGraphicFramePr = Typed(expected_type=NonVisualGraphicFrame)
xfrm = Typed(expected_type=XDRTransform2D)
graphic = Typed(expected_type=GraphicObject)
macro = String(allow_none=True)
fPublished = Bool(allow_none=True)
__elements__ = ('nvGraphicFramePr', 'xfrm', 'graphic', 'macro', 'fPublished')
def __init__(self,
nvGraphicFramePr=None,
xfrm=None,
graphic=None,
macro=None,
fPublished=None,
):
if nvGraphicFramePr is None:
nvGraphicFramePr = NonVisualGraphicFrame()
self.nvGraphicFramePr = nvGraphicFramePr
if xfrm is None:
xfrm = XDRTransform2D()
self.xfrm = xfrm
if graphic is None:
graphic = GraphicObject()
self.graphic = graphic
self.macro = macro
self.fPublished = fPublished
class GroupShape(Serialisable):
nvGrpSpPr = Typed(expected_type=NonVisualGroupShape)
nonVisualProperties = Alias("nvGrpSpPr")
grpSpPr = Typed(expected_type=GroupShapeProperties)
visualProperties = Alias("grpSpPr")
pic = Typed(expected_type=PictureFrame, allow_none=True)
__elements__ = ["nvGrpSpPr", "grpSpPr", "pic"]
def __init__(self,
nvGrpSpPr=None,
grpSpPr=None,
pic=None,
):
self.nvGrpSpPr = nvGrpSpPr
self.grpSpPr = grpSpPr
self.pic = pic

View File

@ -0,0 +1,65 @@
# Copyright (c) 2010-2022 openpyxl
from io import BytesIO
try:
from PIL import Image as PILImage
except ImportError:
PILImage = False
def _import_image(img):
if not PILImage:
raise ImportError('You must install Pillow to fetch image objects')
if not isinstance(img, PILImage.Image):
img = PILImage.open(img)
return img
class Image(object):
"""Image in a spreadsheet"""
_id = 1
_path = "/xl/media/image{0}.{1}"
anchor = "A1"
def __init__(self, img):
self.ref = img
mark_to_close = isinstance(img, str)
image = _import_image(img)
self.width, self.height = image.size
try:
self.format = image.format.lower()
except AttributeError:
self.format = "png"
if mark_to_close:
# PIL instances created for metadata should be closed.
image.close()
def _data(self):
"""
Return image data, convert to supported types if necessary
"""
img = _import_image(self.ref)
# don't convert these file formats
if self.format in ['gif', 'jpeg', 'png']:
img.fp.seek(0)
fp = img.fp
else:
fp = BytesIO()
img.save(fp, format="png")
fp.seek(0)
data = fp.read()
fp.close()
return data
@property
def path(self):
return self._path.format(self._id, self.format)

View File

@ -0,0 +1,151 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Float,
Integer,
Bool,
MinMax,
Set,
NoneSet,
String,
Alias,
Sequence
)
from openpyxl.descriptors.excel import Coordinate, Percentage
from openpyxl.descriptors.nested import (
NestedInteger,
NestedSet,
NestedNoneSet,
EmptyTag,
)
from openpyxl.compat import safe_string
from openpyxl.xml.constants import DRAWING_NS
from openpyxl.xml.functions import Element
from .colors import ColorChoiceDescriptor
from .fill import GradientFillProperties, PatternFillProperties
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
"""
Line elements from drawing main schema
"""
class LineEndProperties(Serialisable):
tagname = "end"
namespace = DRAWING_NS
type = NoneSet(values=(['none', 'triangle', 'stealth', 'diamond', 'oval', 'arrow']))
w = NoneSet(values=(['sm', 'med', 'lg']))
len = NoneSet(values=(['sm', 'med', 'lg']))
def __init__(self,
type=None,
w=None,
len=None,
):
self.type = type
self.w = w
self.len = len
class DashStop(Serialisable):
tagname = "ds"
namespace = DRAWING_NS
d = Integer()
length = Alias('d')
sp = Integer()
space = Alias('sp')
def __init__(self,
d=0,
sp=0,
):
self.d = d
self.sp = sp
class DashStopList(Serialisable):
ds = Sequence(expected_type=DashStop, allow_none=True)
def __init__(self,
ds=None,
):
self.ds = ds
class LineProperties(Serialisable):
tagname = "ln"
namespace = DRAWING_NS
w = MinMax(min=0, max=20116800, allow_none=True) # EMU
width = Alias('w')
cap = NoneSet(values=(['rnd', 'sq', 'flat']))
cmpd = NoneSet(values=(['sng', 'dbl', 'thickThin', 'thinThick', 'tri']))
algn = NoneSet(values=(['ctr', 'in']))
noFill = EmptyTag()
solidFill = ColorChoiceDescriptor()
gradFill = Typed(expected_type=GradientFillProperties, allow_none=True)
pattFill = Typed(expected_type=PatternFillProperties, allow_none=True)
prstDash = NestedNoneSet(values=(['solid', 'dot', 'dash', 'lgDash', 'dashDot',
'lgDashDot', 'lgDashDotDot', 'sysDash', 'sysDot', 'sysDashDot',
'sysDashDotDot']), namespace=namespace)
dashStyle = Alias('prstDash')
custDash = Typed(expected_type=DashStop, allow_none=True)
round = EmptyTag()
bevel = EmptyTag()
miter = NestedInteger(allow_none=True, attribute="lim")
headEnd = Typed(expected_type=LineEndProperties, allow_none=True)
tailEnd = Typed(expected_type=LineEndProperties, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
__elements__ = ('noFill', 'solidFill', 'gradFill', 'pattFill',
'prstDash', 'custDash', 'round', 'bevel', 'miter', 'headEnd', 'tailEnd')
def __init__(self,
w=None,
cap=None,
cmpd=None,
algn=None,
noFill=None,
solidFill=None,
gradFill=None,
pattFill=None,
prstDash=None,
custDash=None,
round=None,
bevel=None,
miter=None,
headEnd=None,
tailEnd=None,
extLst=None,
):
self.w = w
self.cap = cap
self.cmpd = cmpd
self.algn = algn
self.noFill = noFill
self.solidFill = solidFill
self.gradFill = gradFill
self.pattFill = pattFill
if prstDash is None:
prstDash = "solid"
self.prstDash = prstDash
self.custDash = custDash
self.round = round
self.bevel = bevel
self.miter = miter
self.headEnd = headEnd
self.tailEnd = tailEnd

View File

@ -0,0 +1,148 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.xml.constants import DRAWING_NS
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Bool,
NoneSet,
Integer,
Set,
String,
Alias,
)
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
from openpyxl.chart.shapes import GraphicalProperties
from .fill import RelativeRect, BlipFillProperties
from .properties import NonVisualDrawingProps, NonVisualGroupDrawingShapeProps
from .geometry import ShapeStyle
class PictureLocking(Serialisable):
tagname = "picLocks"
namespace = DRAWING_NS
#Using attribute group AG_Locking
noCrop = Bool(allow_none=True)
noGrp = Bool(allow_none=True)
noSelect = Bool(allow_none=True)
noRot = Bool(allow_none=True)
noChangeAspect = Bool(allow_none=True)
noMove = Bool(allow_none=True)
noResize = Bool(allow_none=True)
noEditPoints = Bool(allow_none=True)
noAdjustHandles = Bool(allow_none=True)
noChangeArrowheads = Bool(allow_none=True)
noChangeShapeType = Bool(allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
__elements__ = ()
def __init__(self,
noCrop=None,
noGrp=None,
noSelect=None,
noRot=None,
noChangeAspect=None,
noMove=None,
noResize=None,
noEditPoints=None,
noAdjustHandles=None,
noChangeArrowheads=None,
noChangeShapeType=None,
extLst=None,
):
self.noCrop = noCrop
self.noGrp = noGrp
self.noSelect = noSelect
self.noRot = noRot
self.noChangeAspect = noChangeAspect
self.noMove = noMove
self.noResize = noResize
self.noEditPoints = noEditPoints
self.noAdjustHandles = noAdjustHandles
self.noChangeArrowheads = noChangeArrowheads
self.noChangeShapeType = noChangeShapeType
class NonVisualPictureProperties(Serialisable):
tagname = "cNvPicPr"
preferRelativeResize = Bool(allow_none=True)
picLocks = Typed(expected_type=PictureLocking, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
__elements__ = ("picLocks",)
def __init__(self,
preferRelativeResize=None,
picLocks=None,
extLst=None,
):
self.preferRelativeResize = preferRelativeResize
self.picLocks = picLocks
class PictureNonVisual(Serialisable):
tagname = "nvPicPr"
cNvPr = Typed(expected_type=NonVisualDrawingProps, )
cNvPicPr = Typed(expected_type=NonVisualPictureProperties, )
__elements__ = ("cNvPr", "cNvPicPr")
def __init__(self,
cNvPr=None,
cNvPicPr=None,
):
if cNvPr is None:
cNvPr = NonVisualDrawingProps(id=0, name="Image 1", descr="Name of file")
self.cNvPr = cNvPr
if cNvPicPr is None:
cNvPicPr = NonVisualPictureProperties()
self.cNvPicPr = cNvPicPr
class PictureFrame(Serialisable):
tagname = "pic"
macro = String(allow_none=True)
fPublished = Bool(allow_none=True)
nvPicPr = Typed(expected_type=PictureNonVisual, )
blipFill = Typed(expected_type=BlipFillProperties, )
spPr = Typed(expected_type=GraphicalProperties, )
graphicalProperties = Alias('spPr')
style = Typed(expected_type=ShapeStyle, allow_none=True)
__elements__ = ("nvPicPr", "blipFill", "spPr", "style")
def __init__(self,
macro=None,
fPublished=None,
nvPicPr=None,
blipFill=None,
spPr=None,
style=None,
):
self.macro = macro
self.fPublished = fPublished
if nvPicPr is None:
nvPicPr = PictureNonVisual()
self.nvPicPr = nvPicPr
if blipFill is None:
blipFill = BlipFillProperties()
self.blipFill = blipFill
if spPr is None:
spPr = GraphicalProperties()
self.spPr = spPr
self.style = style

View File

@ -0,0 +1,174 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.xml.constants import DRAWING_NS
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Bool,
Integer,
Set,
String,
Alias,
NoneSet,
)
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
from .geometry import GroupTransform2D, Scene3D
from .text import Hyperlink
class GroupShapeProperties(Serialisable):
tagname = "grpSpPr"
bwMode = NoneSet(values=(['clr', 'auto', 'gray', 'ltGray', 'invGray',
'grayWhite', 'blackGray', 'blackWhite', 'black', 'white', 'hidden']))
xfrm = Typed(expected_type=GroupTransform2D, allow_none=True)
scene3d = Typed(expected_type=Scene3D, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
def __init__(self,
bwMode=None,
xfrm=None,
scene3d=None,
extLst=None,
):
self.bwMode = bwMode
self.xfrm = xfrm
self.scene3d = scene3d
self.extLst = extLst
class GroupLocking(Serialisable):
tagname = "grpSpLocks"
namespace = DRAWING_NS
noGrp = Bool(allow_none=True)
noUngrp = Bool(allow_none=True)
noSelect = Bool(allow_none=True)
noRot = Bool(allow_none=True)
noChangeAspect = Bool(allow_none=True)
noMove = Bool(allow_none=True)
noResize = Bool(allow_none=True)
noChangeArrowheads = Bool(allow_none=True)
noEditPoints = Bool(allow_none=True)
noAdjustHandles = Bool(allow_none=True)
noChangeArrowheads = Bool(allow_none=True)
noChangeShapeType = Bool(allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
__elements__ = ()
def __init__(self,
noGrp=None,
noUngrp=None,
noSelect=None,
noRot=None,
noChangeAspect=None,
noChangeArrowheads=None,
noMove=None,
noResize=None,
noEditPoints=None,
noAdjustHandles=None,
noChangeShapeType=None,
extLst=None,
):
self.noGrp = noGrp
self.noUngrp = noUngrp
self.noSelect = noSelect
self.noRot = noRot
self.noChangeAspect = noChangeAspect
self.noChangeArrowheads = noChangeArrowheads
self.noMove = noMove
self.noResize = noResize
self.noEditPoints = noEditPoints
self.noAdjustHandles = noAdjustHandles
self.noChangeShapeType = noChangeShapeType
class NonVisualGroupDrawingShapeProps(Serialisable):
tagname = "cNvGrpSpPr"
grpSpLocks = Typed(expected_type=GroupLocking, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
__elements__ = ("grpSpLocks",)
def __init__(self,
grpSpLocks=None,
extLst=None,
):
self.grpSpLocks = grpSpLocks
class NonVisualDrawingShapeProps(Serialisable):
tagname = "cNvSpPr"
spLocks = Typed(expected_type=GroupLocking, allow_none=True)
txBax = Bool(allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
__elements__ = ("spLocks", "txBax")
def __init__(self,
spLocks=None,
txBox=None,
extLst=None,
):
self.spLocks = spLocks
self.txBox = txBox
class NonVisualDrawingProps(Serialisable):
tagname = "cNvPr"
id = Integer()
name = String()
descr = String(allow_none=True)
hidden = Bool(allow_none=True)
title = String(allow_none=True)
hlinkClick = Typed(expected_type=Hyperlink, allow_none=True)
hlinkHover = Typed(expected_type=Hyperlink, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
__elements__ = ["hlinkClick", "hlinkHover"]
def __init__(self,
id=None,
name=None,
descr=None,
hidden=None,
title=None,
hlinkClick=None,
hlinkHover=None,
extLst=None,
):
self.id = id
self.name = name
self.descr = descr
self.hidden = hidden
self.title = title
self.hlinkClick = hlinkClick
self.hlinkHover = hlinkHover
self.extLst = extLst
class NonVisualGroupShape(Serialisable):
tagname = "nvGrpSpPr"
cNvPr = Typed(expected_type=NonVisualDrawingProps)
cNvGrpSpPr = Typed(expected_type=NonVisualGroupDrawingShapeProps)
__elements__ = ("cNvPr", "cNvGrpSpPr")
def __init__(self,
cNvPr=None,
cNvGrpSpPr=None,
):
self.cNvPr = cNvPr
self.cNvGrpSpPr = cNvGrpSpPr

View File

@ -0,0 +1,17 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.xml.constants import CHART_NS
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors.excel import Relation
class ChartRelation(Serialisable):
tagname = "chart"
namespace = CHART_NS
id = Relation()
def __init__(self, id):
self.id = id

View File

@ -0,0 +1,381 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Bool,
NoneSet,
Integer,
Sequence,
Alias,
)
from openpyxl.descriptors.nested import (
NestedText,
NestedNoneSet,
)
from openpyxl.descriptors.excel import Relation
from openpyxl.packaging.relationship import (
Relationship,
RelationshipList,
)
from openpyxl.utils import coordinate_to_tuple
from openpyxl.utils.units import (
cm_to_EMU,
pixels_to_EMU,
)
from openpyxl.drawing.image import Image
from openpyxl.xml.constants import SHEET_DRAWING_NS
from openpyxl.chart._chart import ChartBase
from .xdr import (
XDRPoint2D,
XDRPositiveSize2D,
)
from .fill import Blip
from .connector import Shape
from .graphic import (
GroupShape,
GraphicFrame,
)
from .geometry import PresetGeometry2D
from .picture import PictureFrame
from .relation import ChartRelation
class AnchorClientData(Serialisable):
fLocksWithSheet = Bool(allow_none=True)
fPrintsWithSheet = Bool(allow_none=True)
def __init__(self,
fLocksWithSheet=None,
fPrintsWithSheet=None,
):
self.fLocksWithSheet = fLocksWithSheet
self.fPrintsWithSheet = fPrintsWithSheet
class AnchorMarker(Serialisable):
tagname = "marker"
col = NestedText(expected_type=int)
colOff = NestedText(expected_type=int)
row = NestedText(expected_type=int)
rowOff = NestedText(expected_type=int)
def __init__(self,
col=0,
colOff=0,
row=0,
rowOff=0,
):
self.col = col
self.colOff = colOff
self.row = row
self.rowOff = rowOff
class _AnchorBase(Serialisable):
#one of
sp = Typed(expected_type=Shape, allow_none=True)
shape = Alias("sp")
grpSp = Typed(expected_type=GroupShape, allow_none=True)
groupShape = Alias("grpSp")
graphicFrame = Typed(expected_type=GraphicFrame, allow_none=True)
cxnSp = Typed(expected_type=Shape, allow_none=True)
connectionShape = Alias("cxnSp")
pic = Typed(expected_type=PictureFrame, allow_none=True)
contentPart = Relation()
clientData = Typed(expected_type=AnchorClientData)
__elements__ = ('sp', 'grpSp', 'graphicFrame',
'cxnSp', 'pic', 'contentPart', 'clientData')
def __init__(self,
clientData=None,
sp=None,
grpSp=None,
graphicFrame=None,
cxnSp=None,
pic=None,
contentPart=None
):
if clientData is None:
clientData = AnchorClientData()
self.clientData = clientData
self.sp = sp
self.grpSp = grpSp
self.graphicFrame = graphicFrame
self.cxnSp = cxnSp
self.pic = pic
self.contentPart = contentPart
class AbsoluteAnchor(_AnchorBase):
tagname = "absoluteAnchor"
pos = Typed(expected_type=XDRPoint2D)
ext = Typed(expected_type=XDRPositiveSize2D)
sp = _AnchorBase.sp
grpSp = _AnchorBase.grpSp
graphicFrame = _AnchorBase.graphicFrame
cxnSp = _AnchorBase.cxnSp
pic = _AnchorBase.pic
contentPart = _AnchorBase.contentPart
clientData = _AnchorBase.clientData
__elements__ = ('pos', 'ext') + _AnchorBase.__elements__
def __init__(self,
pos=None,
ext=None,
**kw
):
if pos is None:
pos = XDRPoint2D(0, 0)
self.pos = pos
if ext is None:
ext = XDRPositiveSize2D(0, 0)
self.ext = ext
super(AbsoluteAnchor, self).__init__(**kw)
class OneCellAnchor(_AnchorBase):
tagname = "oneCellAnchor"
_from = Typed(expected_type=AnchorMarker)
ext = Typed(expected_type=XDRPositiveSize2D)
sp = _AnchorBase.sp
grpSp = _AnchorBase.grpSp
graphicFrame = _AnchorBase.graphicFrame
cxnSp = _AnchorBase.cxnSp
pic = _AnchorBase.pic
contentPart = _AnchorBase.contentPart
clientData = _AnchorBase.clientData
__elements__ = ('_from', 'ext') + _AnchorBase.__elements__
def __init__(self,
_from=None,
ext=None,
**kw
):
if _from is None:
_from = AnchorMarker()
self._from = _from
if ext is None:
ext = XDRPositiveSize2D(0, 0)
self.ext = ext
super(OneCellAnchor, self).__init__(**kw)
class TwoCellAnchor(_AnchorBase):
tagname = "twoCellAnchor"
editAs = NoneSet(values=(['twoCell', 'oneCell', 'absolute']))
_from = Typed(expected_type=AnchorMarker)
to = Typed(expected_type=AnchorMarker)
sp = _AnchorBase.sp
grpSp = _AnchorBase.grpSp
graphicFrame = _AnchorBase.graphicFrame
cxnSp = _AnchorBase.cxnSp
pic = _AnchorBase.pic
contentPart = _AnchorBase.contentPart
clientData = _AnchorBase.clientData
__elements__ = ('_from', 'to') + _AnchorBase.__elements__
def __init__(self,
editAs=None,
_from=None,
to=None,
**kw
):
self.editAs = editAs
if _from is None:
_from = AnchorMarker()
self._from = _from
if to is None:
to = AnchorMarker()
self.to = to
super(TwoCellAnchor, self).__init__(**kw)
def _check_anchor(obj):
"""
Check whether an object has an existing Anchor object
If not create a OneCellAnchor using the provided coordinate
"""
anchor = obj.anchor
if not isinstance(anchor, _AnchorBase):
row, col = coordinate_to_tuple(anchor.upper())
anchor = OneCellAnchor()
anchor._from.row = row -1
anchor._from.col = col -1
if isinstance(obj, ChartBase):
anchor.ext.width = cm_to_EMU(obj.width)
anchor.ext.height = cm_to_EMU(obj.height)
elif isinstance(obj, Image):
anchor.ext.width = pixels_to_EMU(obj.width)
anchor.ext.height = pixels_to_EMU(obj.height)
return anchor
class SpreadsheetDrawing(Serialisable):
tagname = "wsDr"
mime_type = "application/vnd.openxmlformats-officedocument.drawing+xml"
_rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
_path = PartName="/xl/drawings/drawing{0}.xml"
_id = None
twoCellAnchor = Sequence(expected_type=TwoCellAnchor, allow_none=True)
oneCellAnchor = Sequence(expected_type=OneCellAnchor, allow_none=True)
absoluteAnchor = Sequence(expected_type=AbsoluteAnchor, allow_none=True)
__elements__ = ("twoCellAnchor", "oneCellAnchor", "absoluteAnchor")
def __init__(self,
twoCellAnchor=(),
oneCellAnchor=(),
absoluteAnchor=(),
):
self.twoCellAnchor = twoCellAnchor
self.oneCellAnchor = oneCellAnchor
self.absoluteAnchor = absoluteAnchor
self.charts = []
self.images = []
self._rels = []
def __hash__(self):
"""
Just need to check for identity
"""
return id(self)
def __bool__(self):
return bool(self.charts) or bool(self.images)
def _write(self):
"""
create required structure and the serialise
"""
anchors = []
for idx, obj in enumerate(self.charts + self.images, 1):
anchor = _check_anchor(obj)
if isinstance(obj, ChartBase):
rel = Relationship(type="chart", Target=obj.path)
anchor.graphicFrame = self._chart_frame(idx)
elif isinstance(obj, Image):
rel = Relationship(type="image", Target=obj.path)
child = anchor.pic or anchor.groupShape and anchor.groupShape.pic
if not child:
anchor.pic = self._picture_frame(idx)
else:
child.blipFill.blip.embed = "rId{0}".format(idx)
anchors.append(anchor)
self._rels.append(rel)
for a in anchors:
if isinstance(a, OneCellAnchor):
self.oneCellAnchor.append(a)
elif isinstance(a, TwoCellAnchor):
self.twoCellAnchor.append(a)
else:
self.absoluteAnchor.append(a)
tree = self.to_tree()
tree.set('xmlns', SHEET_DRAWING_NS)
return tree
def _chart_frame(self, idx):
chart_rel = ChartRelation(f"rId{idx}")
frame = GraphicFrame()
nv = frame.nvGraphicFramePr.cNvPr
nv.id = idx
nv.name = "Chart {0}".format(idx)
frame.graphic.graphicData.chart = chart_rel
return frame
def _picture_frame(self, idx):
pic = PictureFrame()
pic.nvPicPr.cNvPr.descr = "Picture"
pic.nvPicPr.cNvPr.id = idx
pic.nvPicPr.cNvPr.name = "Image {0}".format(idx)
pic.blipFill.blip = Blip()
pic.blipFill.blip.embed = "rId{0}".format(idx)
pic.blipFill.blip.cstate = "print"
pic.spPr.prstGeom = PresetGeometry2D(prst="rect")
pic.spPr.ln = None
return pic
def _write_rels(self):
rels = RelationshipList()
rels.Relationship = self._rels
return rels.to_tree()
@property
def path(self):
return self._path.format(self._id)
@property
def _chart_rels(self):
"""
Get relationship information for each chart and bind anchor to it
"""
rels = []
anchors = self.absoluteAnchor + self.oneCellAnchor + self.twoCellAnchor
for anchor in anchors:
if anchor.graphicFrame is not None:
graphic = anchor.graphicFrame.graphic
rel = graphic.graphicData.chart
if rel is not None:
rel.anchor = anchor
rel.anchor.graphicFrame = None
rels.append(rel)
return rels
@property
def _blip_rels(self):
"""
Get relationship information for each blip and bind anchor to it
Images that are not part of the XLSX package will be ignored.
"""
rels = []
anchors = self.absoluteAnchor + self.oneCellAnchor + self.twoCellAnchor
for anchor in anchors:
child = anchor.pic or anchor.groupShape and anchor.groupShape.pic
if child and child.blipFill:
rel = child.blipFill.blip
if rel is not None and rel.embed:
rel.anchor = anchor
rels.append(rel)
return rels

View File

@ -0,0 +1,712 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Alias,
Typed,
Set,
NoneSet,
Sequence,
String,
Bool,
MinMax,
Integer
)
from openpyxl.descriptors.excel import (
HexBinary,
TextPoint,
Coordinate,
ExtensionList,
Relation,
)
from openpyxl.descriptors.nested import (
NestedInteger,
NestedString,
NestedText,
NestedValue,
EmptyTag
)
from openpyxl.xml.constants import DRAWING_NS
from .colors import ColorChoiceDescriptor
from .effect import *
from .fill import *
from .geometry import (
LineProperties,
Color,
Scene3D
)
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
from openpyxl.descriptors.nested import NestedBool
class EmbeddedWAVAudioFile(Serialisable):
name = String(allow_none=True)
def __init__(self,
name=None,
):
self.name = name
class Hyperlink(Serialisable):
tagname = "hlinkClick"
namespace = DRAWING_NS
invalidUrl = String(allow_none=True)
action = String(allow_none=True)
tgtFrame = String(allow_none=True)
tooltip = String(allow_none=True)
history = Bool(allow_none=True)
highlightClick = Bool(allow_none=True)
endSnd = Bool(allow_none=True)
snd = Typed(expected_type=EmbeddedWAVAudioFile, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
id = Relation(allow_none=True)
__elements__ = ('snd',)
def __init__(self,
invalidUrl=None,
action=None,
tgtFrame=None,
tooltip=None,
history=None,
highlightClick=None,
endSnd=None,
snd=None,
extLst=None,
id=None,
):
self.invalidUrl = invalidUrl
self.action = action
self.tgtFrame = tgtFrame
self.tooltip = tooltip
self.history = history
self.highlightClick = highlightClick
self.endSnd = endSnd
self.snd = snd
self.id = id
class Font(Serialisable):
tagname = "latin"
namespace = DRAWING_NS
typeface = String()
panose = HexBinary(allow_none=True)
pitchFamily = MinMax(min=0, max=52, allow_none=True)
charset = Integer(allow_none=True)
def __init__(self,
typeface=None,
panose=None,
pitchFamily=None,
charset=None,
):
self.typeface = typeface
self.panose = panose
self.pitchFamily = pitchFamily
self.charset = charset
class CharacterProperties(Serialisable):
tagname = "defRPr"
namespace = DRAWING_NS
kumimoji = Bool(allow_none=True)
lang = String(allow_none=True)
altLang = String(allow_none=True)
sz = MinMax(allow_none=True, min=100, max=400000) # 100ths of a point
b = Bool(allow_none=True)
i = Bool(allow_none=True)
u = NoneSet(values=(['words', 'sng', 'dbl', 'heavy', 'dotted',
'dottedHeavy', 'dash', 'dashHeavy', 'dashLong', 'dashLongHeavy',
'dotDash', 'dotDashHeavy', 'dotDotDash', 'dotDotDashHeavy', 'wavy',
'wavyHeavy', 'wavyDbl']))
strike = NoneSet(values=(['noStrike', 'sngStrike', 'dblStrike']))
kern = Integer(allow_none=True)
cap = NoneSet(values=(['small', 'all']))
spc = Integer(allow_none=True)
normalizeH = Bool(allow_none=True)
baseline = Integer(allow_none=True)
noProof = Bool(allow_none=True)
dirty = Bool(allow_none=True)
err = Bool(allow_none=True)
smtClean = Bool(allow_none=True)
smtId = Integer(allow_none=True)
bmk = String(allow_none=True)
ln = Typed(expected_type=LineProperties, allow_none=True)
highlight = Typed(expected_type=Color, allow_none=True)
latin = Typed(expected_type=Font, allow_none=True)
ea = Typed(expected_type=Font, allow_none=True)
cs = Typed(expected_type=Font, allow_none=True)
sym = Typed(expected_type=Font, allow_none=True)
hlinkClick = Typed(expected_type=Hyperlink, allow_none=True)
hlinkMouseOver = Typed(expected_type=Hyperlink, allow_none=True)
rtl = NestedBool(allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
# uses element group EG_FillProperties
noFill = EmptyTag(namespace=DRAWING_NS)
solidFill = ColorChoiceDescriptor()
gradFill = Typed(expected_type=GradientFillProperties, allow_none=True)
blipFill = Typed(expected_type=BlipFillProperties, allow_none=True)
pattFill = Typed(expected_type=PatternFillProperties, allow_none=True)
grpFill = EmptyTag(namespace=DRAWING_NS)
# uses element group EG_EffectProperties
effectLst = Typed(expected_type=EffectList, allow_none=True)
effectDag = Typed(expected_type=EffectContainer, allow_none=True)
# uses element group EG_TextUnderlineLine
uLnTx = EmptyTag()
uLn = Typed(expected_type=LineProperties, allow_none=True)
# uses element group EG_TextUnderlineFill
uFillTx = EmptyTag()
uFill = EmptyTag()
__elements__ = ('ln', 'noFill', 'solidFill', 'gradFill', 'blipFill',
'pattFill', 'grpFill', 'effectLst', 'effectDag', 'highlight','uLnTx',
'uLn', 'uFillTx', 'uFill', 'latin', 'ea', 'cs', 'sym', 'hlinkClick',
'hlinkMouseOver', 'rtl', )
def __init__(self,
kumimoji=None,
lang=None,
altLang=None,
sz=None,
b=None,
i=None,
u=None,
strike=None,
kern=None,
cap=None,
spc=None,
normalizeH=None,
baseline=None,
noProof=None,
dirty=None,
err=None,
smtClean=None,
smtId=None,
bmk=None,
ln=None,
highlight=None,
latin=None,
ea=None,
cs=None,
sym=None,
hlinkClick=None,
hlinkMouseOver=None,
rtl=None,
extLst=None,
noFill=None,
solidFill=None,
gradFill=None,
blipFill=None,
pattFill=None,
grpFill=None,
effectLst=None,
effectDag=None,
uLnTx=None,
uLn=None,
uFillTx=None,
uFill=None,
):
self.kumimoji = kumimoji
self.lang = lang
self.altLang = altLang
self.sz = sz
self.b = b
self.i = i
self.u = u
self.strike = strike
self.kern = kern
self.cap = cap
self.spc = spc
self.normalizeH = normalizeH
self.baseline = baseline
self.noProof = noProof
self.dirty = dirty
self.err = err
self.smtClean = smtClean
self.smtId = smtId
self.bmk = bmk
self.ln = ln
self.highlight = highlight
self.latin = latin
self.ea = ea
self.cs = cs
self.sym = sym
self.hlinkClick = hlinkClick
self.hlinkMouseOver = hlinkMouseOver
self.rtl = rtl
self.noFill = noFill
self.solidFill = solidFill
self.gradFill = gradFill
self.blipFill = blipFill
self.pattFill = pattFill
self.grpFill = grpFill
self.effectLst = effectLst
self.effectDag = effectDag
self.uLnTx = uLnTx
self.uLn = uLn
self.uFillTx = uFillTx
self.uFill = uFill
class TabStop(Serialisable):
pos = Typed(expected_type=Coordinate, allow_none=True)
algn = Typed(expected_type=Set(values=(['l', 'ctr', 'r', 'dec'])))
def __init__(self,
pos=None,
algn=None,
):
self.pos = pos
self.algn = algn
class TabStopList(Serialisable):
tab = Typed(expected_type=TabStop, allow_none=True)
def __init__(self,
tab=None,
):
self.tab = tab
class Spacing(Serialisable):
spcPct = NestedInteger(allow_none=True)
spcPts = NestedInteger(allow_none=True)
__elements__ = ('spcPct', 'spcPts')
def __init__(self,
spcPct=None,
spcPts=None,
):
self.spcPct = spcPct
self.spcPts = spcPts
class AutonumberBullet(Serialisable):
type = Set(values=(['alphaLcParenBoth', 'alphaUcParenBoth',
'alphaLcParenR', 'alphaUcParenR', 'alphaLcPeriod', 'alphaUcPeriod',
'arabicParenBoth', 'arabicParenR', 'arabicPeriod', 'arabicPlain',
'romanLcParenBoth', 'romanUcParenBoth', 'romanLcParenR', 'romanUcParenR',
'romanLcPeriod', 'romanUcPeriod', 'circleNumDbPlain',
'circleNumWdBlackPlain', 'circleNumWdWhitePlain', 'arabicDbPeriod',
'arabicDbPlain', 'ea1ChsPeriod', 'ea1ChsPlain', 'ea1ChtPeriod',
'ea1ChtPlain', 'ea1JpnChsDbPeriod', 'ea1JpnKorPlain', 'ea1JpnKorPeriod',
'arabic1Minus', 'arabic2Minus', 'hebrew2Minus', 'thaiAlphaPeriod',
'thaiAlphaParenR', 'thaiAlphaParenBoth', 'thaiNumPeriod',
'thaiNumParenR', 'thaiNumParenBoth', 'hindiAlphaPeriod',
'hindiNumPeriod', 'hindiNumParenR', 'hindiAlpha1Period']))
startAt = Integer()
def __init__(self,
type=None,
startAt=None,
):
self.type = type
self.startAt = startAt
class ParagraphProperties(Serialisable):
tagname = "pPr"
namespace = DRAWING_NS
marL = Integer(allow_none=True)
marR = Integer(allow_none=True)
lvl = Integer(allow_none=True)
indent = Integer(allow_none=True)
algn = NoneSet(values=(['l', 'ctr', 'r', 'just', 'justLow', 'dist', 'thaiDist']))
defTabSz = Integer(allow_none=True)
rtl = Bool(allow_none=True)
eaLnBrk = Bool(allow_none=True)
fontAlgn = NoneSet(values=(['auto', 't', 'ctr', 'base', 'b']))
latinLnBrk = Bool(allow_none=True)
hangingPunct = Bool(allow_none=True)
# uses element group EG_TextBulletColor
# uses element group EG_TextBulletSize
# uses element group EG_TextBulletTypeface
# uses element group EG_TextBullet
lnSpc = Typed(expected_type=Spacing, allow_none=True)
spcBef = Typed(expected_type=Spacing, allow_none=True)
spcAft = Typed(expected_type=Spacing, allow_none=True)
tabLst = Typed(expected_type=TabStopList, allow_none=True)
defRPr = Typed(expected_type=CharacterProperties, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
buClrTx = EmptyTag()
buClr = Typed(expected_type=Color, allow_none=True)
buSzTx = EmptyTag()
buSzPct = NestedInteger(allow_none=True)
buSzPts = NestedInteger(allow_none=True)
buFontTx = EmptyTag()
buFont = Typed(expected_type=Font, allow_none=True)
buNone = EmptyTag()
buAutoNum = EmptyTag()
buChar = NestedValue(expected_type=str, attribute="char", allow_none=True)
buBlip = NestedValue(expected_type=Blip, attribute="blip", allow_none=True)
__elements__ = ('lnSpc', 'spcBef', 'spcAft', 'tabLst', 'defRPr',
'buClrTx', 'buClr', 'buSzTx', 'buSzPct', 'buSzPts', 'buFontTx', 'buFont',
'buNone', 'buAutoNum', 'buChar', 'buBlip')
def __init__(self,
marL=None,
marR=None,
lvl=None,
indent=None,
algn=None,
defTabSz=None,
rtl=None,
eaLnBrk=None,
fontAlgn=None,
latinLnBrk=None,
hangingPunct=None,
lnSpc=None,
spcBef=None,
spcAft=None,
tabLst=None,
defRPr=None,
extLst=None,
buClrTx=None,
buClr=None,
buSzTx=None,
buSzPct=None,
buSzPts=None,
buFontTx=None,
buFont=None,
buNone=None,
buAutoNum=None,
buChar=None,
buBlip=None,
):
self.marL = marL
self.marR = marR
self.lvl = lvl
self.indent = indent
self.algn = algn
self.defTabSz = defTabSz
self.rtl = rtl
self.eaLnBrk = eaLnBrk
self.fontAlgn = fontAlgn
self.latinLnBrk = latinLnBrk
self.hangingPunct = hangingPunct
self.lnSpc = lnSpc
self.spcBef = spcBef
self.spcAft = spcAft
self.tabLst = tabLst
self.defRPr = defRPr
self.buClrTx = buClrTx
self.buClr = buClr
self.buSzTx = buSzTx
self.buSzPct = buSzPct
self.buSzPts = buSzPts
self.buFontTx = buFontTx
self.buFont = buFont
self.buNone = buNone
self.buAutoNum = buAutoNum
self.buChar = buChar
self.buBlip = buBlip
self.defRPr = defRPr
class ListStyle(Serialisable):
tagname = "lstStyle"
namespace = DRAWING_NS
defPPr = Typed(expected_type=ParagraphProperties, allow_none=True)
lvl1pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
lvl2pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
lvl3pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
lvl4pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
lvl5pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
lvl6pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
lvl7pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
lvl8pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
lvl9pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
__elements__ = ("defPPr", "lvl1pPr", "lvl2pPr", "lvl3pPr", "lvl4pPr",
"lvl5pPr", "lvl6pPr", "lvl7pPr", "lvl8pPr", "lvl9pPr")
def __init__(self,
defPPr=None,
lvl1pPr=None,
lvl2pPr=None,
lvl3pPr=None,
lvl4pPr=None,
lvl5pPr=None,
lvl6pPr=None,
lvl7pPr=None,
lvl8pPr=None,
lvl9pPr=None,
extLst=None,
):
self.defPPr = defPPr
self.lvl1pPr = lvl1pPr
self.lvl2pPr = lvl2pPr
self.lvl3pPr = lvl3pPr
self.lvl4pPr = lvl4pPr
self.lvl5pPr = lvl5pPr
self.lvl6pPr = lvl6pPr
self.lvl7pPr = lvl7pPr
self.lvl8pPr = lvl8pPr
self.lvl9pPr = lvl9pPr
class RegularTextRun(Serialisable):
tagname = "r"
namespace = DRAWING_NS
rPr = Typed(expected_type=CharacterProperties, allow_none=True)
properties = Alias("rPr")
t = NestedText(expected_type=str)
value = Alias("t")
__elements__ = ('rPr', 't')
def __init__(self,
rPr=None,
t="",
):
self.rPr = rPr
self.t = t
class LineBreak(Serialisable):
tagname = "br"
namespace = DRAWING_NS
rPr = Typed(expected_type=CharacterProperties, allow_none=True)
__elements__ = ('rPr',)
def __init__(self,
rPr=None,
):
self.rPr = rPr
class TextField(Serialisable):
id = String()
type = String(allow_none=True)
rPr = Typed(expected_type=CharacterProperties, allow_none=True)
pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
t = String(allow_none=True)
__elements__ = ('rPr', 'pPr')
def __init__(self,
id=None,
type=None,
rPr=None,
pPr=None,
t=None,
):
self.id = id
self.type = type
self.rPr = rPr
self.pPr = pPr
self.t = t
class Paragraph(Serialisable):
tagname = "p"
namespace = DRAWING_NS
# uses element group EG_TextRun
pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
properties = Alias("pPr")
endParaRPr = Typed(expected_type=CharacterProperties, allow_none=True)
r = Sequence(expected_type=RegularTextRun)
text = Alias('r')
br = Typed(expected_type=LineBreak, allow_none=True)
fld = Typed(expected_type=TextField, allow_none=True)
__elements__ = ('pPr', 'r', 'br', 'fld', 'endParaRPr')
def __init__(self,
pPr=None,
endParaRPr=None,
r=None,
br=None,
fld=None,
):
self.pPr = pPr
self.endParaRPr = endParaRPr
if r is None:
r = [RegularTextRun()]
self.r = r
self.br = br
self.fld = fld
class GeomGuide(Serialisable):
name = String(())
fmla = String(())
def __init__(self,
name=None,
fmla=None,
):
self.name = name
self.fmla = fmla
class GeomGuideList(Serialisable):
gd = Sequence(expected_type=GeomGuide, allow_none=True)
def __init__(self,
gd=None,
):
self.gd = gd
class PresetTextShape(Serialisable):
prst = Typed(expected_type=Set(values=(
['textNoShape', 'textPlain','textStop', 'textTriangle', 'textTriangleInverted', 'textChevron',
'textChevronInverted', 'textRingInside', 'textRingOutside', 'textArchUp',
'textArchDown', 'textCircle', 'textButton', 'textArchUpPour',
'textArchDownPour', 'textCirclePour', 'textButtonPour', 'textCurveUp',
'textCurveDown', 'textCanUp', 'textCanDown', 'textWave1', 'textWave2',
'textDoubleWave1', 'textWave4', 'textInflate', 'textDeflate',
'textInflateBottom', 'textDeflateBottom', 'textInflateTop',
'textDeflateTop', 'textDeflateInflate', 'textDeflateInflateDeflate',
'textFadeRight', 'textFadeLeft', 'textFadeUp', 'textFadeDown',
'textSlantUp', 'textSlantDown', 'textCascadeUp', 'textCascadeDown'
]
)))
avLst = Typed(expected_type=GeomGuideList, allow_none=True)
def __init__(self,
prst=None,
avLst=None,
):
self.prst = prst
self.avLst = avLst
class TextNormalAutofit(Serialisable):
fontScale = Integer()
lnSpcReduction = Integer()
def __init__(self,
fontScale=None,
lnSpcReduction=None,
):
self.fontScale = fontScale
self.lnSpcReduction = lnSpcReduction
class RichTextProperties(Serialisable):
tagname = "bodyPr"
namespace = DRAWING_NS
rot = Integer(allow_none=True)
spcFirstLastPara = Bool(allow_none=True)
vertOverflow = NoneSet(values=(['overflow', 'ellipsis', 'clip']))
horzOverflow = NoneSet(values=(['overflow', 'clip']))
vert = NoneSet(values=(['horz', 'vert', 'vert270', 'wordArtVert',
'eaVert', 'mongolianVert', 'wordArtVertRtl']))
wrap = NoneSet(values=(['none', 'square']))
lIns = Integer(allow_none=True)
tIns = Integer(allow_none=True)
rIns = Integer(allow_none=True)
bIns = Integer(allow_none=True)
numCol = Integer(allow_none=True)
spcCol = Integer(allow_none=True)
rtlCol = Bool(allow_none=True)
fromWordArt = Bool(allow_none=True)
anchor = NoneSet(values=(['t', 'ctr', 'b', 'just', 'dist']))
anchorCtr = Bool(allow_none=True)
forceAA = Bool(allow_none=True)
upright = Bool(allow_none=True)
compatLnSpc = Bool(allow_none=True)
prstTxWarp = Typed(expected_type=PresetTextShape, allow_none=True)
scene3d = Typed(expected_type=Scene3D, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
noAutofit = EmptyTag()
normAutofit = EmptyTag()
spAutoFit = EmptyTag()
flatTx = NestedInteger(attribute="z", allow_none=True)
__elements__ = ('prstTxWarp', 'scene3d', 'noAutofit', 'normAutofit', 'spAutoFit')
def __init__(self,
rot=None,
spcFirstLastPara=None,
vertOverflow=None,
horzOverflow=None,
vert=None,
wrap=None,
lIns=None,
tIns=None,
rIns=None,
bIns=None,
numCol=None,
spcCol=None,
rtlCol=None,
fromWordArt=None,
anchor=None,
anchorCtr=None,
forceAA=None,
upright=None,
compatLnSpc=None,
prstTxWarp=None,
scene3d=None,
extLst=None,
noAutofit=None,
normAutofit=None,
spAutoFit=None,
flatTx=None,
):
self.rot = rot
self.spcFirstLastPara = spcFirstLastPara
self.vertOverflow = vertOverflow
self.horzOverflow = horzOverflow
self.vert = vert
self.wrap = wrap
self.lIns = lIns
self.tIns = tIns
self.rIns = rIns
self.bIns = bIns
self.numCol = numCol
self.spcCol = spcCol
self.rtlCol = rtlCol
self.fromWordArt = fromWordArt
self.anchor = anchor
self.anchorCtr = anchorCtr
self.forceAA = forceAA
self.upright = upright
self.compatLnSpc = compatLnSpc
self.prstTxWarp = prstTxWarp
self.scene3d = scene3d
self.noAutofit = noAutofit
self.normAutofit = normAutofit
self.spAutoFit = spAutoFit
self.flatTx = flatTx

View File

@ -0,0 +1,33 @@
# Copyright (c) 2010-2022 openpyxl
"""
Spreadsheet Drawing has some copies of Drawing ML elements
"""
from .geometry import Point2D, PositiveSize2D, Transform2D
class XDRPoint2D(Point2D):
namespace = None
x = Point2D.x
y = Point2D.y
class XDRPositiveSize2D(PositiveSize2D):
namespace = None
cx = PositiveSize2D.cx
cy = PositiveSize2D.cy
class XDRTransform2D(Transform2D):
namespace = None
rot = Transform2D.rot
flipH = Transform2D.flipH
flipV = Transform2D.flipV
off = Transform2D.off
ext = Transform2D.ext
chOff = Transform2D.chOff
chExt = Transform2D.chExt

View File

@ -0,0 +1,3 @@
# Copyright (c) 2010-2022 openpyxl
from .rule import Rule

View File

@ -0,0 +1,118 @@
# Copyright (c) 2010-2022 openpyxl
from collections import OrderedDict
from openpyxl.descriptors import (
Bool,
String,
Sequence,
Alias,
Convertible,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.serialisable import Serialisable
from .rule import Rule
from openpyxl.worksheet.cell_range import MultiCellRange
class ConditionalFormatting(Serialisable):
tagname = "conditionalFormatting"
sqref = Convertible(expected_type=MultiCellRange)
cells = Alias("sqref")
pivot = Bool(allow_none=True)
cfRule = Sequence(expected_type=Rule)
rules = Alias("cfRule")
def __init__(self, sqref=(), pivot=None, cfRule=(), extLst=None):
self.sqref = sqref
self.pivot = pivot
self.cfRule = cfRule
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
return self.sqref == other.sqref
def __hash__(self):
return hash(str(self.sqref))
def __repr__(self):
return "<{cls} {cells}>".format(cls=self.__class__.__name__, cells=self.sqref)
def __contains__(self, coord):
"""
Check whether a certain cell is affected by the formatting
"""
return coord in self.sqref
class ConditionalFormattingList(object):
"""Conditional formatting rules."""
def __init__(self):
self._cf_rules = OrderedDict()
self.max_priority = 0
def add(self, range_string, cfRule):
"""Add a rule such as ColorScaleRule, FormulaRule or CellIsRule
The priority will be added automatically.
"""
cf = range_string
if isinstance(range_string, str):
cf = ConditionalFormatting(range_string)
if not isinstance(cfRule, Rule):
raise ValueError("Only instances of openpyxl.formatting.rule.Rule may be added")
rule = cfRule
self.max_priority += 1
if not rule.priority:
rule.priority = self.max_priority
self._cf_rules.setdefault(cf, []).append(rule)
def __bool__(self):
return bool(self._cf_rules)
__nonzero = __bool__
def __len__(self):
return len(self._cf_rules)
def __iter__(self):
for cf, rules in self._cf_rules.items():
cf.rules = rules
yield cf
def __getitem__(self, key):
"""
Get the rules for a cell range
"""
if isinstance(key, str):
key = ConditionalFormatting(sqref=key)
return self._cf_rules[key]
def __delitem__(self, key):
key = ConditionalFormatting(sqref=key)
del self._cf_rules[key]
def __setitem__(self, key, rule):
"""
Add a rule for a cell range
"""
self.add(key, rule)

View File

@ -0,0 +1,291 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
String,
Sequence,
Bool,
NoneSet,
Set,
Integer,
Float,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.styles.colors import Color, ColorDescriptor
from openpyxl.styles.differential import DifferentialStyle
from openpyxl.utils.cell import COORD_RE
class ValueDescriptor(Float):
"""
Expected type depends upon type attribue of parent :-(
Most values should be numeric BUT they can also be cell references
"""
def __set__(self, instance, value):
ref = None
if value is not None and isinstance(value, str):
ref = COORD_RE.match(value)
if instance.type == "formula" or ref:
self.expected_type = str
else:
self.expected_type = float
super(ValueDescriptor, self).__set__(instance, value)
class FormatObject(Serialisable):
tagname = "cfvo"
type = Set(values=(['num', 'percent', 'max', 'min', 'formula', 'percentile']))
val = ValueDescriptor(allow_none=True)
gte = Bool(allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ()
def __init__(self,
type,
val=None,
gte=None,
extLst=None,
):
self.type = type
self.val = val
self.gte = gte
class RuleType(Serialisable):
cfvo = Sequence(expected_type=FormatObject)
class IconSet(RuleType):
tagname = "iconSet"
iconSet = NoneSet(values=(['3Arrows', '3ArrowsGray', '3Flags',
'3TrafficLights1', '3TrafficLights2', '3Signs', '3Symbols', '3Symbols2',
'4Arrows', '4ArrowsGray', '4RedToBlack', '4Rating', '4TrafficLights',
'5Arrows', '5ArrowsGray', '5Rating', '5Quarters']))
showValue = Bool(allow_none=True)
percent = Bool(allow_none=True)
reverse = Bool(allow_none=True)
__elements__ = ("cfvo",)
def __init__(self,
iconSet=None,
showValue=None,
percent=None,
reverse=None,
cfvo=None,
):
self.iconSet = iconSet
self.showValue = showValue
self.percent = percent
self.reverse = reverse
self.cfvo = cfvo
class DataBar(RuleType):
tagname = "dataBar"
minLength = Integer(allow_none=True)
maxLength = Integer(allow_none=True)
showValue = Bool(allow_none=True)
color = ColorDescriptor()
__elements__ = ('cfvo', 'color')
def __init__(self,
minLength=None,
maxLength=None,
showValue=None,
cfvo=None,
color=None,
):
self.minLength = minLength
self.maxLength = maxLength
self.showValue = showValue
self.cfvo = cfvo
self.color = color
class ColorScale(RuleType):
tagname = "colorScale"
color = Sequence(expected_type=Color)
__elements__ = ('cfvo', 'color')
def __init__(self,
cfvo=None,
color=None,
):
self.cfvo = cfvo
self.color = color
class Rule(Serialisable):
tagname = "cfRule"
type = Set(values=(['expression', 'cellIs', 'colorScale', 'dataBar',
'iconSet', 'top10', 'uniqueValues', 'duplicateValues', 'containsText',
'notContainsText', 'beginsWith', 'endsWith', 'containsBlanks',
'notContainsBlanks', 'containsErrors', 'notContainsErrors', 'timePeriod',
'aboveAverage']))
dxfId = Integer(allow_none=True)
priority = Integer()
stopIfTrue = Bool(allow_none=True)
aboveAverage = Bool(allow_none=True)
percent = Bool(allow_none=True)
bottom = Bool(allow_none=True)
operator = NoneSet(values=(['lessThan', 'lessThanOrEqual', 'equal',
'notEqual', 'greaterThanOrEqual', 'greaterThan', 'between', 'notBetween',
'containsText', 'notContains', 'beginsWith', 'endsWith']))
text = String(allow_none=True)
timePeriod = NoneSet(values=(['today', 'yesterday', 'tomorrow', 'last7Days',
'thisMonth', 'lastMonth', 'nextMonth', 'thisWeek', 'lastWeek',
'nextWeek']))
rank = Integer(allow_none=True)
stdDev = Integer(allow_none=True)
equalAverage = Bool(allow_none=True)
formula = Sequence(expected_type=str)
colorScale = Typed(expected_type=ColorScale, allow_none=True)
dataBar = Typed(expected_type=DataBar, allow_none=True)
iconSet = Typed(expected_type=IconSet, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
dxf = Typed(expected_type=DifferentialStyle, allow_none=True)
__elements__ = ('colorScale', 'dataBar', 'iconSet', 'formula')
__attrs__ = ('type', 'rank', 'priority', 'equalAverage', 'operator',
'aboveAverage', 'dxfId', 'stdDev', 'stopIfTrue', 'timePeriod', 'text',
'percent', 'bottom')
def __init__(self,
type,
dxfId=None,
priority=0,
stopIfTrue=None,
aboveAverage=None,
percent=None,
bottom=None,
operator=None,
text=None,
timePeriod=None,
rank=None,
stdDev=None,
equalAverage=None,
formula=(),
colorScale=None,
dataBar=None,
iconSet=None,
extLst=None,
dxf=None,
):
self.type = type
self.dxfId = dxfId
self.priority = priority
self.stopIfTrue = stopIfTrue
self.aboveAverage = aboveAverage
self.percent = percent
self.bottom = bottom
self.operator = operator
self.text = text
self.timePeriod = timePeriod
self.rank = rank
self.stdDev = stdDev
self.equalAverage = equalAverage
self.formula = formula
self.colorScale = colorScale
self.dataBar = dataBar
self.iconSet = iconSet
self.dxf = dxf
def ColorScaleRule(start_type=None,
start_value=None,
start_color=None,
mid_type=None,
mid_value=None,
mid_color=None,
end_type=None,
end_value=None,
end_color=None):
"""Backwards compatibility"""
formats = []
if start_type is not None:
formats.append(FormatObject(type=start_type, val=start_value))
if mid_type is not None:
formats.append(FormatObject(type=mid_type, val=mid_value))
if end_type is not None:
formats.append(FormatObject(type=end_type, val=end_value))
colors = []
for v in (start_color, mid_color, end_color):
if v is not None:
if not isinstance(v, Color):
v = Color(v)
colors.append(v)
cs = ColorScale(cfvo=formats, color=colors)
rule = Rule(type="colorScale", colorScale=cs)
return rule
def FormulaRule(formula=None, stopIfTrue=None, font=None, border=None,
fill=None):
"""
Conditional formatting with custom differential style
"""
rule = Rule(type="expression", formula=formula, stopIfTrue=stopIfTrue)
rule.dxf = DifferentialStyle(font=font, border=border, fill=fill)
return rule
def CellIsRule(operator=None, formula=None, stopIfTrue=None, font=None, border=None, fill=None):
"""
Conditional formatting rule based on cell contents.
"""
# Excel doesn't use >, >=, etc, but allow for ease of python development
expand = {">": "greaterThan", ">=": "greaterThanOrEqual", "<": "lessThan", "<=": "lessThanOrEqual",
"=": "equal", "==": "equal", "!=": "notEqual"}
operator = expand.get(operator, operator)
rule = Rule(type='cellIs', operator=operator, formula=formula, stopIfTrue=stopIfTrue)
rule.dxf = DifferentialStyle(font=font, border=border, fill=fill)
return rule
def IconSetRule(icon_style=None, type=None, values=None, showValue=None, percent=None, reverse=None):
"""
Convenience function for creating icon set rules
"""
cfvo = []
for val in values:
cfvo.append(FormatObject(type, val))
icon_set = IconSet(iconSet=icon_style, cfvo=cfvo, showValue=showValue,
percent=percent, reverse=reverse)
rule = Rule(type='iconSet', iconSet=icon_set)
return rule
def DataBarRule(start_type=None, start_value=None, end_type=None,
end_value=None, color=None, showValue=None, minLength=None, maxLength=None):
start = FormatObject(start_type, start_value)
end = FormatObject(end_type, end_value)
data_bar = DataBar(cfvo=[start, end], color=color, showValue=showValue,
minLength=minLength, maxLength=maxLength)
rule = Rule(type='dataBar', dataBar=data_bar)
return rule

View File

@ -0,0 +1,3 @@
# Copyright (c) 2010-2022 openpyxl
from .tokenizer import Tokenizer

View File

@ -0,0 +1,446 @@
"""
This module contains a tokenizer for Excel formulae.
The tokenizer is based on the Javascript tokenizer found at
http://ewbi.blogs.com/develops/2004/12/excel_formula_p.html written by Eric
Bachtal
"""
import re
class TokenizerError(Exception):
"""Base class for all Tokenizer errors."""
class Tokenizer(object):
"""
A tokenizer for Excel worksheet formulae.
Converts a str string representing an Excel formula (in A1 notation)
into a sequence of `Token` objects.
`formula`: The str string to tokenize
Tokenizer defines a method `._parse()` to parse the formula into tokens,
which can then be accessed through the `.items` attribute.
"""
SN_RE = re.compile("^[1-9](\\.[0-9]+)?[Ee]$") # Scientific notation
WSPACE_RE = re.compile(r"[ \n]+")
STRING_REGEXES = {
# Inside a string, all characters are treated as literals, except for
# the quote character used to start the string. That character, when
# doubled is treated as a single character in the string. If an
# unmatched quote appears, the string is terminated.
'"': re.compile('"(?:[^"]*"")*[^"]*"(?!")'),
"'": re.compile("'(?:[^']*'')*[^']*'(?!')"),
}
ERROR_CODES = ("#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?",
"#NUM!", "#N/A", "#GETTING_DATA")
TOKEN_ENDERS = ',;}) +-*/^&=><%' # Each of these characters, marks the
# end of an operand token
def __init__(self, formula):
self.formula = formula
self.items = []
self.token_stack = [] # Used to keep track of arrays, functions, and
# parentheses
self.offset = 0 # How many chars have we read
self.token = [] # Used to build up token values char by char
self._parse()
def _parse(self):
"""Populate self.items with the tokens from the formula."""
if self.offset:
return # Already parsed!
if not self.formula:
return
elif self.formula[0] == '=':
self.offset += 1
else:
self.items.append(Token(self.formula, Token.LITERAL))
return
consumers = (
('"\'', self._parse_string),
('[', self._parse_brackets),
('#', self._parse_error),
(' ', self._parse_whitespace),
('\n', self._parse_whitespace),
('+-*/^&=><%', self._parse_operator),
('{(', self._parse_opener),
(')}', self._parse_closer),
(';,', self._parse_separator),
)
dispatcher = {} # maps chars to the specific parsing function
for chars, consumer in consumers:
dispatcher.update(dict.fromkeys(chars, consumer))
while self.offset < len(self.formula):
if self.check_scientific_notation(): # May consume one character
continue
curr_char = self.formula[self.offset]
if curr_char in self.TOKEN_ENDERS:
self.save_token()
if curr_char in dispatcher:
self.offset += dispatcher[curr_char]()
else:
# TODO: this can probably be sped up using a regex to get to
# the next interesting character
self.token.append(curr_char)
self.offset += 1
self.save_token()
def _parse_string(self):
"""
Parse a "-delimited string or '-delimited link.
The offset must be pointing to either a single quote ("'") or double
quote ('"') character. The strings are parsed according to Excel
rules where to escape the delimiter you just double it up. E.g.,
"abc""def" in Excel is parsed as 'abc"def' in Python.
Returns the number of characters matched. (Does not update
self.offset)
"""
self.assert_empty_token(can_follow=':')
delim = self.formula[self.offset]
assert delim in ('"', "'")
regex = self.STRING_REGEXES[delim]
match = regex.match(self.formula[self.offset:])
if match is None:
subtype = "string" if delim == '"' else 'link'
raise TokenizerError(f"Reached end of formula while parsing {subtype} in {self.formula}")
match = match.group(0)
if delim == '"':
self.items.append(Token.make_operand(match))
else:
self.token.append(match)
return len(match)
def _parse_brackets(self):
"""
Consume all the text between square brackets [].
Returns the number of characters matched. (Does not update
self.offset)
"""
assert self.formula[self.offset] == '['
lefts = [(t.start(), 1) for t in
re.finditer(r"\[", self.formula[self.offset:])]
rights = [(t.start(), -1) for t in
re.finditer(r"\]", self.formula[self.offset:])]
open_count = 0
for idx, open_close in sorted(lefts + rights):
open_count += open_close
if open_count == 0:
outer_right = idx + 1
self.token.append(
self.formula[self.offset:self.offset + outer_right])
return outer_right
raise TokenizerError(f"Encountered unmatched '[' in {self.formula}")
def _parse_error(self):
"""
Consume the text following a '#' as an error.
Looks for a match in self.ERROR_CODES and returns the number of
characters matched. (Does not update self.offset)
"""
self.assert_empty_token(can_follow='!')
assert self.formula[self.offset] == '#'
subformula = self.formula[self.offset:]
for err in self.ERROR_CODES:
if subformula.startswith(err):
self.items.append(Token.make_operand(''.join(self.token) + err))
del self.token[:]
return len(err)
raise TokenizerError(f"Invalid error code at position {self.offset} in '{self.formula}'")
def _parse_whitespace(self):
"""
Consume a string of consecutive spaces.
Returns the number of spaces found. (Does not update self.offset).
"""
assert self.formula[self.offset] in (' ', '\n')
self.items.append(Token(self.formula[self.offset], Token.WSPACE))
return self.WSPACE_RE.match(self.formula[self.offset:]).end()
def _parse_operator(self):
"""
Consume the characters constituting an operator.
Returns the number of characters consumed. (Does not update
self.offset)
"""
if self.formula[self.offset:self.offset + 2] in ('>=', '<=', '<>'):
self.items.append(Token(
self.formula[self.offset:self.offset + 2],
Token.OP_IN
))
return 2
curr_char = self.formula[self.offset] # guaranteed to be 1 char
assert curr_char in '%*/^&=><+-'
if curr_char == '%':
token = Token('%', Token.OP_POST)
elif curr_char in "*/^&=><":
token = Token(curr_char, Token.OP_IN)
# From here on, curr_char is guaranteed to be in '+-'
elif not self.items:
token = Token(curr_char, Token.OP_PRE)
else:
prev = next((i for i in reversed(self.items)
if i.type != Token.WSPACE), None)
is_infix = prev and (
prev.subtype == Token.CLOSE
or prev.type == Token.OP_POST
or prev.type == Token.OPERAND
)
if is_infix:
token = Token(curr_char, Token.OP_IN)
else:
token = Token(curr_char, Token.OP_PRE)
self.items.append(token)
return 1
def _parse_opener(self):
"""
Consumes a ( or { character.
Returns the number of characters consumed. (Does not update
self.offset)
"""
assert self.formula[self.offset] in ('(', '{')
if self.formula[self.offset] == '{':
self.assert_empty_token()
token = Token.make_subexp("{")
elif self.token:
token_value = "".join(self.token) + '('
del self.token[:]
token = Token.make_subexp(token_value)
else:
token = Token.make_subexp("(")
self.items.append(token)
self.token_stack.append(token)
return 1
def _parse_closer(self):
"""
Consumes a } or ) character.
Returns the number of characters consumed. (Does not update
self.offset)
"""
assert self.formula[self.offset] in (')', '}')
token = self.token_stack.pop().get_closer()
if token.value != self.formula[self.offset]:
raise TokenizerError(
"Mismatched ( and { pair in '%s'" % self.formula)
self.items.append(token)
return 1
def _parse_separator(self):
"""
Consumes a ; or , character.
Returns the number of characters consumed. (Does not update
self.offset)
"""
curr_char = self.formula[self.offset]
assert curr_char in (';', ',')
if curr_char == ';':
token = Token.make_separator(";")
else:
try:
top_type = self.token_stack[-1].type
except IndexError:
token = Token(",", Token.OP_IN) # Range Union operator
else:
if top_type == Token.PAREN:
token = Token(",", Token.OP_IN) # Range Union operator
else:
token = Token.make_separator(",")
self.items.append(token)
return 1
def check_scientific_notation(self):
"""
Consumes a + or - character if part of a number in sci. notation.
Returns True if the character was consumed and self.offset was
updated, False otherwise.
"""
curr_char = self.formula[self.offset]
if (curr_char in '+-'
and len(self.token) >= 1
and self.SN_RE.match("".join(self.token))):
self.token.append(curr_char)
self.offset += 1
return True
return False
def assert_empty_token(self, can_follow=()):
"""
Ensure that there's no token currently being parsed.
Or if there is a token being parsed, it must end with a character in
can_follow.
If there are unconsumed token contents, it means we hit an unexpected
token transition. In this case, we raise a TokenizerError
"""
if self.token and self.token[-1] not in can_follow:
raise TokenizerError(f"Unexpected character at position {self.offset} in '{self.formula}'")
def save_token(self):
"""If there's a token being parsed, add it to the item list."""
if self.token:
self.items.append(Token.make_operand("".join(self.token)))
del self.token[:]
def render(self):
"""Convert the parsed tokens back to a string."""
if not self.items:
return ""
elif self.items[0].type == Token.LITERAL:
return self.items[0].value
return "=" + "".join(token.value for token in self.items)
class Token(object):
"""
A token in an Excel formula.
Tokens have three attributes:
* `value`: The string value parsed that led to this token
* `type`: A string identifying the type of token
* `subtype`: A string identifying subtype of the token (optional, and
defaults to "")
"""
__slots__ = ['value', 'type', 'subtype']
LITERAL = "LITERAL"
OPERAND = "OPERAND"
FUNC = "FUNC"
ARRAY = "ARRAY"
PAREN = "PAREN"
SEP = "SEP"
OP_PRE = "OPERATOR-PREFIX"
OP_IN = "OPERATOR-INFIX"
OP_POST = "OPERATOR-POSTFIX"
WSPACE = "WHITE-SPACE"
def __init__(self, value, type_, subtype=""):
self.value = value
self.type = type_
self.subtype = subtype
# Literal operands:
#
# Literal operands are always of type 'OPERAND' and can be of subtype
# 'TEXT' (for text strings), 'NUMBER' (for all numeric types), 'LOGICAL'
# (for TRUE and FALSE), 'ERROR' (for literal error values), or 'RANGE'
# (for all range references).
TEXT = 'TEXT'
NUMBER = 'NUMBER'
LOGICAL = 'LOGICAL'
ERROR = 'ERROR'
RANGE = 'RANGE'
def __repr__(self):
return u"{0} {1} {2}:".format(self.type, self.subtype, self.value)
@classmethod
def make_operand(cls, value):
"""Create an operand token."""
if value.startswith('"'):
subtype = cls.TEXT
elif value.startswith('#'):
subtype = cls.ERROR
elif value in ('TRUE', 'FALSE'):
subtype = cls.LOGICAL
else:
try:
float(value)
subtype = cls.NUMBER
except ValueError:
subtype = cls.RANGE
return cls(value, cls.OPERAND, subtype)
# Subexpresssions
#
# There are 3 types of `Subexpressions`: functions, array literals, and
# parentheticals. Subexpressions have 'OPEN' and 'CLOSE' tokens. 'OPEN'
# is used when parsing the initial expression token (i.e., '(' or '{')
# and 'CLOSE' is used when parsing the closing expression token ('}' or
# ')').
OPEN = "OPEN"
CLOSE = "CLOSE"
@classmethod
def make_subexp(cls, value, func=False):
"""
Create a subexpression token.
`value`: The value of the token
`func`: If True, force the token to be of type FUNC
"""
assert value[-1] in ('{', '}', '(', ')')
if func:
assert re.match('.+\\(|\\)', value)
type_ = Token.FUNC
elif value in '{}':
type_ = Token.ARRAY
elif value in '()':
type_ = Token.PAREN
else:
type_ = Token.FUNC
subtype = cls.CLOSE if value in ')}' else cls.OPEN
return cls(value, type_, subtype)
def get_closer(self):
"""Return a closing token that matches this token's type."""
assert self.type in (self.FUNC, self.ARRAY, self.PAREN)
assert self.subtype == self.OPEN
value = "}" if self.type == self.ARRAY else ")"
return self.make_subexp(value, func=self.type == self.FUNC)
# Separator tokens
#
# Argument separators always have type 'SEP' and can have one of two
# subtypes: 'ARG', 'ROW'. 'ARG' is used for the ',' token, when used to
# delimit either function arguments or array elements. 'ROW' is used for
# the ';' token, which is always used to delimit rows in an array
# literal.
ARG = "ARG"
ROW = "ROW"
@classmethod
def make_separator(cls, value):
"""Create a separator token"""
assert value in (',', ';')
subtype = cls.ARG if value == ',' else cls.ROW
return cls(value, cls.SEP, subtype)

View File

@ -0,0 +1,166 @@
"""
This module contains code to translate formulae across cells in a worksheet.
The idea is that if A1 has formula "=B1+C1", then translating it to cell A2
results in formula "=B2+C2". The algorithm relies on the formula tokenizer
to identify the parts of the formula that need to change.
"""
import re
from .tokenizer import Tokenizer, Token
from openpyxl.utils import (
coordinate_to_tuple,
column_index_from_string,
get_column_letter
)
class TranslatorError(Exception):
"""
Raised when a formula can't be translated across cells.
This error arises when a formula's references would be translated outside
the worksheet's bounds on the top or left. Excel represents these
situations with a #REF! literal error. E.g., if the formula at B2 is
'=A1', attempting to translate the formula to B1 raises TranslatorError,
since there's no cell above A1. Similarly, translating the same formula
from B2 to A2 raises TranslatorError, since there's no cell to the left of
A1.
"""
class Translator(object):
"""
Modifies a formula so that it can be translated from one cell to another.
`formula`: The str string to translate. Must include the leading '='
character.
`origin`: The cell address (in A1 notation) where this formula was
defined (excluding the worksheet name).
"""
def __init__(self, formula, origin):
# Excel errors out when a workbook has formulae in R1C1 notation,
# regardless of the calcPr:refMode setting, so I'm assuming the
# formulae stored in the workbook must be in A1 notation.
self.row, self.col = coordinate_to_tuple(origin)
self.tokenizer = Tokenizer(formula)
def get_tokens(self):
"Returns a list with the tokens comprising the formula."
return self.tokenizer.items
ROW_RANGE_RE = re.compile(r"(\$?[1-9][0-9]{0,6}):(\$?[1-9][0-9]{0,6})$")
COL_RANGE_RE = re.compile(r"(\$?[A-Za-z]{1,3}):(\$?[A-Za-z]{1,3})$")
CELL_REF_RE = re.compile(r"(\$?[A-Za-z]{1,3})(\$?[1-9][0-9]{0,6})$")
@staticmethod
def translate_row(row_str, rdelta):
"""
Translate a range row-snippet by the given number of rows.
"""
if row_str.startswith('$'):
return row_str
else:
new_row = int(row_str) + rdelta
if new_row <= 0:
raise TranslatorError("Formula out of range")
return str(new_row)
@staticmethod
def translate_col(col_str, cdelta):
"""
Translate a range col-snippet by the given number of columns
"""
if col_str.startswith('$'):
return col_str
else:
try:
return get_column_letter(
column_index_from_string(col_str) + cdelta)
except ValueError:
raise TranslatorError("Formula out of range")
@staticmethod
def strip_ws_name(range_str):
"Splits out the worksheet reference, if any, from a range reference."
# This code assumes that named ranges cannot contain any exclamation
# marks. Excel refuses to create these (even using VBA), and
# complains of a corrupt workbook when there are names with
# exclamation marks. The ECMA spec only states that named ranges will
# be of `ST_Xstring` type, which in theory allows '!' (char code
# 0x21) per http://www.w3.org/TR/xml/#charsets
if '!' in range_str:
sheet, range_str = range_str.rsplit('!', 1)
return sheet + "!", range_str
return "", range_str
@classmethod
def translate_range(cls, range_str, rdelta, cdelta):
"""
Translate an A1-style range reference to the destination cell.
`rdelta`: the row offset to add to the range
`cdelta`: the column offset to add to the range
`range_str`: an A1-style reference to a range. Potentially includes
the worksheet reference. Could also be a named range.
"""
ws_part, range_str = cls.strip_ws_name(range_str)
match = cls.ROW_RANGE_RE.match(range_str) # e.g. `3:4`
if match is not None:
return (ws_part + cls.translate_row(match.group(1), rdelta) + ":"
+ cls.translate_row(match.group(2), rdelta))
match = cls.COL_RANGE_RE.match(range_str) # e.g. `A:BC`
if match is not None:
return (ws_part + cls.translate_col(match.group(1), cdelta) + ':'
+ cls.translate_col(match.group(2), cdelta))
if ':' in range_str: # e.g. `A1:B5`
# The check is necessarily general because range references can
# have one or both endpoints specified by named ranges. I.e.,
# `named_range:C2`, `C2:named_range`, and `name1:name2` are all
# valid references. Further, Excel allows chaining multiple
# colons together (with unclear meaning)
return ws_part + ":".join(
cls.translate_range(piece, rdelta, cdelta)
for piece in range_str.split(':'))
match = cls.CELL_REF_RE.match(range_str)
if match is None: # Must be a named range
return range_str
return (ws_part + cls.translate_col(match.group(1), cdelta)
+ cls.translate_row(match.group(2), rdelta))
def translate_formula(self, dest=None, row_delta=0, col_delta=0):
"""
Convert the formula into A1 notation, or as row and column coordinates
The formula is converted into A1 assuming it is assigned to the cell
whose address is `dest` (no worksheet name).
"""
tokens = self.get_tokens()
if not tokens:
return ""
elif tokens[0].type == Token.LITERAL:
return tokens[0].value
out = ['=']
# per the spec:
# A compliant producer or consumer considers a defined name in the
# range A1-XFD1048576 to be an error. All other names outside this
# range can be defined as names and overrides a cell reference if an
# ambiguity exists. (I.18.2.5)
if dest:
row, col = coordinate_to_tuple(dest)
row_delta = row - self.row
col_delta = col - self.col
for token in tokens:
if (token.type == Token.OPERAND
and token.subtype == Token.RANGE):
out.append(self.translate_range(token.value, row_delta,
col_delta))
else:
out.append(token.value)
return "".join(out)

View File

@ -0,0 +1,3 @@
"""
Stuff related to Office OpenXML packaging: relationships, archive, content types.
"""

View File

@ -0,0 +1,112 @@
# Copyright (c) 2010-2022 openpyxl
import datetime
from openpyxl.compat import safe_string
from openpyxl.descriptors import (
String,
DateTime,
Alias,
)
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors.nested import NestedText
from openpyxl.xml.functions import (Element, QName, tostring)
from openpyxl.xml.constants import (
COREPROPS_NS,
DCORE_NS,
XSI_NS,
DCTERMS_NS,
DCTERMS_PREFIX
)
class NestedDateTime(DateTime, NestedText):
expected_type = datetime.datetime
def to_tree(self, tagname=None, value=None, namespace=None):
namespace = getattr(self, "namespace", namespace)
if namespace is not None:
tagname = "{%s}%s" % (namespace, tagname)
el = Element(tagname)
if value is not None:
el.text = value.isoformat(timespec="seconds") + 'Z'
return el
class QualifiedDateTime(NestedDateTime):
"""In certain situations Excel will complain if the additional type
attribute isn't set"""
def to_tree(self, tagname=None, value=None, namespace=None):
el = super(QualifiedDateTime, self).to_tree(tagname, value, namespace)
el.set("{%s}type" % XSI_NS, QName(DCTERMS_NS, "W3CDTF"))
return el
class DocumentProperties(Serialisable):
"""High-level properties of the document.
Defined in ECMA-376 Par2 Annex D
"""
tagname = "coreProperties"
namespace = COREPROPS_NS
category = NestedText(expected_type=str, allow_none=True)
contentStatus = NestedText(expected_type=str, allow_none=True)
keywords = NestedText(expected_type=str, allow_none=True)
lastModifiedBy = NestedText(expected_type=str, allow_none=True)
lastPrinted = NestedDateTime(allow_none=True)
revision = NestedText(expected_type=str, allow_none=True)
version = NestedText(expected_type=str, allow_none=True)
last_modified_by = Alias("lastModifiedBy")
# Dublin Core Properties
subject = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
title = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
creator = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
description = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
identifier = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
language = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
# Dublin Core Terms
created = QualifiedDateTime(allow_none=True, namespace=DCTERMS_NS)
modified = QualifiedDateTime(allow_none=True, namespace=DCTERMS_NS)
__elements__ = ("creator", "title", "description", "subject","identifier",
"language", "created", "modified", "lastModifiedBy", "category",
"contentStatus", "version", "revision", "keywords", "lastPrinted",
)
def __init__(self,
category=None,
contentStatus=None,
keywords=None,
lastModifiedBy=None,
lastPrinted=None,
revision=None,
version=None,
created=datetime.datetime.utcnow(),
creator="openpyxl",
description=None,
identifier=None,
language=None,
modified=datetime.datetime.utcnow(),
subject=None,
title=None,
):
self.contentStatus = contentStatus
self.lastPrinted = lastPrinted
self.revision = revision
self.version = version
self.creator = creator
self.lastModifiedBy = lastModifiedBy
self.modified = modified
self.created = created
self.title = title
self.subject = subject
self.description = description
self.identifier = identifier
self.language = language
self.keywords = keywords
self.category = category

View File

@ -0,0 +1,141 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
)
from openpyxl.descriptors.nested import (
NestedText,
)
from openpyxl.xml.constants import XPROPS_NS
def get_version():
from openpyxl import __version__
VERSION = ".".join(__version__.split(".")[:2])
return VERSION
class DigSigBlob(Serialisable):
__elements__ = __attrs__ = ()
class VectorLpstr(Serialisable):
__elements__ = __attrs__ = ()
class VectorVariant(Serialisable):
__elements__ = __attrs__ = ()
class ExtendedProperties(Serialisable):
"""
See 22.2
Most of this is irrelevant
"""
tagname = "Properties"
Template = NestedText(expected_type=str, allow_none=True)
Manager = NestedText(expected_type=str, allow_none=True)
Company = NestedText(expected_type=str, allow_none=True)
Pages = NestedText(expected_type=int, allow_none=True)
Words = NestedText(expected_type=int,allow_none=True)
Characters = NestedText(expected_type=int, allow_none=True)
PresentationFormat = NestedText(expected_type=str, allow_none=True)
Lines = NestedText(expected_type=int, allow_none=True)
Paragraphs = NestedText(expected_type=int, allow_none=True)
Slides = NestedText(expected_type=int, allow_none=True)
Notes = NestedText(expected_type=int, allow_none=True)
TotalTime = NestedText(expected_type=int, allow_none=True)
HiddenSlides = NestedText(expected_type=int, allow_none=True)
MMClips = NestedText(expected_type=int, allow_none=True)
ScaleCrop = NestedText(expected_type=bool, allow_none=True)
HeadingPairs = Typed(expected_type=VectorVariant, allow_none=True)
TitlesOfParts = Typed(expected_type=VectorLpstr, allow_none=True)
LinksUpToDate = NestedText(expected_type=bool, allow_none=True)
CharactersWithSpaces = NestedText(expected_type=int, allow_none=True)
SharedDoc = NestedText(expected_type=bool, allow_none=True)
HyperlinkBase = NestedText(expected_type=str, allow_none=True)
HLinks = Typed(expected_type=VectorVariant, allow_none=True)
HyperlinksChanged = NestedText(expected_type=bool, allow_none=True)
DigSig = Typed(expected_type=DigSigBlob, allow_none=True)
Application = NestedText(expected_type=str, allow_none=True)
AppVersion = NestedText(expected_type=str, allow_none=True)
DocSecurity = NestedText(expected_type=int, allow_none=True)
__elements__ = ('Application', 'AppVersion', 'DocSecurity', 'ScaleCrop',
'LinksUpToDate', 'SharedDoc', 'HyperlinksChanged')
def __init__(self,
Template=None,
Manager=None,
Company=None,
Pages=None,
Words=None,
Characters=None,
PresentationFormat=None,
Lines=None,
Paragraphs=None,
Slides=None,
Notes=None,
TotalTime=None,
HiddenSlides=None,
MMClips=None,
ScaleCrop=None,
HeadingPairs=None,
TitlesOfParts=None,
LinksUpToDate=None,
CharactersWithSpaces=None,
SharedDoc=None,
HyperlinkBase=None,
HLinks=None,
HyperlinksChanged=None,
DigSig=None,
Application="Microsoft Excel",
AppVersion=None,
DocSecurity=None,
):
self.Template = Template
self.Manager = Manager
self.Company = Company
self.Pages = Pages
self.Words = Words
self.Characters = Characters
self.PresentationFormat = PresentationFormat
self.Lines = Lines
self.Paragraphs = Paragraphs
self.Slides = Slides
self.Notes = Notes
self.TotalTime = TotalTime
self.HiddenSlides = HiddenSlides
self.MMClips = MMClips
self.ScaleCrop = ScaleCrop
self.HeadingPairs = None
self.TitlesOfParts = None
self.LinksUpToDate = LinksUpToDate
self.CharactersWithSpaces = CharactersWithSpaces
self.SharedDoc = SharedDoc
self.HyperlinkBase = HyperlinkBase
self.HLinks = None
self.HyperlinksChanged = HyperlinksChanged
self.DigSig = None
self.Application = Application
if AppVersion is None:
AppVersion = get_version()
self.AppVersion = AppVersion
self.DocSecurity = DocSecurity
def to_tree(self):
tree = super(ExtendedProperties, self).to_tree()
tree.set("xmlns", XPROPS_NS)
return tree

View File

@ -0,0 +1,56 @@
# Copyright (c) 2010-2022 openpyxl
from abc import abstractproperty
from openpyxl.compat.abc import ABC
class ISerialisableFile(ABC):
"""
Interface for Serialisable classes that represent files in the archive
"""
@abstractproperty
def id(self):
"""
Object id making it unique
"""
pass
@abstractproperty
def _path(self):
"""
File path in the archive
"""
pass
@abstractproperty
def _namespace(self):
"""
Qualified namespace when serialised
"""
pass
@abstractproperty
def _type(self):
"""
The content type for the manifest
"""
@abstractproperty
def _rel_type(self):
"""
The content type for relationships
"""
@abstractproperty
def _rel_id(self):
"""
Links object with parent
"""

View File

@ -0,0 +1,210 @@
# Copyright (c) 2010-2022 openpyxl
"""
File manifest
"""
from mimetypes import MimeTypes
import os.path
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import String, Sequence
from openpyxl.xml.functions import fromstring
from openpyxl.xml.constants import (
ARC_CORE,
ARC_CONTENT_TYPES,
ARC_WORKBOOK,
ARC_APP,
ARC_THEME,
ARC_STYLE,
ARC_SHARED_STRINGS,
EXTERNAL_LINK,
THEME_TYPE,
STYLES_TYPE,
XLSX,
XLSM,
XLTM,
XLTX,
WORKSHEET_TYPE,
COMMENTS_TYPE,
SHARED_STRINGS,
DRAWING_TYPE,
CHART_TYPE,
CHARTSHAPE_TYPE,
CHARTSHEET_TYPE,
CONTYPES_NS,
ACTIVEX,
CTRL,
VBA,
)
from openpyxl.xml.functions import tostring
# initialise mime-types
mimetypes = MimeTypes()
mimetypes.add_type('application/xml', ".xml")
mimetypes.add_type('application/vnd.openxmlformats-package.relationships+xml', ".rels")
mimetypes.add_type("application/vnd.ms-office.vbaProject", ".bin")
mimetypes.add_type("application/vnd.openxmlformats-officedocument.vmlDrawing", ".vml")
mimetypes.add_type("image/x-emf", ".emf")
class FileExtension(Serialisable):
tagname = "Default"
Extension = String()
ContentType = String()
def __init__(self, Extension, ContentType):
self.Extension = Extension
self.ContentType = ContentType
class Override(Serialisable):
tagname = "Override"
PartName = String()
ContentType = String()
def __init__(self, PartName, ContentType):
self.PartName = PartName
self.ContentType = ContentType
DEFAULT_TYPES = [
FileExtension("rels", "application/vnd.openxmlformats-package.relationships+xml"),
FileExtension("xml", "application/xml"),
]
DEFAULT_OVERRIDE = [
Override("/" + ARC_STYLE, STYLES_TYPE), # Styles
Override("/" + ARC_THEME, THEME_TYPE), # Theme
Override("/docProps/core.xml", "application/vnd.openxmlformats-package.core-properties+xml"),
Override("/docProps/app.xml", "application/vnd.openxmlformats-officedocument.extended-properties+xml")
]
class Manifest(Serialisable):
tagname = "Types"
Default = Sequence(expected_type=FileExtension, unique=True)
Override = Sequence(expected_type=Override, unique=True)
path = "[Content_Types].xml"
__elements__ = ("Default", "Override")
def __init__(self,
Default=(),
Override=(),
):
if not Default:
Default = DEFAULT_TYPES
self.Default = Default
if not Override:
Override = DEFAULT_OVERRIDE
self.Override = Override
@property
def filenames(self):
return [part.PartName for part in self.Override]
@property
def extensions(self):
"""
Map content types to file extensions
Skip parts without extensions
"""
exts = {os.path.splitext(part.PartName)[-1] for part in self.Override}
return [(ext[1:], mimetypes.types_map[True][ext]) for ext in sorted(exts) if ext]
def to_tree(self):
"""
Custom serialisation method to allow setting a default namespace
"""
defaults = [t.Extension for t in self.Default]
for ext, mime in self.extensions:
if ext not in defaults:
mime = FileExtension(ext, mime)
self.Default.append(mime)
tree = super(Manifest, self).to_tree()
tree.set("xmlns", CONTYPES_NS)
return tree
def __contains__(self, content_type):
"""
Check whether a particular content type is contained
"""
for t in self.Override:
if t.ContentType == content_type:
return True
def find(self, content_type):
"""
Find specific content-type
"""
try:
return next(self.findall(content_type))
except StopIteration:
return
def findall(self, content_type):
"""
Find all elements of a specific content-type
"""
for t in self.Override:
if t.ContentType == content_type:
yield t
def append(self, obj):
"""
Add content object to the package manifest
# needs a contract...
"""
ct = Override(PartName=obj.path, ContentType=obj.mime_type)
self.Override.append(ct)
def _write(self, archive, workbook):
"""
Write manifest to the archive
"""
self.append(workbook)
self._write_vba(workbook)
self._register_mimetypes(filenames=archive.namelist())
archive.writestr(self.path, tostring(self.to_tree()))
def _register_mimetypes(self, filenames):
"""
Make sure that the mime type for all file extensions is registered
"""
for fn in filenames:
ext = os.path.splitext(fn)[-1]
if not ext:
continue
mime = mimetypes.types_map[True][ext]
fe = FileExtension(ext[1:], mime)
self.Default.append(fe)
def _write_vba(self, workbook):
"""
Add content types from cached workbook when keeping VBA
"""
if workbook.vba_archive:
node = fromstring(workbook.vba_archive.read(ARC_CONTENT_TYPES))
mf = Manifest.from_tree(node)
filenames = self.filenames
for override in mf.Override:
if override.PartName not in (ACTIVEX, CTRL, VBA):
continue
if override.PartName not in filenames:
self.Override.append(override)

View File

@ -0,0 +1,176 @@
# Copyright (c) 2010-2022 openpyxl
import posixpath
from openpyxl.descriptors import (
String,
Set,
NoneSet,
Alias,
Sequence,
)
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.xml.constants import REL_NS, PKG_REL_NS
from openpyxl.xml.functions import (
Element,
fromstring,
tostring
)
class Relationship(Serialisable):
"""Represents many kinds of relationships."""
tagname = "Relationship"
Type = String()
Target = String()
target = Alias("Target")
TargetMode = String(allow_none=True)
Id = String(allow_none=True)
id = Alias("Id")
def __init__(self,
Id=None,
Type=None,
type=None,
Target=None,
TargetMode=None
):
"""
`type` can be used as a shorthand with the default relationships namespace
otherwise the `Type` must be a fully qualified URL
"""
if type is not None:
Type = "{0}/{1}".format(REL_NS, type)
self.Type = Type
self.Target = Target
self.TargetMode = TargetMode
self.Id = Id
class RelationshipList(Serialisable):
tagname = "Relationships"
Relationship = Sequence(expected_type=Relationship)
def __init__(self, Relationship=()):
self.Relationship = Relationship
def append(self, value):
values = self.Relationship[:]
values.append(value)
if not value.Id:
value.Id = "rId{0}".format((len(values)))
self.Relationship = values
def __len__(self):
return len(self.Relationship)
def __bool__(self):
return bool(self.Relationship)
def find(self, content_type):
"""
Find relationships by content-type
NB. these content-types namespaced objects and different to the MIME-types
in the package manifest :-(
"""
for r in self.Relationship:
if r.Type == content_type:
yield r
def __getitem__(self, key):
for r in self.Relationship:
if r.Id == key:
return r
raise KeyError("Unknown relationship: {0}".format(key))
def to_tree(self):
tree = Element("Relationships", xmlns=PKG_REL_NS)
for idx, rel in enumerate(self.Relationship, 1):
if not rel.Id:
rel.Id = "rId{0}".format(idx)
tree.append(rel.to_tree())
return tree
def get_rels_path(path):
"""
Convert relative path to absolutes that can be loaded from a zip
archive.
The path to be passed in is that of containing object (workbook,
worksheet, etc.)
"""
folder, obj = posixpath.split(path)
filename = posixpath.join(folder, '_rels', '{0}.rels'.format(obj))
return filename
from warnings import warn
def get_dependents(archive, filename):
"""
Normalise dependency file paths to absolute ones
Relative paths are relative to parent object
"""
src = archive.read(filename)
node = fromstring(src)
try:
rels = RelationshipList.from_tree(node)
except TypeError:
msg = "{0} contains invalid dependency definitions".format(filename)
warn(msg)
rels = RelationshipList()
folder = posixpath.dirname(filename)
parent = posixpath.split(folder)[0]
for r in rels.Relationship:
if r.TargetMode == "External":
continue
elif r.target.startswith("/"):
r.target = r.target[1:]
else:
pth = posixpath.join(parent, r.target)
r.target = posixpath.normpath(pth)
return rels
def get_rel(archive, deps, id=None, cls=None):
"""
Get related object based on id or rel_type
"""
if not any([id, cls]):
raise ValueError("Either the id or the content type are required")
if id is not None:
rel = deps[id]
else:
try:
rel = next(deps.find(cls.rel_type))
except StopIteration: # no known dependency
return
path = rel.target
src = archive.read(path)
tree = fromstring(src)
obj = cls.from_tree(tree)
rels_path = get_rels_path(path)
try:
obj.deps = get_dependents(archive, rels_path)
except KeyError:
obj.deps = []
return obj

View File

@ -0,0 +1,204 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Alias,
Typed,
String,
Integer,
Bool,
NoneSet,
Set,
Sequence,
)
from openpyxl.descriptors.excel import ExtensionList, Relation
from openpyxl.descriptors.sequence import NestedSequence
from openpyxl.descriptors.nested import NestedString
from openpyxl.xml.constants import SHEET_MAIN_NS
from openpyxl.workbook.defined_name import DefinedName, DefinedNameList
from openpyxl.workbook.external_reference import ExternalReference
from openpyxl.workbook.function_group import FunctionGroupList
from openpyxl.workbook.properties import WorkbookProperties, CalcProperties, FileVersion
from openpyxl.workbook.protection import WorkbookProtection, FileSharing
from openpyxl.workbook.smart_tags import SmartTagList, SmartTagProperties
from openpyxl.workbook.views import CustomWorkbookView, BookView
from openpyxl.workbook.web import WebPublishing, WebPublishObjectList
class FileRecoveryProperties(Serialisable):
tagname = "fileRecoveryPr"
autoRecover = Bool(allow_none=True)
crashSave = Bool(allow_none=True)
dataExtractLoad = Bool(allow_none=True)
repairLoad = Bool(allow_none=True)
def __init__(self,
autoRecover=None,
crashSave=None,
dataExtractLoad=None,
repairLoad=None,
):
self.autoRecover = autoRecover
self.crashSave = crashSave
self.dataExtractLoad = dataExtractLoad
self.repairLoad = repairLoad
class ChildSheet(Serialisable):
"""
Represents a reference to a worksheet or chartsheet in workbook.xml
It contains the title, order and state but only an indirect reference to
the objects themselves.
"""
tagname = "sheet"
name = String()
sheetId = Integer()
state = NoneSet(values=(['visible', 'hidden', 'veryHidden']))
id = Relation()
def __init__(self,
name=None,
sheetId=None,
state="visible",
id=None,
):
self.name = name
self.sheetId = sheetId
self.state = state
self.id = id
class PivotCache(Serialisable):
tagname = "pivotCache"
cacheId = Integer()
id = Relation()
def __init__(self,
cacheId=None,
id=None
):
self.cacheId = cacheId
self.id = id
class WorkbookPackage(Serialisable):
"""
Represent the workbook file in the archive
"""
tagname = "workbook"
conformance = NoneSet(values=['strict', 'transitional'])
fileVersion = Typed(expected_type=FileVersion, allow_none=True)
fileSharing = Typed(expected_type=FileSharing, allow_none=True)
workbookPr = Typed(expected_type=WorkbookProperties, allow_none=True)
properties = Alias("workbookPr")
workbookProtection = Typed(expected_type=WorkbookProtection, allow_none=True)
bookViews = NestedSequence(expected_type=BookView)
sheets = NestedSequence(expected_type=ChildSheet)
functionGroups = Typed(expected_type=FunctionGroupList, allow_none=True)
externalReferences = NestedSequence(expected_type=ExternalReference)
definedNames = Typed(expected_type=DefinedNameList, allow_none=True)
calcPr = Typed(expected_type=CalcProperties, allow_none=True)
oleSize = NestedString(allow_none=True, attribute="ref")
customWorkbookViews = NestedSequence(expected_type=CustomWorkbookView)
pivotCaches = NestedSequence(expected_type=PivotCache, allow_none=True)
smartTagPr = Typed(expected_type=SmartTagProperties, allow_none=True)
smartTagTypes = Typed(expected_type=SmartTagList, allow_none=True)
webPublishing = Typed(expected_type=WebPublishing, allow_none=True)
fileRecoveryPr = Typed(expected_type=FileRecoveryProperties, allow_none=True)
webPublishObjects = Typed(expected_type=WebPublishObjectList, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
Ignorable = NestedString(namespace="http://schemas.openxmlformats.org/markup-compatibility/2006", allow_none=True)
__elements__ = ('fileVersion', 'fileSharing', 'workbookPr',
'workbookProtection', 'bookViews', 'sheets', 'functionGroups',
'externalReferences', 'definedNames', 'calcPr', 'oleSize',
'customWorkbookViews', 'pivotCaches', 'smartTagPr', 'smartTagTypes',
'webPublishing', 'fileRecoveryPr', 'webPublishObjects')
def __init__(self,
conformance=None,
fileVersion=None,
fileSharing=None,
workbookPr=None,
workbookProtection=None,
bookViews=(),
sheets=(),
functionGroups=None,
externalReferences=(),
definedNames=None,
calcPr=None,
oleSize=None,
customWorkbookViews=(),
pivotCaches=(),
smartTagPr=None,
smartTagTypes=None,
webPublishing=None,
fileRecoveryPr=None,
webPublishObjects=None,
extLst=None,
Ignorable=None,
):
self.conformance = conformance
self.fileVersion = fileVersion
self.fileSharing = fileSharing
if workbookPr is None:
workbookPr = WorkbookProperties()
self.workbookPr = workbookPr
self.workbookProtection = workbookProtection
self.bookViews = bookViews
self.sheets = sheets
self.functionGroups = functionGroups
self.externalReferences = externalReferences
self.definedNames = definedNames
self.calcPr = calcPr
self.oleSize = oleSize
self.customWorkbookViews = customWorkbookViews
self.pivotCaches = pivotCaches
self.smartTagPr = smartTagPr
self.smartTagTypes = smartTagTypes
self.webPublishing = webPublishing
self.fileRecoveryPr = fileRecoveryPr
self.webPublishObjects = webPublishObjects
def to_tree(self):
tree = super(WorkbookPackage, self).to_tree()
tree.set("xmlns", SHEET_MAIN_NS)
return tree
@property
def active(self):
for view in self.bookViews:
if view.activeTab is not None:
return view.activeTab
return 0
@property
def pivot_caches(self):
"""
Get PivotCache objects
"""
d = {}
for c in self.caches:
cache = get_rel(self.archive, self.rels, id=c.id, cls=CacheDefinition)
if cache.deps:
records = get_rel(self.archive, cache.deps, cache.id, RecordList)
else:
records = None
cache.records = records
d[c.cacheId] = cache
return d

View File

@ -0,0 +1 @@
# Copyright (c) 2010-2022 openpyxl

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,322 @@
# Copyright (c) 2010-2022 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
DateTime,
Bool,
Float,
String,
Integer,
Sequence,
)
from openpyxl.descriptors.excel import HexBinary
class Index(Serialisable):
tagname = "x"
v = Integer(allow_none=True)
def __init__(self,
v=0,
):
self.v = v
class Tuple(Serialisable):
fld = Integer()
hier = Integer()
item = Integer()
def __init__(self,
fld=None,
hier=None,
item=None,
):
self.fld = fld
self.hier = hier
self.item = item
class TupleList(Serialisable):
c = Integer(allow_none=True)
tpl = Typed(expected_type=Tuple, )
__elements__ = ('tpl',)
def __init__(self,
c=None,
tpl=None,
):
self.c = c
self.tpl = tpl
class Missing(Serialisable):
tagname = "m"
tpls = Sequence(expected_type=TupleList)
x = Sequence(expected_type=Index)
u = Bool(allow_none=True)
f = Bool(allow_none=True)
c = String(allow_none=True)
cp = Integer(allow_none=True)
_in = Integer(allow_none=True)
bc = HexBinary(allow_none=True)
fc = HexBinary(allow_none=True)
i = Bool(allow_none=True)
un = Bool(allow_none=True)
st = Bool(allow_none=True)
b = Bool(allow_none=True)
__elements__ = ('tpls', 'x')
def __init__(self,
tpls=(),
x=(),
u=None,
f=None,
c=None,
cp=None,
_in=None,
bc=None,
fc=None,
i=None,
un=None,
st=None,
b=None,
):
self.tpls = tpls
self.x = x
self.u = u
self.f = f
self.c = c
self.cp = cp
self._in = _in
self.bc = bc
self.fc = fc
self.i = i
self.un = un
self.st = st
self.b = b
class Number(Serialisable):
tagname = "n"
tpls = Sequence(expected_type=TupleList)
x = Sequence(expected_type=Index)
v = Float()
u = Bool(allow_none=True)
f = Bool(allow_none=True)
c = String(allow_none=True)
cp = Integer(allow_none=True)
_in = Integer(allow_none=True)
bc = HexBinary(allow_none=True)
fc = HexBinary(allow_none=True)
i = Bool(allow_none=True)
un = Bool(allow_none=True)
st = Bool(allow_none=True)
b = Bool(allow_none=True)
__elements__ = ('tpls', 'x')
def __init__(self,
tpls=(),
x=(),
v=None,
u=None,
f=None,
c=None,
cp=None,
_in=None,
bc=None,
fc=None,
i=None,
un=None,
st=None,
b=None,
):
self.tpls = tpls
self.x = x
self.v = v
self.u = u
self.f = f
self.c = c
self.cp = cp
self._in = _in
self.bc = bc
self.fc = fc
self.i = i
self.un = un
self.st = st
self.b = b
class Error(Serialisable):
tagname = "e"
tpls = Typed(expected_type=TupleList, allow_none=True)
x = Sequence(expected_type=Index)
v = String()
u = Bool(allow_none=True)
f = Bool(allow_none=True)
c = String(allow_none=True)
cp = Integer(allow_none=True)
_in = Integer(allow_none=True)
bc = HexBinary(allow_none=True)
fc = HexBinary(allow_none=True)
i = Bool(allow_none=True)
un = Bool(allow_none=True)
st = Bool(allow_none=True)
b = Bool(allow_none=True)
__elements__ = ('tpls', 'x')
def __init__(self,
tpls=None,
x=(),
v=None,
u=None,
f=None,
c=None,
cp=None,
_in=None,
bc=None,
fc=None,
i=None,
un=None,
st=None,
b=None,
):
self.tpls = tpls
self.x = x
self.v = v
self.u = u
self.f = f
self.c = c
self.cp = cp
self._in = _in
self.bc = bc
self.fc = fc
self.i = i
self.un = un
self.st = st
self.b = b
class Boolean(Serialisable):
tagname = "b"
x = Sequence(expected_type=Index)
v = Bool()
u = Bool(allow_none=True)
f = Bool(allow_none=True)
c = String(allow_none=True)
cp = Integer(allow_none=True)
__elements__ = ('x',)
def __init__(self,
x=(),
v=None,
u=None,
f=None,
c=None,
cp=None,
):
self.x = x
self.v = v
self.u = u
self.f = f
self.c = c
self.cp = cp
class Text(Serialisable):
tagname = "s"
tpls = Sequence(expected_type=TupleList)
x = Sequence(expected_type=Index)
v = String()
u = Bool(allow_none=True)
f = Bool(allow_none=True)
c = String(allow_none=True)
cp = Integer(allow_none=True)
_in = Integer(allow_none=True)
bc = HexBinary(allow_none=True)
fc = HexBinary(allow_none=True)
i = Bool(allow_none=True)
un = Bool(allow_none=True)
st = Bool(allow_none=True)
b = Bool(allow_none=True)
__elements__ = ('tpls', 'x')
def __init__(self,
tpls=(),
x=(),
v=None,
u=None,
f=None,
c=None,
cp=None,
_in=None,
bc=None,
fc=None,
i=None,
un=None,
st=None,
b=None,
):
self.tpls = tpls
self.x = x
self.v = v
self.u = u
self.f = f
self.c = c
self.cp = cp
self._in = _in
self.bc = bc
self.fc = fc
self.i = i
self.un = un
self.st = st
self.b = b
class DateTimeField(Serialisable):
tagname = "d"
x = Sequence(expected_type=Index)
v = DateTime()
u = Bool(allow_none=True)
f = Bool(allow_none=True)
c = String(allow_none=True)
cp = Integer(allow_none=True)
__elements__ = ('x',)
def __init__(self,
x=(),
v=None,
u=None,
f=None,
c=None,
cp=None,
):
self.x = x
self.v = v
self.u = u
self.f = f
self.c = c
self.cp = cp

Some files were not shown because too many files have changed in this diff Show More