initial commit
This commit is contained in:
7
billinglayer/python/xlsxwriter/__init__.py
Normal file
7
billinglayer/python/xlsxwriter/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
__version__ = "3.1.2"
|
||||
__VERSION__ = __version__
|
||||
from .workbook import Workbook # noqa
|
||||
Binary file not shown.
BIN
billinglayer/python/xlsxwriter/__pycache__/app.cpython-311.pyc
Normal file
BIN
billinglayer/python/xlsxwriter/__pycache__/app.cpython-311.pyc
Normal file
Binary file not shown.
BIN
billinglayer/python/xlsxwriter/__pycache__/chart.cpython-311.pyc
Normal file
BIN
billinglayer/python/xlsxwriter/__pycache__/chart.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
billinglayer/python/xlsxwriter/__pycache__/core.cpython-311.pyc
Normal file
BIN
billinglayer/python/xlsxwriter/__pycache__/core.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
billinglayer/python/xlsxwriter/__pycache__/shape.cpython-311.pyc
Normal file
BIN
billinglayer/python/xlsxwriter/__pycache__/shape.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
billinglayer/python/xlsxwriter/__pycache__/table.cpython-311.pyc
Normal file
BIN
billinglayer/python/xlsxwriter/__pycache__/table.cpython-311.pyc
Normal file
Binary file not shown.
BIN
billinglayer/python/xlsxwriter/__pycache__/theme.cpython-311.pyc
Normal file
BIN
billinglayer/python/xlsxwriter/__pycache__/theme.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
billinglayer/python/xlsxwriter/__pycache__/vml.cpython-311.pyc
Normal file
BIN
billinglayer/python/xlsxwriter/__pycache__/vml.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
198
billinglayer/python/xlsxwriter/app.py
Normal file
198
billinglayer/python/xlsxwriter/app.py
Normal file
@@ -0,0 +1,198 @@
|
||||
###############################################################################
|
||||
#
|
||||
# App - A class for writing the Excel XLSX App file.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
# Package imports.
|
||||
from . import xmlwriter
|
||||
|
||||
|
||||
class App(xmlwriter.XMLwriter):
|
||||
"""
|
||||
A class for writing the Excel XLSX App file.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
|
||||
super(App, self).__init__()
|
||||
|
||||
self.part_names = []
|
||||
self.heading_pairs = []
|
||||
self.properties = {}
|
||||
self.doc_security = 0
|
||||
|
||||
def _add_part_name(self, part_name):
|
||||
# Add the name of a workbook Part such as 'Sheet1' or 'Print_Titles'.
|
||||
self.part_names.append(part_name)
|
||||
|
||||
def _add_heading_pair(self, heading_pair):
|
||||
# Add the name of a workbook Heading Pair such as 'Worksheets',
|
||||
# 'Charts' or 'Named Ranges'.
|
||||
|
||||
# Ignore empty pairs such as chartsheets.
|
||||
if not heading_pair[1]:
|
||||
return
|
||||
|
||||
self.heading_pairs.append(("lpstr", heading_pair[0]))
|
||||
self.heading_pairs.append(("i4", heading_pair[1]))
|
||||
|
||||
def _set_properties(self, properties):
|
||||
# Set the document properties.
|
||||
self.properties = properties
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _assemble_xml_file(self):
|
||||
# Assemble and write the XML file.
|
||||
|
||||
# Write the XML declaration.
|
||||
self._xml_declaration()
|
||||
|
||||
self._write_properties()
|
||||
self._write_application()
|
||||
self._write_doc_security()
|
||||
self._write_scale_crop()
|
||||
self._write_heading_pairs()
|
||||
self._write_titles_of_parts()
|
||||
self._write_manager()
|
||||
self._write_company()
|
||||
self._write_links_up_to_date()
|
||||
self._write_shared_doc()
|
||||
self._write_hyperlink_base()
|
||||
self._write_hyperlinks_changed()
|
||||
self._write_app_version()
|
||||
|
||||
self._xml_end_tag("Properties")
|
||||
|
||||
# Close the file.
|
||||
self._xml_close()
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_properties(self):
|
||||
# Write the <Properties> element.
|
||||
schema = "http://schemas.openxmlformats.org/officeDocument/2006/"
|
||||
xmlns = schema + "extended-properties"
|
||||
xmlns_vt = schema + "docPropsVTypes"
|
||||
|
||||
attributes = [
|
||||
("xmlns", xmlns),
|
||||
("xmlns:vt", xmlns_vt),
|
||||
]
|
||||
|
||||
self._xml_start_tag("Properties", attributes)
|
||||
|
||||
def _write_application(self):
|
||||
# Write the <Application> element.
|
||||
self._xml_data_element("Application", "Microsoft Excel")
|
||||
|
||||
def _write_doc_security(self):
|
||||
# Write the <DocSecurity> element.
|
||||
self._xml_data_element("DocSecurity", self.doc_security)
|
||||
|
||||
def _write_scale_crop(self):
|
||||
# Write the <ScaleCrop> element.
|
||||
self._xml_data_element("ScaleCrop", "false")
|
||||
|
||||
def _write_heading_pairs(self):
|
||||
# Write the <HeadingPairs> element.
|
||||
self._xml_start_tag("HeadingPairs")
|
||||
self._write_vt_vector("variant", self.heading_pairs)
|
||||
self._xml_end_tag("HeadingPairs")
|
||||
|
||||
def _write_titles_of_parts(self):
|
||||
# Write the <TitlesOfParts> element.
|
||||
parts_data = []
|
||||
|
||||
self._xml_start_tag("TitlesOfParts")
|
||||
|
||||
for part_name in self.part_names:
|
||||
parts_data.append(("lpstr", part_name))
|
||||
|
||||
self._write_vt_vector("lpstr", parts_data)
|
||||
|
||||
self._xml_end_tag("TitlesOfParts")
|
||||
|
||||
def _write_vt_vector(self, base_type, vector_data):
|
||||
# Write the <vt:vector> element.
|
||||
attributes = [
|
||||
("size", len(vector_data)),
|
||||
("baseType", base_type),
|
||||
]
|
||||
|
||||
self._xml_start_tag("vt:vector", attributes)
|
||||
|
||||
for vt_data in vector_data:
|
||||
if base_type == "variant":
|
||||
self._xml_start_tag("vt:variant")
|
||||
|
||||
self._write_vt_data(vt_data)
|
||||
|
||||
if base_type == "variant":
|
||||
self._xml_end_tag("vt:variant")
|
||||
|
||||
self._xml_end_tag("vt:vector")
|
||||
|
||||
def _write_vt_data(self, vt_data):
|
||||
# Write the <vt:*> elements such as <vt:lpstr> and <vt:if>.
|
||||
self._xml_data_element("vt:%s" % vt_data[0], vt_data[1])
|
||||
|
||||
def _write_company(self):
|
||||
company = self.properties.get("company", "")
|
||||
|
||||
self._xml_data_element("Company", company)
|
||||
|
||||
def _write_manager(self):
|
||||
# Write the <Manager> element.
|
||||
if "manager" not in self.properties:
|
||||
return
|
||||
|
||||
self._xml_data_element("Manager", self.properties["manager"])
|
||||
|
||||
def _write_links_up_to_date(self):
|
||||
# Write the <LinksUpToDate> element.
|
||||
self._xml_data_element("LinksUpToDate", "false")
|
||||
|
||||
def _write_shared_doc(self):
|
||||
# Write the <SharedDoc> element.
|
||||
self._xml_data_element("SharedDoc", "false")
|
||||
|
||||
def _write_hyperlink_base(self):
|
||||
# Write the <HyperlinkBase> element.
|
||||
hyperlink_base = self.properties.get("hyperlink_base")
|
||||
|
||||
if hyperlink_base is None:
|
||||
return
|
||||
|
||||
self._xml_data_element("HyperlinkBase", hyperlink_base)
|
||||
|
||||
def _write_hyperlinks_changed(self):
|
||||
# Write the <HyperlinksChanged> element.
|
||||
self._xml_data_element("HyperlinksChanged", "false")
|
||||
|
||||
def _write_app_version(self):
|
||||
# Write the <AppVersion> element.
|
||||
self._xml_data_element("AppVersion", "12.0000")
|
||||
4278
billinglayer/python/xlsxwriter/chart.py
Normal file
4278
billinglayer/python/xlsxwriter/chart.py
Normal file
File diff suppressed because it is too large
Load Diff
101
billinglayer/python/xlsxwriter/chart_area.py
Normal file
101
billinglayer/python/xlsxwriter/chart_area.py
Normal file
@@ -0,0 +1,101 @@
|
||||
###############################################################################
|
||||
#
|
||||
# ChartArea - A class for writing the Excel XLSX Area charts.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
from . import chart
|
||||
|
||||
|
||||
class ChartArea(chart.Chart):
|
||||
"""
|
||||
A class for writing the Excel XLSX Area charts.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self, options=None):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
super(ChartArea, self).__init__()
|
||||
|
||||
if options is None:
|
||||
options = {}
|
||||
|
||||
self.subtype = options.get("subtype")
|
||||
|
||||
if not self.subtype:
|
||||
self.subtype = "standard"
|
||||
|
||||
self.cross_between = "midCat"
|
||||
self.show_crosses = 0
|
||||
|
||||
# Override and reset the default axis values.
|
||||
if self.subtype == "percent_stacked":
|
||||
self.y_axis["defaults"]["num_format"] = "0%"
|
||||
|
||||
# Set the available data label positions for this chart type.
|
||||
self.label_position_default = "center"
|
||||
self.label_positions = {"center": "ctr"}
|
||||
|
||||
self.set_y_axis({})
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_chart_type(self, args):
|
||||
# Override the virtual superclass method with a chart specific method.
|
||||
# Write the c:areaChart element.
|
||||
self._write_area_chart(args)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
#
|
||||
def _write_area_chart(self, args):
|
||||
# Write the <c:areaChart> element.
|
||||
|
||||
if args["primary_axes"]:
|
||||
series = self._get_primary_axes_series()
|
||||
else:
|
||||
series = self._get_secondary_axes_series()
|
||||
|
||||
if not len(series):
|
||||
return
|
||||
|
||||
subtype = self.subtype
|
||||
|
||||
if subtype == "percent_stacked":
|
||||
subtype = "percentStacked"
|
||||
|
||||
self._xml_start_tag("c:areaChart")
|
||||
|
||||
# Write the c:grouping element.
|
||||
self._write_grouping(subtype)
|
||||
|
||||
# Write the series elements.
|
||||
for data in series:
|
||||
self._write_ser(data)
|
||||
|
||||
# Write the c:dropLines element.
|
||||
self._write_drop_lines()
|
||||
|
||||
# Write the c:axId elements
|
||||
self._write_axis_ids(args)
|
||||
|
||||
self._xml_end_tag("c:areaChart")
|
||||
173
billinglayer/python/xlsxwriter/chart_bar.py
Normal file
173
billinglayer/python/xlsxwriter/chart_bar.py
Normal file
@@ -0,0 +1,173 @@
|
||||
###############################################################################
|
||||
#
|
||||
# ChartBar - A class for writing the Excel XLSX Bar charts.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
from . import chart
|
||||
from warnings import warn
|
||||
|
||||
|
||||
class ChartBar(chart.Chart):
|
||||
"""
|
||||
A class for writing the Excel XLSX Bar charts.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self, options=None):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
super(ChartBar, self).__init__()
|
||||
|
||||
if options is None:
|
||||
options = {}
|
||||
|
||||
self.subtype = options.get("subtype")
|
||||
|
||||
if not self.subtype:
|
||||
self.subtype = "clustered"
|
||||
|
||||
self.cat_axis_position = "l"
|
||||
self.val_axis_position = "b"
|
||||
self.horiz_val_axis = 0
|
||||
self.horiz_cat_axis = 1
|
||||
self.show_crosses = 0
|
||||
|
||||
# Override and reset the default axis values.
|
||||
self.x_axis["defaults"]["major_gridlines"] = {"visible": 1}
|
||||
self.y_axis["defaults"]["major_gridlines"] = {"visible": 0}
|
||||
|
||||
if self.subtype == "percent_stacked":
|
||||
self.x_axis["defaults"]["num_format"] = "0%"
|
||||
|
||||
# Set the available data label positions for this chart type.
|
||||
self.label_position_default = "outside_end"
|
||||
self.label_positions = {
|
||||
"center": "ctr",
|
||||
"inside_base": "inBase",
|
||||
"inside_end": "inEnd",
|
||||
"outside_end": "outEnd",
|
||||
}
|
||||
|
||||
self.set_x_axis({})
|
||||
self.set_y_axis({})
|
||||
|
||||
def combine(self, chart=None):
|
||||
"""
|
||||
Create a combination chart with a secondary chart.
|
||||
|
||||
Note: Override parent method to add an extra check that is required
|
||||
for Bar charts to ensure that their combined chart is on a secondary
|
||||
axis.
|
||||
|
||||
Args:
|
||||
chart: The secondary chart to combine with the primary chart.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
|
||||
"""
|
||||
if chart is None:
|
||||
return
|
||||
|
||||
if not chart.is_secondary:
|
||||
warn("Charts combined with Bar charts must be on a secondary axis")
|
||||
|
||||
self.combined = chart
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_chart_type(self, args):
|
||||
# Override the virtual superclass method with a chart specific method.
|
||||
if args["primary_axes"]:
|
||||
# Reverse X and Y axes for Bar charts.
|
||||
tmp = self.y_axis
|
||||
self.y_axis = self.x_axis
|
||||
self.x_axis = tmp
|
||||
|
||||
if self.y2_axis["position"] == "r":
|
||||
self.y2_axis["position"] = "t"
|
||||
|
||||
# Write the c:barChart element.
|
||||
self._write_bar_chart(args)
|
||||
|
||||
def _write_bar_chart(self, args):
|
||||
# Write the <c:barChart> element.
|
||||
|
||||
if args["primary_axes"]:
|
||||
series = self._get_primary_axes_series()
|
||||
else:
|
||||
series = self._get_secondary_axes_series()
|
||||
|
||||
if not len(series):
|
||||
return
|
||||
|
||||
subtype = self.subtype
|
||||
if subtype == "percent_stacked":
|
||||
subtype = "percentStacked"
|
||||
|
||||
# Set a default overlap for stacked charts.
|
||||
if "stacked" in self.subtype and self.series_overlap_1 is None:
|
||||
self.series_overlap_1 = 100
|
||||
|
||||
self._xml_start_tag("c:barChart")
|
||||
|
||||
# Write the c:barDir element.
|
||||
self._write_bar_dir()
|
||||
|
||||
# Write the c:grouping element.
|
||||
self._write_grouping(subtype)
|
||||
|
||||
# Write the c:ser elements.
|
||||
for data in series:
|
||||
self._write_ser(data)
|
||||
|
||||
# Write the c:gapWidth element.
|
||||
if args["primary_axes"]:
|
||||
self._write_gap_width(self.series_gap_1)
|
||||
else:
|
||||
self._write_gap_width(self.series_gap_2)
|
||||
|
||||
# Write the c:overlap element.
|
||||
if args["primary_axes"]:
|
||||
self._write_overlap(self.series_overlap_1)
|
||||
else:
|
||||
self._write_overlap(self.series_overlap_2)
|
||||
|
||||
# Write the c:axId elements
|
||||
self._write_axis_ids(args)
|
||||
|
||||
self._xml_end_tag("c:barChart")
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_bar_dir(self):
|
||||
# Write the <c:barDir> element.
|
||||
val = "bar"
|
||||
|
||||
attributes = [("val", val)]
|
||||
|
||||
self._xml_empty_tag("c:barDir", attributes)
|
||||
|
||||
def _write_err_dir(self, val):
|
||||
# Overridden from Chart class since it is not used in Bar charts.
|
||||
pass
|
||||
132
billinglayer/python/xlsxwriter/chart_column.py
Normal file
132
billinglayer/python/xlsxwriter/chart_column.py
Normal file
@@ -0,0 +1,132 @@
|
||||
###############################################################################
|
||||
#
|
||||
# ChartColumn - A class for writing the Excel XLSX Column charts.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
from . import chart
|
||||
|
||||
|
||||
class ChartColumn(chart.Chart):
|
||||
"""
|
||||
A class for writing the Excel XLSX Column charts.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self, options=None):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
super(ChartColumn, self).__init__()
|
||||
|
||||
if options is None:
|
||||
options = {}
|
||||
|
||||
self.subtype = options.get("subtype")
|
||||
|
||||
if not self.subtype:
|
||||
self.subtype = "clustered"
|
||||
|
||||
self.horiz_val_axis = 0
|
||||
|
||||
if self.subtype == "percent_stacked":
|
||||
self.y_axis["defaults"]["num_format"] = "0%"
|
||||
|
||||
# Set the available data label positions for this chart type.
|
||||
self.label_position_default = "outside_end"
|
||||
self.label_positions = {
|
||||
"center": "ctr",
|
||||
"inside_base": "inBase",
|
||||
"inside_end": "inEnd",
|
||||
"outside_end": "outEnd",
|
||||
}
|
||||
|
||||
self.set_y_axis({})
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_chart_type(self, args):
|
||||
# Override the virtual superclass method with a chart specific method.
|
||||
|
||||
# Write the c:barChart element.
|
||||
self._write_bar_chart(args)
|
||||
|
||||
def _write_bar_chart(self, args):
|
||||
# Write the <c:barChart> element.
|
||||
|
||||
if args["primary_axes"]:
|
||||
series = self._get_primary_axes_series()
|
||||
else:
|
||||
series = self._get_secondary_axes_series()
|
||||
|
||||
if not len(series):
|
||||
return
|
||||
|
||||
subtype = self.subtype
|
||||
if subtype == "percent_stacked":
|
||||
subtype = "percentStacked"
|
||||
|
||||
# Set a default overlap for stacked charts.
|
||||
if "stacked" in self.subtype and self.series_overlap_1 is None:
|
||||
self.series_overlap_1 = 100
|
||||
|
||||
self._xml_start_tag("c:barChart")
|
||||
|
||||
# Write the c:barDir element.
|
||||
self._write_bar_dir()
|
||||
|
||||
# Write the c:grouping element.
|
||||
self._write_grouping(subtype)
|
||||
|
||||
# Write the c:ser elements.
|
||||
for data in series:
|
||||
self._write_ser(data)
|
||||
|
||||
# Write the c:gapWidth element.
|
||||
if args["primary_axes"]:
|
||||
self._write_gap_width(self.series_gap_1)
|
||||
else:
|
||||
self._write_gap_width(self.series_gap_2)
|
||||
|
||||
# Write the c:overlap element.
|
||||
if args["primary_axes"]:
|
||||
self._write_overlap(self.series_overlap_1)
|
||||
else:
|
||||
self._write_overlap(self.series_overlap_2)
|
||||
|
||||
# Write the c:axId elements
|
||||
self._write_axis_ids(args)
|
||||
|
||||
self._xml_end_tag("c:barChart")
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_bar_dir(self):
|
||||
# Write the <c:barDir> element.
|
||||
val = "col"
|
||||
|
||||
attributes = [("val", val)]
|
||||
|
||||
self._xml_empty_tag("c:barDir", attributes)
|
||||
|
||||
def _write_err_dir(self, val):
|
||||
# Overridden from Chart class since it is not used in Column charts.
|
||||
pass
|
||||
99
billinglayer/python/xlsxwriter/chart_doughnut.py
Normal file
99
billinglayer/python/xlsxwriter/chart_doughnut.py
Normal file
@@ -0,0 +1,99 @@
|
||||
###############################################################################
|
||||
#
|
||||
# ChartDoughnut - A class for writing the Excel XLSX Doughnut charts.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
from warnings import warn
|
||||
from . import chart_pie
|
||||
|
||||
|
||||
class ChartDoughnut(chart_pie.ChartPie):
|
||||
"""
|
||||
A class for writing the Excel XLSX Doughnut charts.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self, options=None):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
super(ChartDoughnut, self).__init__()
|
||||
|
||||
self.vary_data_color = 1
|
||||
self.rotation = 0
|
||||
self.hole_size = 50
|
||||
|
||||
def set_hole_size(self, size):
|
||||
"""
|
||||
Set the Doughnut chart hole size.
|
||||
|
||||
Args:
|
||||
size: 10 <= size <= 90.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
|
||||
"""
|
||||
if size is None:
|
||||
return
|
||||
|
||||
# Ensure the size is in Excel's range.
|
||||
if size < 10 or size > 90:
|
||||
warn("Chart hole size %d outside Excel range: 10 <= size <= 90" % size)
|
||||
return
|
||||
|
||||
self.hole_size = int(size)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_chart_type(self, args):
|
||||
# Override the virtual superclass method with a chart specific method.
|
||||
# Write the c:doughnutChart element.
|
||||
self._write_doughnut_chart(args)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_doughnut_chart(self, args):
|
||||
# Write the <c:doughnutChart> element. Over-ridden method to remove
|
||||
# axis_id code since Doughnut charts don't require val and cat axes.
|
||||
self._xml_start_tag("c:doughnutChart")
|
||||
|
||||
# Write the c:varyColors element.
|
||||
self._write_vary_colors()
|
||||
|
||||
# Write the series elements.
|
||||
for data in self.series:
|
||||
self._write_ser(data)
|
||||
|
||||
# Write the c:firstSliceAng element.
|
||||
self._write_first_slice_ang()
|
||||
|
||||
# Write the c:holeSize element.
|
||||
self._write_c_hole_size()
|
||||
|
||||
self._xml_end_tag("c:doughnutChart")
|
||||
|
||||
def _write_c_hole_size(self):
|
||||
# Write the <c:holeSize> element.
|
||||
attributes = [("val", self.hole_size)]
|
||||
|
||||
self._xml_empty_tag("c:holeSize", attributes)
|
||||
143
billinglayer/python/xlsxwriter/chart_line.py
Normal file
143
billinglayer/python/xlsxwriter/chart_line.py
Normal file
@@ -0,0 +1,143 @@
|
||||
###############################################################################
|
||||
#
|
||||
# ChartLine - A class for writing the Excel XLSX Line charts.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
from . import chart
|
||||
|
||||
|
||||
class ChartLine(chart.Chart):
|
||||
"""
|
||||
A class for writing the Excel XLSX Line charts.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self, options=None):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
super(ChartLine, self).__init__()
|
||||
|
||||
if options is None:
|
||||
options = {}
|
||||
|
||||
self.subtype = options.get("subtype")
|
||||
|
||||
if not self.subtype:
|
||||
self.subtype = "standard"
|
||||
|
||||
self.default_marker = {"type": "none"}
|
||||
self.smooth_allowed = True
|
||||
|
||||
# Override and reset the default axis values.
|
||||
if self.subtype == "percent_stacked":
|
||||
self.y_axis["defaults"]["num_format"] = "0%"
|
||||
|
||||
# Set the available data label positions for this chart type.
|
||||
self.label_position_default = "right"
|
||||
self.label_positions = {
|
||||
"center": "ctr",
|
||||
"right": "r",
|
||||
"left": "l",
|
||||
"above": "t",
|
||||
"below": "b",
|
||||
# For backward compatibility.
|
||||
"top": "t",
|
||||
"bottom": "b",
|
||||
}
|
||||
|
||||
self.set_y_axis({})
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_chart_type(self, args):
|
||||
# Override the virtual superclass method with a chart specific method.
|
||||
# Write the c:lineChart element.
|
||||
self._write_line_chart(args)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_line_chart(self, args):
|
||||
# Write the <c:lineChart> element.
|
||||
|
||||
if args["primary_axes"]:
|
||||
series = self._get_primary_axes_series()
|
||||
else:
|
||||
series = self._get_secondary_axes_series()
|
||||
|
||||
if not len(series):
|
||||
return
|
||||
|
||||
subtype = self.subtype
|
||||
|
||||
if subtype == "percent_stacked":
|
||||
subtype = "percentStacked"
|
||||
|
||||
self._xml_start_tag("c:lineChart")
|
||||
|
||||
# Write the c:grouping element.
|
||||
self._write_grouping(subtype)
|
||||
|
||||
# Write the series elements.
|
||||
for data in series:
|
||||
self._write_ser(data)
|
||||
|
||||
# Write the c:dropLines element.
|
||||
self._write_drop_lines()
|
||||
|
||||
# Write the c:hiLowLines element.
|
||||
self._write_hi_low_lines()
|
||||
|
||||
# Write the c:upDownBars element.
|
||||
self._write_up_down_bars()
|
||||
|
||||
# Write the c:marker element.
|
||||
self._write_marker_value()
|
||||
|
||||
# Write the c:axId elements
|
||||
self._write_axis_ids(args)
|
||||
|
||||
self._xml_end_tag("c:lineChart")
|
||||
|
||||
def _write_d_pt_point(self, index, point):
|
||||
# Write an individual <c:dPt> element. Override the parent method to
|
||||
# add markers.
|
||||
|
||||
self._xml_start_tag("c:dPt")
|
||||
|
||||
# Write the c:idx element.
|
||||
self._write_idx(index)
|
||||
|
||||
self._xml_start_tag("c:marker")
|
||||
|
||||
# Write the c:spPr element.
|
||||
self._write_sp_pr(point)
|
||||
|
||||
self._xml_end_tag("c:marker")
|
||||
|
||||
self._xml_end_tag("c:dPt")
|
||||
|
||||
def _write_marker_value(self):
|
||||
# Write the <c:marker> element without a sub-element.
|
||||
attributes = [("val", 1)]
|
||||
|
||||
self._xml_empty_tag("c:marker", attributes)
|
||||
251
billinglayer/python/xlsxwriter/chart_pie.py
Normal file
251
billinglayer/python/xlsxwriter/chart_pie.py
Normal file
@@ -0,0 +1,251 @@
|
||||
###############################################################################
|
||||
#
|
||||
# ChartPie - A class for writing the Excel XLSX Pie charts.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
from warnings import warn
|
||||
from . import chart
|
||||
|
||||
|
||||
class ChartPie(chart.Chart):
|
||||
"""
|
||||
A class for writing the Excel XLSX Pie charts.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self, options=None):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
super(ChartPie, self).__init__()
|
||||
|
||||
self.vary_data_color = 1
|
||||
self.rotation = 0
|
||||
|
||||
# Set the available data label positions for this chart type.
|
||||
self.label_position_default = "best_fit"
|
||||
self.label_positions = {
|
||||
"center": "ctr",
|
||||
"inside_end": "inEnd",
|
||||
"outside_end": "outEnd",
|
||||
"best_fit": "bestFit",
|
||||
}
|
||||
|
||||
def set_rotation(self, rotation):
|
||||
"""
|
||||
Set the Pie/Doughnut chart rotation: the angle of the first slice.
|
||||
|
||||
Args:
|
||||
rotation: First segment angle: 0 <= rotation <= 360.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
|
||||
"""
|
||||
if rotation is None:
|
||||
return
|
||||
|
||||
# Ensure the rotation is in Excel's range.
|
||||
if rotation < 0 or rotation > 360:
|
||||
warn(
|
||||
"Chart rotation %d outside Excel range: 0 <= rotation <= 360" % rotation
|
||||
)
|
||||
return
|
||||
|
||||
self.rotation = int(rotation)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_chart_type(self, args):
|
||||
# Override the virtual superclass method with a chart specific method.
|
||||
# Write the c:pieChart element.
|
||||
self._write_pie_chart(args)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_pie_chart(self, args):
|
||||
# Write the <c:pieChart> element. Over-ridden method to remove
|
||||
# axis_id code since Pie charts don't require val and cat axes.
|
||||
self._xml_start_tag("c:pieChart")
|
||||
|
||||
# Write the c:varyColors element.
|
||||
self._write_vary_colors()
|
||||
|
||||
# Write the series elements.
|
||||
for data in self.series:
|
||||
self._write_ser(data)
|
||||
|
||||
# Write the c:firstSliceAng element.
|
||||
self._write_first_slice_ang()
|
||||
|
||||
self._xml_end_tag("c:pieChart")
|
||||
|
||||
def _write_plot_area(self):
|
||||
# Over-ridden method to remove the cat_axis() and val_axis() code
|
||||
# since Pie charts don't require those axes.
|
||||
#
|
||||
# Write the <c:plotArea> element.
|
||||
|
||||
self._xml_start_tag("c:plotArea")
|
||||
|
||||
# Write the c:layout element.
|
||||
self._write_layout(self.plotarea.get("layout"), "plot")
|
||||
|
||||
# Write the subclass chart type element.
|
||||
self._write_chart_type(None)
|
||||
# Configure a combined chart if present.
|
||||
second_chart = self.combined
|
||||
|
||||
if second_chart:
|
||||
# Secondary axis has unique id otherwise use same as primary.
|
||||
if second_chart.is_secondary:
|
||||
second_chart.id = 1000 + self.id
|
||||
else:
|
||||
second_chart.id = self.id
|
||||
|
||||
# Share the same filehandle for writing.
|
||||
second_chart.fh = self.fh
|
||||
|
||||
# Share series index with primary chart.
|
||||
second_chart.series_index = self.series_index
|
||||
|
||||
# Write the subclass chart type elements for combined chart.
|
||||
second_chart._write_chart_type(None)
|
||||
|
||||
# Write the c:spPr element for the plotarea formatting.
|
||||
self._write_sp_pr(self.plotarea)
|
||||
|
||||
self._xml_end_tag("c:plotArea")
|
||||
|
||||
def _write_legend(self):
|
||||
# Over-ridden method to add <c:txPr> to legend.
|
||||
# Write the <c:legend> element.
|
||||
legend = self.legend
|
||||
position = legend.get("position", "right")
|
||||
font = legend.get("font")
|
||||
delete_series = []
|
||||
overlay = 0
|
||||
|
||||
if legend.get("delete_series") and type(legend["delete_series"]) is list:
|
||||
delete_series = legend["delete_series"]
|
||||
|
||||
if position.startswith("overlay_"):
|
||||
position = position.replace("overlay_", "")
|
||||
overlay = 1
|
||||
|
||||
allowed = {
|
||||
"right": "r",
|
||||
"left": "l",
|
||||
"top": "t",
|
||||
"bottom": "b",
|
||||
"top_right": "tr",
|
||||
}
|
||||
|
||||
if position == "none":
|
||||
return
|
||||
|
||||
if position not in allowed:
|
||||
return
|
||||
|
||||
position = allowed[position]
|
||||
|
||||
self._xml_start_tag("c:legend")
|
||||
|
||||
# Write the c:legendPos element.
|
||||
self._write_legend_pos(position)
|
||||
|
||||
# Remove series labels from the legend.
|
||||
for index in delete_series:
|
||||
# Write the c:legendEntry element.
|
||||
self._write_legend_entry(index)
|
||||
|
||||
# Write the c:layout element.
|
||||
self._write_layout(legend.get("layout"), "legend")
|
||||
|
||||
# Write the c:overlay element.
|
||||
if overlay:
|
||||
self._write_overlay()
|
||||
|
||||
# Write the c:spPr element.
|
||||
self._write_sp_pr(legend)
|
||||
|
||||
# Write the c:txPr element. Over-ridden.
|
||||
self._write_tx_pr_legend(None, font)
|
||||
|
||||
self._xml_end_tag("c:legend")
|
||||
|
||||
def _write_tx_pr_legend(self, horiz, font):
|
||||
# Write the <c:txPr> element for legends.
|
||||
|
||||
if font and font.get("rotation"):
|
||||
rotation = font["rotation"]
|
||||
else:
|
||||
rotation = None
|
||||
|
||||
self._xml_start_tag("c:txPr")
|
||||
|
||||
# Write the a:bodyPr element.
|
||||
self._write_a_body_pr(rotation, horiz)
|
||||
|
||||
# Write the a:lstStyle element.
|
||||
self._write_a_lst_style()
|
||||
|
||||
# Write the a:p element.
|
||||
self._write_a_p_legend(font)
|
||||
|
||||
self._xml_end_tag("c:txPr")
|
||||
|
||||
def _write_a_p_legend(self, font):
|
||||
# Write the <a:p> element for legends.
|
||||
|
||||
self._xml_start_tag("a:p")
|
||||
|
||||
# Write the a:pPr element.
|
||||
self._write_a_p_pr_legend(font)
|
||||
|
||||
# Write the a:endParaRPr element.
|
||||
self._write_a_end_para_rpr()
|
||||
|
||||
self._xml_end_tag("a:p")
|
||||
|
||||
def _write_a_p_pr_legend(self, font):
|
||||
# Write the <a:pPr> element for legends.
|
||||
attributes = [("rtl", 0)]
|
||||
|
||||
self._xml_start_tag("a:pPr", attributes)
|
||||
|
||||
# Write the a:defRPr element.
|
||||
self._write_a_def_rpr(font)
|
||||
|
||||
self._xml_end_tag("a:pPr")
|
||||
|
||||
def _write_vary_colors(self):
|
||||
# Write the <c:varyColors> element.
|
||||
attributes = [("val", 1)]
|
||||
|
||||
self._xml_empty_tag("c:varyColors", attributes)
|
||||
|
||||
def _write_first_slice_ang(self):
|
||||
# Write the <c:firstSliceAng> element.
|
||||
attributes = [("val", self.rotation)]
|
||||
|
||||
self._xml_empty_tag("c:firstSliceAng", attributes)
|
||||
102
billinglayer/python/xlsxwriter/chart_radar.py
Normal file
102
billinglayer/python/xlsxwriter/chart_radar.py
Normal file
@@ -0,0 +1,102 @@
|
||||
###############################################################################
|
||||
#
|
||||
# ChartRadar - A class for writing the Excel XLSX Radar charts.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
from . import chart
|
||||
|
||||
|
||||
class ChartRadar(chart.Chart):
|
||||
"""
|
||||
A class for writing the Excel XLSX Radar charts.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self, options=None):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
super(ChartRadar, self).__init__()
|
||||
|
||||
if options is None:
|
||||
options = {}
|
||||
|
||||
self.subtype = options.get("subtype")
|
||||
|
||||
if not self.subtype:
|
||||
self.subtype = "marker"
|
||||
self.default_marker = {"type": "none"}
|
||||
|
||||
# Override and reset the default axis values.
|
||||
self.x_axis["defaults"]["major_gridlines"] = {"visible": 1}
|
||||
self.set_x_axis({})
|
||||
|
||||
# Set the available data label positions for this chart type.
|
||||
self.label_position_default = "center"
|
||||
self.label_positions = {"center": "ctr"}
|
||||
|
||||
# Hardcode major_tick_mark for now until there is an accessor.
|
||||
self.y_axis["major_tick_mark"] = "cross"
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_chart_type(self, args):
|
||||
# Write the c:radarChart element.
|
||||
self._write_radar_chart(args)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_radar_chart(self, args):
|
||||
# Write the <c:radarChart> element.
|
||||
|
||||
if args["primary_axes"]:
|
||||
series = self._get_primary_axes_series()
|
||||
else:
|
||||
series = self._get_secondary_axes_series()
|
||||
|
||||
if not len(series):
|
||||
return
|
||||
|
||||
self._xml_start_tag("c:radarChart")
|
||||
|
||||
# Write the c:radarStyle element.
|
||||
self._write_radar_style()
|
||||
|
||||
# Write the series elements.
|
||||
for data in series:
|
||||
self._write_ser(data)
|
||||
|
||||
# Write the c:axId elements
|
||||
self._write_axis_ids(args)
|
||||
|
||||
self._xml_end_tag("c:radarChart")
|
||||
|
||||
def _write_radar_style(self):
|
||||
# Write the <c:radarStyle> element.
|
||||
val = "marker"
|
||||
|
||||
if self.subtype == "filled":
|
||||
val = "filled"
|
||||
|
||||
attributes = [("val", val)]
|
||||
|
||||
self._xml_empty_tag("c:radarStyle", attributes)
|
||||
333
billinglayer/python/xlsxwriter/chart_scatter.py
Normal file
333
billinglayer/python/xlsxwriter/chart_scatter.py
Normal file
@@ -0,0 +1,333 @@
|
||||
###############################################################################
|
||||
#
|
||||
# ChartScatter - A class for writing the Excel XLSX Scatter charts.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
from . import chart
|
||||
from warnings import warn
|
||||
|
||||
|
||||
class ChartScatter(chart.Chart):
|
||||
"""
|
||||
A class for writing the Excel XLSX Scatter charts.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self, options=None):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
super(ChartScatter, self).__init__()
|
||||
|
||||
if options is None:
|
||||
options = {}
|
||||
|
||||
self.subtype = options.get("subtype")
|
||||
|
||||
if not self.subtype:
|
||||
self.subtype = "marker_only"
|
||||
|
||||
self.cross_between = "midCat"
|
||||
self.horiz_val_axis = 0
|
||||
self.val_axis_position = "b"
|
||||
self.smooth_allowed = True
|
||||
self.requires_category = True
|
||||
|
||||
# Set the available data label positions for this chart type.
|
||||
self.label_position_default = "right"
|
||||
self.label_positions = {
|
||||
"center": "ctr",
|
||||
"right": "r",
|
||||
"left": "l",
|
||||
"above": "t",
|
||||
"below": "b",
|
||||
# For backward compatibility.
|
||||
"top": "t",
|
||||
"bottom": "b",
|
||||
}
|
||||
|
||||
def combine(self, chart=None):
|
||||
"""
|
||||
Create a combination chart with a secondary chart.
|
||||
|
||||
Note: Override parent method to add a warning.
|
||||
|
||||
Args:
|
||||
chart: The secondary chart to combine with the primary chart.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
|
||||
"""
|
||||
if chart is None:
|
||||
return
|
||||
|
||||
warn(
|
||||
"Combined chart not currently supported with scatter chart "
|
||||
"as the primary chart"
|
||||
)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_chart_type(self, args):
|
||||
# Override the virtual superclass method with a chart specific method.
|
||||
# Write the c:scatterChart element.
|
||||
self._write_scatter_chart(args)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_scatter_chart(self, args):
|
||||
# Write the <c:scatterChart> element.
|
||||
|
||||
if args["primary_axes"]:
|
||||
series = self._get_primary_axes_series()
|
||||
else:
|
||||
series = self._get_secondary_axes_series()
|
||||
|
||||
if not len(series):
|
||||
return
|
||||
|
||||
style = "lineMarker"
|
||||
subtype = self.subtype
|
||||
|
||||
# Set the user defined chart subtype.
|
||||
if subtype == "marker_only":
|
||||
style = "lineMarker"
|
||||
|
||||
if subtype == "straight_with_markers":
|
||||
style = "lineMarker"
|
||||
|
||||
if subtype == "straight":
|
||||
style = "lineMarker"
|
||||
self.default_marker = {"type": "none"}
|
||||
|
||||
if subtype == "smooth_with_markers":
|
||||
style = "smoothMarker"
|
||||
|
||||
if subtype == "smooth":
|
||||
style = "smoothMarker"
|
||||
self.default_marker = {"type": "none"}
|
||||
|
||||
# Add default formatting to the series data.
|
||||
self._modify_series_formatting()
|
||||
|
||||
self._xml_start_tag("c:scatterChart")
|
||||
|
||||
# Write the c:scatterStyle element.
|
||||
self._write_scatter_style(style)
|
||||
|
||||
# Write the series elements.
|
||||
for data in series:
|
||||
self._write_ser(data)
|
||||
|
||||
# Write the c:axId elements
|
||||
self._write_axis_ids(args)
|
||||
|
||||
self._xml_end_tag("c:scatterChart")
|
||||
|
||||
def _write_ser(self, series):
|
||||
# Over-ridden to write c:xVal/c:yVal instead of c:cat/c:val elements.
|
||||
# Write the <c:ser> element.
|
||||
|
||||
index = self.series_index
|
||||
self.series_index += 1
|
||||
|
||||
self._xml_start_tag("c:ser")
|
||||
|
||||
# Write the c:idx element.
|
||||
self._write_idx(index)
|
||||
|
||||
# Write the c:order element.
|
||||
self._write_order(index)
|
||||
|
||||
# Write the series name.
|
||||
self._write_series_name(series)
|
||||
|
||||
# Write the c:spPr element.
|
||||
self._write_sp_pr(series)
|
||||
|
||||
# Write the c:marker element.
|
||||
self._write_marker(series.get("marker"))
|
||||
|
||||
# Write the c:dPt element.
|
||||
self._write_d_pt(series.get("points"))
|
||||
|
||||
# Write the c:dLbls element.
|
||||
self._write_d_lbls(series.get("labels"))
|
||||
|
||||
# Write the c:trendline element.
|
||||
self._write_trendline(series.get("trendline"))
|
||||
|
||||
# Write the c:errBars element.
|
||||
self._write_error_bars(series.get("error_bars"))
|
||||
|
||||
# Write the c:xVal element.
|
||||
self._write_x_val(series)
|
||||
|
||||
# Write the c:yVal element.
|
||||
self._write_y_val(series)
|
||||
|
||||
# Write the c:smooth element.
|
||||
if "smooth" in self.subtype and series["smooth"] is None:
|
||||
# Default is on for smooth scatter charts.
|
||||
self._write_c_smooth(True)
|
||||
else:
|
||||
self._write_c_smooth(series["smooth"])
|
||||
|
||||
self._xml_end_tag("c:ser")
|
||||
|
||||
def _write_plot_area(self):
|
||||
# Over-ridden to have 2 valAx elements for scatter charts instead
|
||||
# of catAx/valAx.
|
||||
#
|
||||
# Write the <c:plotArea> element.
|
||||
self._xml_start_tag("c:plotArea")
|
||||
|
||||
# Write the c:layout element.
|
||||
self._write_layout(self.plotarea.get("layout"), "plot")
|
||||
|
||||
# Write the subclass chart elements for primary and secondary axes.
|
||||
self._write_chart_type({"primary_axes": 1})
|
||||
self._write_chart_type({"primary_axes": 0})
|
||||
|
||||
# Write c:catAx and c:valAx elements for series using primary axes.
|
||||
self._write_cat_val_axis(
|
||||
{
|
||||
"x_axis": self.x_axis,
|
||||
"y_axis": self.y_axis,
|
||||
"axis_ids": self.axis_ids,
|
||||
"position": "b",
|
||||
}
|
||||
)
|
||||
|
||||
tmp = self.horiz_val_axis
|
||||
self.horiz_val_axis = 1
|
||||
|
||||
self._write_val_axis(
|
||||
{
|
||||
"x_axis": self.x_axis,
|
||||
"y_axis": self.y_axis,
|
||||
"axis_ids": self.axis_ids,
|
||||
"position": "l",
|
||||
}
|
||||
)
|
||||
|
||||
self.horiz_val_axis = tmp
|
||||
|
||||
# Write c:valAx and c:catAx elements for series using secondary axes
|
||||
self._write_cat_val_axis(
|
||||
{
|
||||
"x_axis": self.x2_axis,
|
||||
"y_axis": self.y2_axis,
|
||||
"axis_ids": self.axis2_ids,
|
||||
"position": "b",
|
||||
}
|
||||
)
|
||||
self.horiz_val_axis = 1
|
||||
self._write_val_axis(
|
||||
{
|
||||
"x_axis": self.x2_axis,
|
||||
"y_axis": self.y2_axis,
|
||||
"axis_ids": self.axis2_ids,
|
||||
"position": "l",
|
||||
}
|
||||
)
|
||||
|
||||
# Write the c:spPr element for the plotarea formatting.
|
||||
self._write_sp_pr(self.plotarea)
|
||||
|
||||
self._xml_end_tag("c:plotArea")
|
||||
|
||||
def _write_x_val(self, series):
|
||||
# Write the <c:xVal> element.
|
||||
formula = series.get("categories")
|
||||
data_id = series.get("cat_data_id")
|
||||
data = self.formula_data[data_id]
|
||||
|
||||
self._xml_start_tag("c:xVal")
|
||||
|
||||
# Check the type of cached data.
|
||||
data_type = self._get_data_type(data)
|
||||
|
||||
if data_type == "str":
|
||||
# Write the c:numRef element.
|
||||
self._write_str_ref(formula, data, data_type)
|
||||
else:
|
||||
# Write the c:numRef element.
|
||||
self._write_num_ref(formula, data, data_type)
|
||||
|
||||
self._xml_end_tag("c:xVal")
|
||||
|
||||
def _write_y_val(self, series):
|
||||
# Write the <c:yVal> element.
|
||||
formula = series.get("values")
|
||||
data_id = series.get("val_data_id")
|
||||
data = self.formula_data[data_id]
|
||||
|
||||
self._xml_start_tag("c:yVal")
|
||||
|
||||
# Unlike Cat axes data should only be numeric.
|
||||
# Write the c:numRef element.
|
||||
self._write_num_ref(formula, data, "num")
|
||||
|
||||
self._xml_end_tag("c:yVal")
|
||||
|
||||
def _write_scatter_style(self, val):
|
||||
# Write the <c:scatterStyle> element.
|
||||
attributes = [("val", val)]
|
||||
|
||||
self._xml_empty_tag("c:scatterStyle", attributes)
|
||||
|
||||
def _modify_series_formatting(self):
|
||||
# Add default formatting to the series data unless it has already been
|
||||
# specified by the user.
|
||||
subtype = self.subtype
|
||||
|
||||
# The default scatter style "markers only" requires a line type.
|
||||
if subtype == "marker_only":
|
||||
# Go through each series and define default values.
|
||||
for series in self.series:
|
||||
# Set a line type unless there is already a user defined type.
|
||||
if not series["line"]["defined"]:
|
||||
series["line"] = {
|
||||
"width": 2.25,
|
||||
"none": 1,
|
||||
"defined": 1,
|
||||
}
|
||||
|
||||
def _write_d_pt_point(self, index, point):
|
||||
# Write an individual <c:dPt> element. Override the parent method to
|
||||
# add markers.
|
||||
|
||||
self._xml_start_tag("c:dPt")
|
||||
|
||||
# Write the c:idx element.
|
||||
self._write_idx(index)
|
||||
|
||||
self._xml_start_tag("c:marker")
|
||||
|
||||
# Write the c:spPr element.
|
||||
self._write_sp_pr(point)
|
||||
|
||||
self._xml_end_tag("c:marker")
|
||||
|
||||
self._xml_end_tag("c:dPt")
|
||||
124
billinglayer/python/xlsxwriter/chart_stock.py
Normal file
124
billinglayer/python/xlsxwriter/chart_stock.py
Normal file
@@ -0,0 +1,124 @@
|
||||
###############################################################################
|
||||
#
|
||||
# ChartStock - A class for writing the Excel XLSX Stock charts.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
from . import chart
|
||||
|
||||
|
||||
class ChartStock(chart.Chart):
|
||||
"""
|
||||
A class for writing the Excel XLSX Stock charts.
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self, options=None):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
super(ChartStock, self).__init__()
|
||||
|
||||
self.show_crosses = 0
|
||||
self.hi_low_lines = {}
|
||||
self.date_category = True
|
||||
|
||||
# Override and reset the default axis values.
|
||||
self.x_axis["defaults"]["num_format"] = "dd/mm/yyyy"
|
||||
self.x2_axis["defaults"]["num_format"] = "dd/mm/yyyy"
|
||||
|
||||
# Set the available data label positions for this chart type.
|
||||
self.label_position_default = "right"
|
||||
self.label_positions = {
|
||||
"center": "ctr",
|
||||
"right": "r",
|
||||
"left": "l",
|
||||
"above": "t",
|
||||
"below": "b",
|
||||
# For backward compatibility.
|
||||
"top": "t",
|
||||
"bottom": "b",
|
||||
}
|
||||
|
||||
self.set_x_axis({})
|
||||
self.set_x2_axis({})
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_chart_type(self, args):
|
||||
# Override the virtual superclass method with a chart specific method.
|
||||
# Write the c:stockChart element.
|
||||
self._write_stock_chart(args)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_stock_chart(self, args):
|
||||
# Write the <c:stockChart> element.
|
||||
# Overridden to add hi_low_lines().
|
||||
|
||||
if args["primary_axes"]:
|
||||
series = self._get_primary_axes_series()
|
||||
else:
|
||||
series = self._get_secondary_axes_series()
|
||||
|
||||
if not len(series):
|
||||
return
|
||||
|
||||
# Add default formatting to the series data.
|
||||
self._modify_series_formatting()
|
||||
|
||||
self._xml_start_tag("c:stockChart")
|
||||
|
||||
# Write the series elements.
|
||||
for data in series:
|
||||
self._write_ser(data)
|
||||
|
||||
# Write the c:dropLines element.
|
||||
self._write_drop_lines()
|
||||
|
||||
# Write the c:hiLowLines element.
|
||||
if args.get("primary_axes"):
|
||||
self._write_hi_low_lines()
|
||||
|
||||
# Write the c:upDownBars element.
|
||||
self._write_up_down_bars()
|
||||
|
||||
# Write the c:axId elements
|
||||
self._write_axis_ids(args)
|
||||
|
||||
self._xml_end_tag("c:stockChart")
|
||||
|
||||
def _modify_series_formatting(self):
|
||||
# Add default formatting to the series data.
|
||||
|
||||
index = 0
|
||||
|
||||
for series in self.series:
|
||||
if index % 4 != 3:
|
||||
if not series["line"]["defined"]:
|
||||
series["line"] = {"width": 2.25, "none": 1, "defined": 1}
|
||||
|
||||
if series["marker"] is None:
|
||||
if index % 4 == 2:
|
||||
series["marker"] = {"type": "dot", "size": 3}
|
||||
else:
|
||||
series["marker"] = {"type": "none"}
|
||||
|
||||
index += 1
|
||||
193
billinglayer/python/xlsxwriter/chartsheet.py
Normal file
193
billinglayer/python/xlsxwriter/chartsheet.py
Normal file
@@ -0,0 +1,193 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Chartsheet - A class for writing the Excel XLSX Worksheet file.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
from . import worksheet
|
||||
from .drawing import Drawing
|
||||
|
||||
|
||||
class Chartsheet(worksheet.Worksheet):
|
||||
"""
|
||||
A class for writing the Excel XLSX Chartsheet file.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
|
||||
super(Chartsheet, self).__init__()
|
||||
|
||||
self.is_chartsheet = True
|
||||
self.drawing = None
|
||||
self.chart = None
|
||||
self.charts = []
|
||||
self.zoom_scale_normal = 0
|
||||
self.orientation = 0
|
||||
self.protection = False
|
||||
|
||||
def set_chart(self, chart):
|
||||
"""
|
||||
Set the chart object for the chartsheet.
|
||||
Args:
|
||||
chart: Chart object.
|
||||
Returns:
|
||||
chart: A reference to the chart object.
|
||||
"""
|
||||
chart.embedded = False
|
||||
chart.protection = self.protection
|
||||
self.chart = chart
|
||||
self.charts.append([0, 0, chart, 0, 0, 1, 1])
|
||||
return chart
|
||||
|
||||
def protect(self, password="", options=None):
|
||||
"""
|
||||
Set the password and protection options of the worksheet.
|
||||
|
||||
Args:
|
||||
password: An optional password string.
|
||||
options: A dictionary of worksheet objects to protect.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
|
||||
"""
|
||||
# This method is overridden from parent worksheet class.
|
||||
|
||||
# Chartsheets only allow a reduced set of protect options.
|
||||
copy = {}
|
||||
|
||||
if not options:
|
||||
options = {}
|
||||
|
||||
if options.get("objects") is None:
|
||||
copy["objects"] = False
|
||||
else:
|
||||
# Objects are default on for chartsheets, so reverse state.
|
||||
copy["objects"] = not options["objects"]
|
||||
|
||||
if options.get("content") is None:
|
||||
copy["content"] = True
|
||||
else:
|
||||
copy["content"] = options["content"]
|
||||
|
||||
copy["sheet"] = False
|
||||
copy["scenarios"] = True
|
||||
|
||||
# If objects and content are both off then the chartsheet isn't
|
||||
# protected, unless it has a password.
|
||||
if password == "" and copy["objects"] and not copy["content"]:
|
||||
return
|
||||
|
||||
if self.chart:
|
||||
self.chart.protection = True
|
||||
else:
|
||||
self.protection = True
|
||||
|
||||
# Call the parent method.
|
||||
super(Chartsheet, self).protect(password, copy)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
def _assemble_xml_file(self):
|
||||
# Assemble and write the XML file.
|
||||
|
||||
# Write the XML declaration.
|
||||
self._xml_declaration()
|
||||
|
||||
# Write the root worksheet element.
|
||||
self._write_chartsheet()
|
||||
|
||||
# Write the worksheet properties.
|
||||
self._write_sheet_pr()
|
||||
|
||||
# Write the sheet view properties.
|
||||
self._write_sheet_views()
|
||||
|
||||
# Write the sheetProtection element.
|
||||
self._write_sheet_protection()
|
||||
|
||||
# Write the printOptions element.
|
||||
self._write_print_options()
|
||||
|
||||
# Write the worksheet page_margins.
|
||||
self._write_page_margins()
|
||||
|
||||
# Write the worksheet page setup.
|
||||
self._write_page_setup()
|
||||
|
||||
# Write the headerFooter element.
|
||||
self._write_header_footer()
|
||||
|
||||
# Write the drawing element.
|
||||
self._write_drawings()
|
||||
|
||||
# Close the worksheet tag.
|
||||
self._xml_end_tag("chartsheet")
|
||||
|
||||
# Close the file.
|
||||
self._xml_close()
|
||||
|
||||
def _prepare_chart(self, index, chart_id, drawing_id):
|
||||
# Set up chart/drawings.
|
||||
|
||||
self.chart.id = chart_id - 1
|
||||
|
||||
self.drawing = Drawing()
|
||||
self.drawing.orientation = self.orientation
|
||||
|
||||
self.external_drawing_links.append(
|
||||
["/drawing", "../drawings/drawing" + str(drawing_id) + ".xml"]
|
||||
)
|
||||
|
||||
self.drawing_links.append(
|
||||
["/chart", "../charts/chart" + str(chart_id) + ".xml"]
|
||||
)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_chartsheet(self):
|
||||
# Write the <worksheet> element. This is the root element.
|
||||
|
||||
schema = "http://schemas.openxmlformats.org/"
|
||||
xmlns = schema + "spreadsheetml/2006/main"
|
||||
xmlns_r = schema + "officeDocument/2006/relationships"
|
||||
|
||||
attributes = [("xmlns", xmlns), ("xmlns:r", xmlns_r)]
|
||||
|
||||
self._xml_start_tag("chartsheet", attributes)
|
||||
|
||||
def _write_sheet_pr(self):
|
||||
# Write the <sheetPr> element for Sheet level properties.
|
||||
attributes = []
|
||||
|
||||
if self.filter_on:
|
||||
attributes.append(("filterMode", 1))
|
||||
|
||||
if self.fit_page or self.tab_color:
|
||||
self._xml_start_tag("sheetPr", attributes)
|
||||
self._write_tab_color()
|
||||
self._write_page_set_up_pr()
|
||||
self._xml_end_tag("sheetPr")
|
||||
else:
|
||||
self._xml_empty_tag("sheetPr", attributes)
|
||||
209
billinglayer/python/xlsxwriter/comments.py
Normal file
209
billinglayer/python/xlsxwriter/comments.py
Normal file
@@ -0,0 +1,209 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Comments - A class for writing the Excel XLSX Worksheet file.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
from . import xmlwriter
|
||||
from .utility import preserve_whitespace
|
||||
from .utility import xl_rowcol_to_cell
|
||||
|
||||
|
||||
class Comments(xmlwriter.XMLwriter):
|
||||
"""
|
||||
A class for writing the Excel XLSX Comments file.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
|
||||
super(Comments, self).__init__()
|
||||
self.author_ids = {}
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _assemble_xml_file(self, comments_data=[]):
|
||||
# Assemble and write the XML file.
|
||||
|
||||
# Write the XML declaration.
|
||||
self._xml_declaration()
|
||||
|
||||
# Write the comments element.
|
||||
self._write_comments()
|
||||
|
||||
# Write the authors element.
|
||||
self._write_authors(comments_data)
|
||||
|
||||
# Write the commentList element.
|
||||
self._write_comment_list(comments_data)
|
||||
|
||||
self._xml_end_tag("comments")
|
||||
|
||||
# Close the file.
|
||||
self._xml_close()
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_comments(self):
|
||||
# Write the <comments> element.
|
||||
xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
|
||||
attributes = [("xmlns", xmlns)]
|
||||
|
||||
self._xml_start_tag("comments", attributes)
|
||||
|
||||
def _write_authors(self, comment_data):
|
||||
# Write the <authors> element.
|
||||
author_count = 0
|
||||
|
||||
self._xml_start_tag("authors")
|
||||
|
||||
for comment in comment_data:
|
||||
author = comment[3]
|
||||
|
||||
if author is not None and author not in self.author_ids:
|
||||
# Store the author id.
|
||||
self.author_ids[author] = author_count
|
||||
author_count += 1
|
||||
|
||||
# Write the author element.
|
||||
self._write_author(author)
|
||||
|
||||
self._xml_end_tag("authors")
|
||||
|
||||
def _write_author(self, data):
|
||||
# Write the <author> element.
|
||||
self._xml_data_element("author", data)
|
||||
|
||||
def _write_comment_list(self, comment_data):
|
||||
# Write the <commentList> element.
|
||||
self._xml_start_tag("commentList")
|
||||
|
||||
for comment in comment_data:
|
||||
row = comment[0]
|
||||
col = comment[1]
|
||||
text = comment[2]
|
||||
author = comment[3]
|
||||
font_name = comment[6]
|
||||
font_size = comment[7]
|
||||
font_family = comment[8]
|
||||
|
||||
# Look up the author id.
|
||||
author_id = None
|
||||
if author is not None:
|
||||
author_id = self.author_ids[author]
|
||||
|
||||
# Write the comment element.
|
||||
font = (font_name, font_size, font_family)
|
||||
self._write_comment(row, col, text, author_id, font)
|
||||
|
||||
self._xml_end_tag("commentList")
|
||||
|
||||
def _write_comment(self, row, col, text, author_id, font):
|
||||
# Write the <comment> element.
|
||||
ref = xl_rowcol_to_cell(row, col)
|
||||
|
||||
attributes = [("ref", ref)]
|
||||
|
||||
if author_id is not None:
|
||||
attributes.append(("authorId", author_id))
|
||||
|
||||
self._xml_start_tag("comment", attributes)
|
||||
|
||||
# Write the text element.
|
||||
self._write_text(text, font)
|
||||
|
||||
self._xml_end_tag("comment")
|
||||
|
||||
def _write_text(self, text, font):
|
||||
# Write the <text> element.
|
||||
self._xml_start_tag("text")
|
||||
|
||||
# Write the text r element.
|
||||
self._write_text_r(text, font)
|
||||
|
||||
self._xml_end_tag("text")
|
||||
|
||||
def _write_text_r(self, text, font):
|
||||
# Write the <r> element.
|
||||
self._xml_start_tag("r")
|
||||
|
||||
# Write the rPr element.
|
||||
self._write_r_pr(font)
|
||||
|
||||
# Write the text r element.
|
||||
self._write_text_t(text)
|
||||
|
||||
self._xml_end_tag("r")
|
||||
|
||||
def _write_text_t(self, text):
|
||||
# Write the text <t> element.
|
||||
attributes = []
|
||||
|
||||
if preserve_whitespace(text):
|
||||
attributes.append(("xml:space", "preserve"))
|
||||
|
||||
self._xml_data_element("t", text, attributes)
|
||||
|
||||
def _write_r_pr(self, font):
|
||||
# Write the <rPr> element.
|
||||
self._xml_start_tag("rPr")
|
||||
|
||||
# Write the sz element.
|
||||
self._write_sz(font[1])
|
||||
|
||||
# Write the color element.
|
||||
self._write_color()
|
||||
|
||||
# Write the rFont element.
|
||||
self._write_r_font(font[0])
|
||||
|
||||
# Write the family element.
|
||||
self._write_family(font[2])
|
||||
|
||||
self._xml_end_tag("rPr")
|
||||
|
||||
def _write_sz(self, font_size):
|
||||
# Write the <sz> element.
|
||||
attributes = [("val", font_size)]
|
||||
|
||||
self._xml_empty_tag("sz", attributes)
|
||||
|
||||
def _write_color(self):
|
||||
# Write the <color> element.
|
||||
attributes = [("indexed", 81)]
|
||||
|
||||
self._xml_empty_tag("color", attributes)
|
||||
|
||||
def _write_r_font(self, font_name):
|
||||
# Write the <rFont> element.
|
||||
attributes = [("val", font_name)]
|
||||
|
||||
self._xml_empty_tag("rFont", attributes)
|
||||
|
||||
def _write_family(self, font_family):
|
||||
# Write the <family> element.
|
||||
attributes = [("val", font_family)]
|
||||
|
||||
self._xml_empty_tag("family", attributes)
|
||||
222
billinglayer/python/xlsxwriter/contenttypes.py
Normal file
222
billinglayer/python/xlsxwriter/contenttypes.py
Normal file
@@ -0,0 +1,222 @@
|
||||
###############################################################################
|
||||
#
|
||||
# ContentTypes - A class for writing the Excel XLSX ContentTypes file.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
import copy
|
||||
from . import xmlwriter
|
||||
|
||||
# Long namespace strings used in the class.
|
||||
app_package = "application/vnd.openxmlformats-package."
|
||||
app_document = "application/vnd.openxmlformats-officedocument."
|
||||
|
||||
defaults = [
|
||||
["rels", app_package + "relationships+xml"],
|
||||
["xml", "application/xml"],
|
||||
]
|
||||
|
||||
overrides = [
|
||||
["/docProps/app.xml", app_document + "extended-properties+xml"],
|
||||
["/docProps/core.xml", app_package + "core-properties+xml"],
|
||||
["/xl/styles.xml", app_document + "spreadsheetml.styles+xml"],
|
||||
["/xl/theme/theme1.xml", app_document + "theme+xml"],
|
||||
["/xl/workbook.xml", app_document + "spreadsheetml.sheet.main+xml"],
|
||||
]
|
||||
|
||||
|
||||
class ContentTypes(xmlwriter.XMLwriter):
|
||||
"""
|
||||
A class for writing the Excel XLSX ContentTypes file.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
|
||||
super(ContentTypes, self).__init__()
|
||||
|
||||
# Copy the defaults in case we need to change them.
|
||||
self.defaults = copy.deepcopy(defaults)
|
||||
self.overrides = copy.deepcopy(overrides)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _assemble_xml_file(self):
|
||||
# Assemble and write the XML file.
|
||||
|
||||
# Write the XML declaration.
|
||||
self._xml_declaration()
|
||||
|
||||
self._write_types()
|
||||
self._write_defaults()
|
||||
self._write_overrides()
|
||||
|
||||
self._xml_end_tag("Types")
|
||||
|
||||
# Close the file.
|
||||
self._xml_close()
|
||||
|
||||
def _add_default(self, default):
|
||||
# Add elements to the ContentTypes defaults.
|
||||
self.defaults.append(default)
|
||||
|
||||
def _add_override(self, override):
|
||||
# Add elements to the ContentTypes overrides.
|
||||
self.overrides.append(override)
|
||||
|
||||
def _add_worksheet_name(self, worksheet_name):
|
||||
# Add the name of a worksheet to the ContentTypes overrides.
|
||||
worksheet_name = "/xl/worksheets/" + worksheet_name + ".xml"
|
||||
|
||||
self._add_override(
|
||||
(worksheet_name, app_document + "spreadsheetml.worksheet+xml")
|
||||
)
|
||||
|
||||
def _add_chartsheet_name(self, chartsheet_name):
|
||||
# Add the name of a chartsheet to the ContentTypes overrides.
|
||||
chartsheet_name = "/xl/chartsheets/" + chartsheet_name + ".xml"
|
||||
|
||||
self._add_override(
|
||||
(chartsheet_name, app_document + "spreadsheetml.chartsheet+xml")
|
||||
)
|
||||
|
||||
def _add_chart_name(self, chart_name):
|
||||
# Add the name of a chart to the ContentTypes overrides.
|
||||
chart_name = "/xl/charts/" + chart_name + ".xml"
|
||||
|
||||
self._add_override((chart_name, app_document + "drawingml.chart+xml"))
|
||||
|
||||
def _add_drawing_name(self, drawing_name):
|
||||
# Add the name of a drawing to the ContentTypes overrides.
|
||||
drawing_name = "/xl/drawings/" + drawing_name + ".xml"
|
||||
|
||||
self._add_override((drawing_name, app_document + "drawing+xml"))
|
||||
|
||||
def _add_vml_name(self):
|
||||
# Add the name of a VML drawing to the ContentTypes defaults.
|
||||
self._add_default(("vml", app_document + "vmlDrawing"))
|
||||
|
||||
def _add_comment_name(self, comment_name):
|
||||
# Add the name of a comment to the ContentTypes overrides.
|
||||
comment_name = "/xl/" + comment_name + ".xml"
|
||||
|
||||
self._add_override((comment_name, app_document + "spreadsheetml.comments+xml"))
|
||||
|
||||
def _add_shared_strings(self):
|
||||
# Add the sharedStrings link to the ContentTypes overrides.
|
||||
self._add_override(
|
||||
("/xl/sharedStrings.xml", app_document + "spreadsheetml.sharedStrings+xml")
|
||||
)
|
||||
|
||||
def _add_calc_chain(self):
|
||||
# Add the calcChain link to the ContentTypes overrides.
|
||||
self._add_override(
|
||||
("/xl/calcChain.xml", app_document + "spreadsheetml.calcChain+xml")
|
||||
)
|
||||
|
||||
def _add_image_types(self, image_types):
|
||||
# Add the image default types.
|
||||
for image_type in image_types:
|
||||
extension = image_type
|
||||
|
||||
if image_type in ("wmf", "emf"):
|
||||
image_type = "x-" + image_type
|
||||
|
||||
self._add_default((extension, "image/" + image_type))
|
||||
|
||||
def _add_table_name(self, table_name):
|
||||
# Add the name of a table to the ContentTypes overrides.
|
||||
table_name = "/xl/tables/" + table_name + ".xml"
|
||||
|
||||
self._add_override((table_name, app_document + "spreadsheetml.table+xml"))
|
||||
|
||||
def _add_vba_project(self):
|
||||
# Add a vbaProject to the ContentTypes defaults.
|
||||
|
||||
# Change the workbook.xml content-type from xlsx to xlsm.
|
||||
for i, override in enumerate(self.overrides):
|
||||
if override[0] == "/xl/workbook.xml":
|
||||
xlsm = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"
|
||||
self.overrides[i][1] = xlsm
|
||||
|
||||
self._add_default(("bin", "application/vnd.ms-office.vbaProject"))
|
||||
|
||||
def _add_custom_properties(self):
|
||||
# Add the custom properties to the ContentTypes overrides.
|
||||
self._add_override(
|
||||
("/docProps/custom.xml", app_document + "custom-properties+xml")
|
||||
)
|
||||
|
||||
def _add_metadata(self):
|
||||
# Add the metadata file to the ContentTypes overrides.
|
||||
self._add_override(
|
||||
("/xl/metadata.xml", app_document + "spreadsheetml.sheetMetadata+xml")
|
||||
)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_defaults(self):
|
||||
# Write out all of the <Default> types.
|
||||
|
||||
for extension, content_type in self.defaults:
|
||||
self._xml_empty_tag(
|
||||
"Default", [("Extension", extension), ("ContentType", content_type)]
|
||||
)
|
||||
|
||||
def _write_overrides(self):
|
||||
# Write out all of the <Override> types.
|
||||
for part_name, content_type in self.overrides:
|
||||
self._xml_empty_tag(
|
||||
"Override", [("PartName", part_name), ("ContentType", content_type)]
|
||||
)
|
||||
|
||||
def _write_types(self):
|
||||
# Write the <Types> element.
|
||||
xmlns = "http://schemas.openxmlformats.org/package/2006/content-types"
|
||||
|
||||
attributes = [
|
||||
(
|
||||
"xmlns",
|
||||
xmlns,
|
||||
)
|
||||
]
|
||||
self._xml_start_tag("Types", attributes)
|
||||
|
||||
def _write_default(self, extension, content_type):
|
||||
# Write the <Default> element.
|
||||
attributes = [
|
||||
("Extension", extension),
|
||||
("ContentType", content_type),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("Default", attributes)
|
||||
|
||||
def _write_override(self, part_name, content_type):
|
||||
# Write the <Override> element.
|
||||
attributes = [
|
||||
("PartName", part_name),
|
||||
("ContentType", content_type),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("Override", attributes)
|
||||
205
billinglayer/python/xlsxwriter/core.py
Normal file
205
billinglayer/python/xlsxwriter/core.py
Normal file
@@ -0,0 +1,205 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Core - A class for writing the Excel XLSX Worksheet file.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
# Standard packages.
|
||||
from datetime import datetime
|
||||
|
||||
# Package imports.
|
||||
from . import xmlwriter
|
||||
|
||||
|
||||
class Core(xmlwriter.XMLwriter):
|
||||
"""
|
||||
A class for writing the Excel XLSX Core file.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
|
||||
super(Core, self).__init__()
|
||||
|
||||
self.properties = {}
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _assemble_xml_file(self):
|
||||
# Assemble and write the XML file.
|
||||
|
||||
# Write the XML declaration.
|
||||
self._xml_declaration()
|
||||
|
||||
self._write_cp_core_properties()
|
||||
self._write_dc_title()
|
||||
self._write_dc_subject()
|
||||
self._write_dc_creator()
|
||||
self._write_cp_keywords()
|
||||
self._write_dc_description()
|
||||
self._write_cp_last_modified_by()
|
||||
self._write_dcterms_created()
|
||||
self._write_dcterms_modified()
|
||||
self._write_cp_category()
|
||||
self._write_cp_content_status()
|
||||
|
||||
self._xml_end_tag("cp:coreProperties")
|
||||
|
||||
# Close the file.
|
||||
self._xml_close()
|
||||
|
||||
def _set_properties(self, properties):
|
||||
# Set the document properties.
|
||||
self.properties = properties
|
||||
|
||||
def _datetime_to_iso8601_date(self, date):
|
||||
# Convert to a ISO 8601 style "2010-01-01T00:00:00Z" date.
|
||||
if not date:
|
||||
date = datetime.utcnow()
|
||||
|
||||
return date.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_cp_core_properties(self):
|
||||
# Write the <cp:coreProperties> element.
|
||||
|
||||
xmlns_cp = (
|
||||
"http://schemas.openxmlformats.org/package/2006/"
|
||||
+ "metadata/core-properties"
|
||||
)
|
||||
xmlns_dc = "http://purl.org/dc/elements/1.1/"
|
||||
xmlns_dcterms = "http://purl.org/dc/terms/"
|
||||
xmlns_dcmitype = "http://purl.org/dc/dcmitype/"
|
||||
xmlns_xsi = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
|
||||
attributes = [
|
||||
("xmlns:cp", xmlns_cp),
|
||||
("xmlns:dc", xmlns_dc),
|
||||
("xmlns:dcterms", xmlns_dcterms),
|
||||
("xmlns:dcmitype", xmlns_dcmitype),
|
||||
("xmlns:xsi", xmlns_xsi),
|
||||
]
|
||||
|
||||
self._xml_start_tag("cp:coreProperties", attributes)
|
||||
|
||||
def _write_dc_creator(self):
|
||||
# Write the <dc:creator> element.
|
||||
data = self.properties.get("author", "")
|
||||
|
||||
self._xml_data_element("dc:creator", data)
|
||||
|
||||
def _write_cp_last_modified_by(self):
|
||||
# Write the <cp:lastModifiedBy> element.
|
||||
data = self.properties.get("author", "")
|
||||
|
||||
self._xml_data_element("cp:lastModifiedBy", data)
|
||||
|
||||
def _write_dcterms_created(self):
|
||||
# Write the <dcterms:created> element.
|
||||
date = self.properties.get("created", datetime.utcnow())
|
||||
|
||||
xsi_type = "dcterms:W3CDTF"
|
||||
|
||||
date = self._datetime_to_iso8601_date(date)
|
||||
|
||||
attributes = [
|
||||
(
|
||||
"xsi:type",
|
||||
xsi_type,
|
||||
)
|
||||
]
|
||||
|
||||
self._xml_data_element("dcterms:created", date, attributes)
|
||||
|
||||
def _write_dcterms_modified(self):
|
||||
# Write the <dcterms:modified> element.
|
||||
date = self.properties.get("created", datetime.utcnow())
|
||||
|
||||
xsi_type = "dcterms:W3CDTF"
|
||||
|
||||
date = self._datetime_to_iso8601_date(date)
|
||||
|
||||
attributes = [
|
||||
(
|
||||
"xsi:type",
|
||||
xsi_type,
|
||||
)
|
||||
]
|
||||
|
||||
self._xml_data_element("dcterms:modified", date, attributes)
|
||||
|
||||
def _write_dc_title(self):
|
||||
# Write the <dc:title> element.
|
||||
if "title" in self.properties:
|
||||
data = self.properties["title"]
|
||||
else:
|
||||
return
|
||||
|
||||
self._xml_data_element("dc:title", data)
|
||||
|
||||
def _write_dc_subject(self):
|
||||
# Write the <dc:subject> element.
|
||||
if "subject" in self.properties:
|
||||
data = self.properties["subject"]
|
||||
else:
|
||||
return
|
||||
|
||||
self._xml_data_element("dc:subject", data)
|
||||
|
||||
def _write_cp_keywords(self):
|
||||
# Write the <cp:keywords> element.
|
||||
if "keywords" in self.properties:
|
||||
data = self.properties["keywords"]
|
||||
else:
|
||||
return
|
||||
|
||||
self._xml_data_element("cp:keywords", data)
|
||||
|
||||
def _write_dc_description(self):
|
||||
# Write the <dc:description> element.
|
||||
if "comments" in self.properties:
|
||||
data = self.properties["comments"]
|
||||
else:
|
||||
return
|
||||
|
||||
self._xml_data_element("dc:description", data)
|
||||
|
||||
def _write_cp_category(self):
|
||||
# Write the <cp:category> element.
|
||||
if "category" in self.properties:
|
||||
data = self.properties["category"]
|
||||
else:
|
||||
return
|
||||
|
||||
self._xml_data_element("cp:category", data)
|
||||
|
||||
def _write_cp_content_status(self):
|
||||
# Write the <cp:contentStatus> element.
|
||||
if "status" in self.properties:
|
||||
data = self.properties["status"]
|
||||
else:
|
||||
return
|
||||
|
||||
self._xml_data_element("cp:contentStatus", data)
|
||||
141
billinglayer/python/xlsxwriter/custom.py
Normal file
141
billinglayer/python/xlsxwriter/custom.py
Normal file
@@ -0,0 +1,141 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Custom - A class for writing the Excel XLSX Custom Property file.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
# Package imports.
|
||||
from . import xmlwriter
|
||||
|
||||
|
||||
class Custom(xmlwriter.XMLwriter):
|
||||
"""
|
||||
A class for writing the Excel XLSX Custom Workbook Property file.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
|
||||
super(Custom, self).__init__()
|
||||
|
||||
self.properties = []
|
||||
self.pid = 1
|
||||
|
||||
def _set_properties(self, properties):
|
||||
# Set the document properties.
|
||||
self.properties = properties
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _assemble_xml_file(self):
|
||||
# Assemble and write the XML file.
|
||||
|
||||
# Write the XML declaration.
|
||||
self._xml_declaration()
|
||||
|
||||
self._write_properties()
|
||||
|
||||
self._xml_end_tag("Properties")
|
||||
|
||||
# Close the file.
|
||||
self._xml_close()
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_properties(self):
|
||||
# Write the <Properties> element.
|
||||
schema = "http://schemas.openxmlformats.org/officeDocument/2006/"
|
||||
xmlns = schema + "custom-properties"
|
||||
xmlns_vt = schema + "docPropsVTypes"
|
||||
|
||||
attributes = [
|
||||
("xmlns", xmlns),
|
||||
("xmlns:vt", xmlns_vt),
|
||||
]
|
||||
|
||||
self._xml_start_tag("Properties", attributes)
|
||||
|
||||
for custom_property in self.properties:
|
||||
# Write the property element.
|
||||
self._write_property(custom_property)
|
||||
|
||||
def _write_property(self, custom_property):
|
||||
# Write the <property> element.
|
||||
|
||||
fmtid = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"
|
||||
|
||||
name, value, property_type = custom_property
|
||||
self.pid += 1
|
||||
|
||||
attributes = [
|
||||
("fmtid", fmtid),
|
||||
("pid", self.pid),
|
||||
("name", name),
|
||||
]
|
||||
|
||||
self._xml_start_tag("property", attributes)
|
||||
|
||||
if property_type == "number_int":
|
||||
# Write the vt:i4 element.
|
||||
self._write_vt_i4(value)
|
||||
elif property_type == "number":
|
||||
# Write the vt:r8 element.
|
||||
self._write_vt_r8(value)
|
||||
elif property_type == "date":
|
||||
# Write the vt:filetime element.
|
||||
self._write_vt_filetime(value)
|
||||
elif property_type == "bool":
|
||||
# Write the vt:bool element.
|
||||
self._write_vt_bool(value)
|
||||
else:
|
||||
# Write the vt:lpwstr element.
|
||||
self._write_vt_lpwstr(value)
|
||||
|
||||
self._xml_end_tag("property")
|
||||
|
||||
def _write_vt_lpwstr(self, value):
|
||||
# Write the <vt:lpwstr> element.
|
||||
self._xml_data_element("vt:lpwstr", value)
|
||||
|
||||
def _write_vt_filetime(self, value):
|
||||
# Write the <vt:filetime> element.
|
||||
self._xml_data_element("vt:filetime", value)
|
||||
|
||||
def _write_vt_i4(self, value):
|
||||
# Write the <vt:i4> element.
|
||||
self._xml_data_element("vt:i4", value)
|
||||
|
||||
def _write_vt_r8(self, value):
|
||||
# Write the <vt:r8> element.
|
||||
self._xml_data_element("vt:r8", value)
|
||||
|
||||
def _write_vt_bool(self, value):
|
||||
# Write the <vt:bool> element.
|
||||
|
||||
if value:
|
||||
value = "true"
|
||||
else:
|
||||
value = "false"
|
||||
|
||||
self._xml_data_element("vt:bool", value)
|
||||
1204
billinglayer/python/xlsxwriter/drawing.py
Normal file
1204
billinglayer/python/xlsxwriter/drawing.py
Normal file
File diff suppressed because it is too large
Load Diff
55
billinglayer/python/xlsxwriter/exceptions.py
Normal file
55
billinglayer/python/xlsxwriter/exceptions.py
Normal file
@@ -0,0 +1,55 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Exceptions - A class for XlsxWriter exceptions.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
|
||||
class XlsxWriterException(Exception):
|
||||
"""Base exception for XlsxWriter."""
|
||||
|
||||
|
||||
class XlsxInputError(XlsxWriterException):
|
||||
"""Base exception for all input data related errors."""
|
||||
|
||||
|
||||
class XlsxFileError(XlsxWriterException):
|
||||
"""Base exception for all file related errors."""
|
||||
|
||||
|
||||
class EmptyChartSeries(XlsxInputError):
|
||||
"""Chart must contain at least one data series."""
|
||||
|
||||
|
||||
class DuplicateTableName(XlsxInputError):
|
||||
"""Worksheet table name already exists."""
|
||||
|
||||
|
||||
class InvalidWorksheetName(XlsxInputError):
|
||||
"""Worksheet name is too long or contains restricted characters."""
|
||||
|
||||
|
||||
class DuplicateWorksheetName(XlsxInputError):
|
||||
"""Worksheet name already exists."""
|
||||
|
||||
|
||||
class OverlappingRange(XlsxInputError):
|
||||
"""Worksheet merge range or table overlaps previous range."""
|
||||
|
||||
|
||||
class UndefinedImageSize(XlsxFileError):
|
||||
"""No size data found in image file."""
|
||||
|
||||
|
||||
class UnsupportedImageFormat(XlsxFileError):
|
||||
"""Unsupported image file format."""
|
||||
|
||||
|
||||
class FileCreateError(XlsxFileError):
|
||||
"""IO error when creating xlsx file."""
|
||||
|
||||
|
||||
class FileSizeError(XlsxFileError):
|
||||
"""Filesize would require ZIP64 extensions. Use workbook.use_zip64()."""
|
||||
1030
billinglayer/python/xlsxwriter/format.py
Normal file
1030
billinglayer/python/xlsxwriter/format.py
Normal file
File diff suppressed because it is too large
Load Diff
170
billinglayer/python/xlsxwriter/metadata.py
Normal file
170
billinglayer/python/xlsxwriter/metadata.py
Normal file
@@ -0,0 +1,170 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Metadata - A class for writing the Excel XLSX Metadata file.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
from . import xmlwriter
|
||||
|
||||
|
||||
class Metadata(xmlwriter.XMLwriter):
|
||||
"""
|
||||
A class for writing the Excel XLSX Metadata file.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
|
||||
super(Metadata, self).__init__()
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _assemble_xml_file(self):
|
||||
# Assemble and write the XML file.
|
||||
|
||||
# Write the XML declaration.
|
||||
self._xml_declaration()
|
||||
|
||||
# Write the metadata element.
|
||||
self._write_metadata()
|
||||
|
||||
# Write the metadataTypes element.
|
||||
self._write_metadata_types()
|
||||
|
||||
# Write the futureMetadata element.
|
||||
self._write_future_metadata()
|
||||
|
||||
# Write the cellMetadata element.
|
||||
self._write_cell_metadata()
|
||||
|
||||
self._xml_end_tag("metadata")
|
||||
|
||||
# Close the file.
|
||||
self._xml_close()
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_metadata(self):
|
||||
# Write the <metadata> element.
|
||||
xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
schema = "http://schemas.microsoft.com/office"
|
||||
xmlns_xda = schema + "/spreadsheetml/2017/dynamicarray"
|
||||
|
||||
attributes = [
|
||||
("xmlns", xmlns),
|
||||
("xmlns:xda", xmlns_xda),
|
||||
]
|
||||
|
||||
self._xml_start_tag("metadata", attributes)
|
||||
|
||||
def _write_metadata_types(self):
|
||||
# Write the <metadataTypes> element.
|
||||
attributes = [("count", 1)]
|
||||
|
||||
self._xml_start_tag("metadataTypes", attributes)
|
||||
|
||||
# Write the metadataType element.
|
||||
self._write_metadata_type()
|
||||
|
||||
self._xml_end_tag("metadataTypes")
|
||||
|
||||
def _write_metadata_type(self):
|
||||
# Write the <metadataType> element.
|
||||
attributes = [
|
||||
("name", "XLDAPR"),
|
||||
("minSupportedVersion", 120000),
|
||||
("copy", 1),
|
||||
("pasteAll", 1),
|
||||
("pasteValues", 1),
|
||||
("merge", 1),
|
||||
("splitFirst", 1),
|
||||
("rowColShift", 1),
|
||||
("clearFormats", 1),
|
||||
("clearComments", 1),
|
||||
("assign", 1),
|
||||
("coerce", 1),
|
||||
("cellMeta", 1),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("metadataType", attributes)
|
||||
|
||||
def _write_future_metadata(self):
|
||||
# Write the <futureMetadata> element.
|
||||
attributes = [
|
||||
("name", "XLDAPR"),
|
||||
("count", 1),
|
||||
]
|
||||
|
||||
self._xml_start_tag("futureMetadata", attributes)
|
||||
self._xml_start_tag("bk")
|
||||
self._xml_start_tag("extLst")
|
||||
|
||||
# Write the ext element.
|
||||
self._write_ext()
|
||||
|
||||
self._xml_end_tag("extLst")
|
||||
self._xml_end_tag("bk")
|
||||
self._xml_end_tag("futureMetadata")
|
||||
|
||||
def _write_ext(self):
|
||||
# Write the <ext> element.
|
||||
attributes = [("uri", "{bdbb8cdc-fa1e-496e-a857-3c3f30c029c3}")]
|
||||
|
||||
self._xml_start_tag("ext", attributes)
|
||||
|
||||
# Write the xda:dynamicArrayProperties element.
|
||||
self._write_xda_dynamic_array_properties()
|
||||
|
||||
self._xml_end_tag("ext")
|
||||
|
||||
def _write_xda_dynamic_array_properties(self):
|
||||
# Write the <xda:dynamicArrayProperties> element.
|
||||
attributes = [
|
||||
("fDynamic", 1),
|
||||
("fCollapsed", 0),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("xda:dynamicArrayProperties", attributes)
|
||||
|
||||
def _write_cell_metadata(self):
|
||||
# Write the <cellMetadata> element.
|
||||
attributes = [("count", 1)]
|
||||
|
||||
self._xml_start_tag("cellMetadata", attributes)
|
||||
self._xml_start_tag("bk")
|
||||
|
||||
# Write the rc element.
|
||||
self._write_rc()
|
||||
|
||||
self._xml_end_tag("bk")
|
||||
self._xml_end_tag("cellMetadata")
|
||||
|
||||
def _write_rc(self):
|
||||
# Write the <rc> element.
|
||||
attributes = [
|
||||
("t", 1),
|
||||
("v", 0),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("rc", attributes)
|
||||
711
billinglayer/python/xlsxwriter/packager.py
Normal file
711
billinglayer/python/xlsxwriter/packager.py
Normal file
@@ -0,0 +1,711 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Packager - A class for writing the Excel XLSX Worksheet file.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
# Standard packages.
|
||||
import os
|
||||
import stat
|
||||
import tempfile
|
||||
from shutil import copy
|
||||
|
||||
from io import StringIO
|
||||
from io import BytesIO
|
||||
|
||||
# Package imports.
|
||||
from .app import App
|
||||
from .contenttypes import ContentTypes
|
||||
from .core import Core
|
||||
from .custom import Custom
|
||||
from .metadata import Metadata
|
||||
from .relationships import Relationships
|
||||
from .sharedstrings import SharedStrings
|
||||
from .styles import Styles
|
||||
from .theme import Theme
|
||||
from .vml import Vml
|
||||
from .table import Table
|
||||
from .comments import Comments
|
||||
from .exceptions import EmptyChartSeries
|
||||
|
||||
|
||||
class Packager(object):
|
||||
"""
|
||||
A class for writing the Excel XLSX Packager file.
|
||||
|
||||
This module is used in conjunction with XlsxWriter to create an
|
||||
Excel XLSX container file.
|
||||
|
||||
From Wikipedia: The Open Packaging Conventions (OPC) is a
|
||||
container-file technology initially created by Microsoft to store
|
||||
a combination of XML and non-XML files that together form a single
|
||||
entity such as an Open XML Paper Specification (OpenXPS)
|
||||
document. http://en.wikipedia.org/wiki/Open_Packaging_Conventions.
|
||||
|
||||
At its simplest an Excel XLSX file contains the following elements::
|
||||
|
||||
____ [Content_Types].xml
|
||||
|
|
||||
|____ docProps
|
||||
| |____ app.xml
|
||||
| |____ core.xml
|
||||
|
|
||||
|____ xl
|
||||
| |____ workbook.xml
|
||||
| |____ worksheets
|
||||
| | |____ sheet1.xml
|
||||
| |
|
||||
| |____ styles.xml
|
||||
| |
|
||||
| |____ theme
|
||||
| | |____ theme1.xml
|
||||
| |
|
||||
| |_____rels
|
||||
| |____ workbook.xml.rels
|
||||
|
|
||||
|_____rels
|
||||
|____ .rels
|
||||
|
||||
The Packager class coordinates the classes that represent the
|
||||
elements of the package and writes them into the XLSX file.
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
|
||||
super(Packager, self).__init__()
|
||||
|
||||
self.tmpdir = ""
|
||||
self.in_memory = False
|
||||
self.workbook = None
|
||||
self.worksheet_count = 0
|
||||
self.chartsheet_count = 0
|
||||
self.chart_count = 0
|
||||
self.drawing_count = 0
|
||||
self.table_count = 0
|
||||
self.num_vml_files = 0
|
||||
self.num_comment_files = 0
|
||||
self.named_ranges = []
|
||||
self.filenames = []
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _set_tmpdir(self, tmpdir):
|
||||
# Set an optional user defined temp directory.
|
||||
self.tmpdir = tmpdir
|
||||
|
||||
def _set_in_memory(self, in_memory):
|
||||
# Set the optional 'in_memory' mode.
|
||||
self.in_memory = in_memory
|
||||
|
||||
def _add_workbook(self, workbook):
|
||||
# Add the Excel::Writer::XLSX::Workbook object to the package.
|
||||
self.workbook = workbook
|
||||
self.chart_count = len(workbook.charts)
|
||||
self.drawing_count = len(workbook.drawings)
|
||||
self.num_vml_files = workbook.num_vml_files
|
||||
self.num_comment_files = workbook.num_comment_files
|
||||
self.named_ranges = workbook.named_ranges
|
||||
|
||||
for worksheet in self.workbook.worksheets():
|
||||
if worksheet.is_chartsheet:
|
||||
self.chartsheet_count += 1
|
||||
else:
|
||||
self.worksheet_count += 1
|
||||
|
||||
def _create_package(self):
|
||||
# Write the xml files that make up the XLSX OPC package.
|
||||
self._write_content_types_file()
|
||||
self._write_root_rels_file()
|
||||
self._write_workbook_rels_file()
|
||||
self._write_worksheet_files()
|
||||
self._write_chartsheet_files()
|
||||
self._write_workbook_file()
|
||||
self._write_chart_files()
|
||||
self._write_drawing_files()
|
||||
self._write_vml_files()
|
||||
self._write_comment_files()
|
||||
self._write_table_files()
|
||||
self._write_shared_strings_file()
|
||||
self._write_styles_file()
|
||||
self._write_custom_file()
|
||||
self._write_theme_file()
|
||||
self._write_worksheet_rels_files()
|
||||
self._write_chartsheet_rels_files()
|
||||
self._write_drawing_rels_files()
|
||||
self._add_image_files()
|
||||
self._add_vba_project()
|
||||
self._write_core_file()
|
||||
self._write_app_file()
|
||||
self._write_metadata_file()
|
||||
|
||||
return self.filenames
|
||||
|
||||
def _filename(self, xml_filename):
|
||||
# Create a temp filename to write the XML data to and store the Excel
|
||||
# filename to use as the name in the Zip container.
|
||||
if self.in_memory:
|
||||
os_filename = StringIO()
|
||||
else:
|
||||
(fd, os_filename) = tempfile.mkstemp(dir=self.tmpdir)
|
||||
os.close(fd)
|
||||
|
||||
self.filenames.append((os_filename, xml_filename, False))
|
||||
|
||||
return os_filename
|
||||
|
||||
def _write_workbook_file(self):
|
||||
# Write the workbook.xml file.
|
||||
workbook = self.workbook
|
||||
|
||||
workbook._set_xml_writer(self._filename("xl/workbook.xml"))
|
||||
workbook._assemble_xml_file()
|
||||
|
||||
def _write_worksheet_files(self):
|
||||
# Write the worksheet files.
|
||||
index = 1
|
||||
for worksheet in self.workbook.worksheets():
|
||||
if worksheet.is_chartsheet:
|
||||
continue
|
||||
|
||||
if worksheet.constant_memory:
|
||||
worksheet._opt_reopen()
|
||||
worksheet._write_single_row()
|
||||
|
||||
worksheet._set_xml_writer(
|
||||
self._filename("xl/worksheets/sheet" + str(index) + ".xml")
|
||||
)
|
||||
worksheet._assemble_xml_file()
|
||||
index += 1
|
||||
|
||||
def _write_chartsheet_files(self):
|
||||
# Write the chartsheet files.
|
||||
index = 1
|
||||
for worksheet in self.workbook.worksheets():
|
||||
if not worksheet.is_chartsheet:
|
||||
continue
|
||||
|
||||
worksheet._set_xml_writer(
|
||||
self._filename("xl/chartsheets/sheet" + str(index) + ".xml")
|
||||
)
|
||||
worksheet._assemble_xml_file()
|
||||
index += 1
|
||||
|
||||
def _write_chart_files(self):
|
||||
# Write the chart files.
|
||||
if not self.workbook.charts:
|
||||
return
|
||||
|
||||
index = 1
|
||||
for chart in self.workbook.charts:
|
||||
# Check that the chart has at least one data series.
|
||||
if not chart.series:
|
||||
raise EmptyChartSeries(
|
||||
"Chart%d must contain at least one "
|
||||
"data series. See chart.add_series()." % index
|
||||
)
|
||||
|
||||
chart._set_xml_writer(
|
||||
self._filename("xl/charts/chart" + str(index) + ".xml")
|
||||
)
|
||||
chart._assemble_xml_file()
|
||||
index += 1
|
||||
|
||||
def _write_drawing_files(self):
|
||||
# Write the drawing files.
|
||||
if not self.drawing_count:
|
||||
return
|
||||
|
||||
index = 1
|
||||
for drawing in self.workbook.drawings:
|
||||
drawing._set_xml_writer(
|
||||
self._filename("xl/drawings/drawing" + str(index) + ".xml")
|
||||
)
|
||||
drawing._assemble_xml_file()
|
||||
index += 1
|
||||
|
||||
def _write_vml_files(self):
|
||||
# Write the comment VML files.
|
||||
index = 1
|
||||
for worksheet in self.workbook.worksheets():
|
||||
if not worksheet.has_vml and not worksheet.has_header_vml:
|
||||
continue
|
||||
if worksheet.has_vml:
|
||||
vml = Vml()
|
||||
vml._set_xml_writer(
|
||||
self._filename("xl/drawings/vmlDrawing" + str(index) + ".vml")
|
||||
)
|
||||
vml._assemble_xml_file(
|
||||
worksheet.vml_data_id,
|
||||
worksheet.vml_shape_id,
|
||||
worksheet.comments_list,
|
||||
worksheet.buttons_list,
|
||||
)
|
||||
index += 1
|
||||
|
||||
if worksheet.has_header_vml:
|
||||
vml = Vml()
|
||||
|
||||
vml._set_xml_writer(
|
||||
self._filename("xl/drawings/vmlDrawing" + str(index) + ".vml")
|
||||
)
|
||||
vml._assemble_xml_file(
|
||||
worksheet.vml_header_id,
|
||||
worksheet.vml_header_id * 1024,
|
||||
None,
|
||||
None,
|
||||
worksheet.header_images_list,
|
||||
)
|
||||
|
||||
self._write_vml_drawing_rels_file(worksheet, index)
|
||||
index += 1
|
||||
|
||||
def _write_comment_files(self):
|
||||
# Write the comment files.
|
||||
index = 1
|
||||
for worksheet in self.workbook.worksheets():
|
||||
if not worksheet.has_comments:
|
||||
continue
|
||||
|
||||
comment = Comments()
|
||||
comment._set_xml_writer(self._filename("xl/comments" + str(index) + ".xml"))
|
||||
comment._assemble_xml_file(worksheet.comments_list)
|
||||
index += 1
|
||||
|
||||
def _write_shared_strings_file(self):
|
||||
# Write the sharedStrings.xml file.
|
||||
sst = SharedStrings()
|
||||
sst.string_table = self.workbook.str_table
|
||||
|
||||
if not self.workbook.str_table.count:
|
||||
return
|
||||
|
||||
sst._set_xml_writer(self._filename("xl/sharedStrings.xml"))
|
||||
sst._assemble_xml_file()
|
||||
|
||||
def _write_app_file(self):
|
||||
# Write the app.xml file.
|
||||
properties = self.workbook.doc_properties
|
||||
app = App()
|
||||
|
||||
# Add the Worksheet heading pairs.
|
||||
app._add_heading_pair(["Worksheets", self.worksheet_count])
|
||||
|
||||
# Add the Chartsheet heading pairs.
|
||||
app._add_heading_pair(["Charts", self.chartsheet_count])
|
||||
|
||||
# Add the Worksheet parts.
|
||||
for worksheet in self.workbook.worksheets():
|
||||
if worksheet.is_chartsheet:
|
||||
continue
|
||||
app._add_part_name(worksheet.name)
|
||||
|
||||
# Add the Chartsheet parts.
|
||||
for worksheet in self.workbook.worksheets():
|
||||
if not worksheet.is_chartsheet:
|
||||
continue
|
||||
app._add_part_name(worksheet.name)
|
||||
|
||||
# Add the Named Range heading pairs.
|
||||
if self.named_ranges:
|
||||
app._add_heading_pair(["Named Ranges", len(self.named_ranges)])
|
||||
|
||||
# Add the Named Ranges parts.
|
||||
for named_range in self.named_ranges:
|
||||
app._add_part_name(named_range)
|
||||
|
||||
app._set_properties(properties)
|
||||
app.doc_security = self.workbook.read_only
|
||||
|
||||
app._set_xml_writer(self._filename("docProps/app.xml"))
|
||||
app._assemble_xml_file()
|
||||
|
||||
def _write_core_file(self):
|
||||
# Write the core.xml file.
|
||||
properties = self.workbook.doc_properties
|
||||
core = Core()
|
||||
|
||||
core._set_properties(properties)
|
||||
core._set_xml_writer(self._filename("docProps/core.xml"))
|
||||
core._assemble_xml_file()
|
||||
|
||||
def _write_metadata_file(self):
|
||||
# Write the metadata.xml file.
|
||||
if not self.workbook.has_metadata:
|
||||
return
|
||||
|
||||
metadata = Metadata()
|
||||
metadata._set_xml_writer(self._filename("xl/metadata.xml"))
|
||||
metadata._assemble_xml_file()
|
||||
|
||||
def _write_custom_file(self):
|
||||
# Write the custom.xml file.
|
||||
properties = self.workbook.custom_properties
|
||||
custom = Custom()
|
||||
|
||||
if not len(properties):
|
||||
return
|
||||
|
||||
custom._set_properties(properties)
|
||||
custom._set_xml_writer(self._filename("docProps/custom.xml"))
|
||||
custom._assemble_xml_file()
|
||||
|
||||
def _write_content_types_file(self):
|
||||
# Write the ContentTypes.xml file.
|
||||
content = ContentTypes()
|
||||
content._add_image_types(self.workbook.image_types)
|
||||
|
||||
self._get_table_count()
|
||||
|
||||
worksheet_index = 1
|
||||
chartsheet_index = 1
|
||||
for worksheet in self.workbook.worksheets():
|
||||
if worksheet.is_chartsheet:
|
||||
content._add_chartsheet_name("sheet" + str(chartsheet_index))
|
||||
chartsheet_index += 1
|
||||
else:
|
||||
content._add_worksheet_name("sheet" + str(worksheet_index))
|
||||
worksheet_index += 1
|
||||
|
||||
for i in range(1, self.chart_count + 1):
|
||||
content._add_chart_name("chart" + str(i))
|
||||
|
||||
for i in range(1, self.drawing_count + 1):
|
||||
content._add_drawing_name("drawing" + str(i))
|
||||
|
||||
if self.num_vml_files:
|
||||
content._add_vml_name()
|
||||
|
||||
for i in range(1, self.table_count + 1):
|
||||
content._add_table_name("table" + str(i))
|
||||
|
||||
for i in range(1, self.num_comment_files + 1):
|
||||
content._add_comment_name("comments" + str(i))
|
||||
|
||||
# Add the sharedString rel if there is string data in the workbook.
|
||||
if self.workbook.str_table.count:
|
||||
content._add_shared_strings()
|
||||
|
||||
# Add vbaProject if present.
|
||||
if self.workbook.vba_project:
|
||||
content._add_vba_project()
|
||||
|
||||
# Add the custom properties if present.
|
||||
if self.workbook.custom_properties:
|
||||
content._add_custom_properties()
|
||||
|
||||
# Add the metadata file if present.
|
||||
if self.workbook.has_metadata:
|
||||
content._add_metadata()
|
||||
|
||||
content._set_xml_writer(self._filename("[Content_Types].xml"))
|
||||
content._assemble_xml_file()
|
||||
|
||||
def _write_styles_file(self):
|
||||
# Write the style xml file.
|
||||
xf_formats = self.workbook.xf_formats
|
||||
palette = self.workbook.palette
|
||||
font_count = self.workbook.font_count
|
||||
num_formats = self.workbook.num_formats
|
||||
border_count = self.workbook.border_count
|
||||
fill_count = self.workbook.fill_count
|
||||
custom_colors = self.workbook.custom_colors
|
||||
dxf_formats = self.workbook.dxf_formats
|
||||
has_comments = self.workbook.has_comments
|
||||
|
||||
styles = Styles()
|
||||
styles._set_style_properties(
|
||||
[
|
||||
xf_formats,
|
||||
palette,
|
||||
font_count,
|
||||
num_formats,
|
||||
border_count,
|
||||
fill_count,
|
||||
custom_colors,
|
||||
dxf_formats,
|
||||
has_comments,
|
||||
]
|
||||
)
|
||||
|
||||
styles._set_xml_writer(self._filename("xl/styles.xml"))
|
||||
styles._assemble_xml_file()
|
||||
|
||||
def _write_theme_file(self):
|
||||
# Write the theme xml file.
|
||||
theme = Theme()
|
||||
|
||||
theme._set_xml_writer(self._filename("xl/theme/theme1.xml"))
|
||||
theme._assemble_xml_file()
|
||||
|
||||
def _write_table_files(self):
|
||||
# Write the table files.
|
||||
index = 1
|
||||
for worksheet in self.workbook.worksheets():
|
||||
table_props = worksheet.tables
|
||||
|
||||
if not table_props:
|
||||
continue
|
||||
|
||||
for table_props in table_props:
|
||||
table = Table()
|
||||
table._set_xml_writer(
|
||||
self._filename("xl/tables/table" + str(index) + ".xml")
|
||||
)
|
||||
table._set_properties(table_props)
|
||||
table._assemble_xml_file()
|
||||
index += 1
|
||||
|
||||
def _get_table_count(self):
|
||||
# Count the table files. Required for the [Content_Types] file.
|
||||
for worksheet in self.workbook.worksheets():
|
||||
for _ in worksheet.tables:
|
||||
self.table_count += 1
|
||||
|
||||
def _write_root_rels_file(self):
|
||||
# Write the _rels/.rels xml file.
|
||||
rels = Relationships()
|
||||
|
||||
rels._add_document_relationship("/officeDocument", "xl/workbook.xml")
|
||||
|
||||
rels._add_package_relationship("/metadata/core-properties", "docProps/core.xml")
|
||||
|
||||
rels._add_document_relationship("/extended-properties", "docProps/app.xml")
|
||||
|
||||
if self.workbook.custom_properties:
|
||||
rels._add_document_relationship("/custom-properties", "docProps/custom.xml")
|
||||
|
||||
rels._set_xml_writer(self._filename("_rels/.rels"))
|
||||
|
||||
rels._assemble_xml_file()
|
||||
|
||||
def _write_workbook_rels_file(self):
|
||||
# Write the _rels/.rels xml file.
|
||||
rels = Relationships()
|
||||
|
||||
worksheet_index = 1
|
||||
chartsheet_index = 1
|
||||
|
||||
for worksheet in self.workbook.worksheets():
|
||||
if worksheet.is_chartsheet:
|
||||
rels._add_document_relationship(
|
||||
"/chartsheet", "chartsheets/sheet" + str(chartsheet_index) + ".xml"
|
||||
)
|
||||
chartsheet_index += 1
|
||||
else:
|
||||
rels._add_document_relationship(
|
||||
"/worksheet", "worksheets/sheet" + str(worksheet_index) + ".xml"
|
||||
)
|
||||
worksheet_index += 1
|
||||
|
||||
rels._add_document_relationship("/theme", "theme/theme1.xml")
|
||||
rels._add_document_relationship("/styles", "styles.xml")
|
||||
|
||||
# Add the sharedString rel if there is string data in the workbook.
|
||||
if self.workbook.str_table.count:
|
||||
rels._add_document_relationship("/sharedStrings", "sharedStrings.xml")
|
||||
|
||||
# Add vbaProject if present.
|
||||
if self.workbook.vba_project:
|
||||
rels._add_ms_package_relationship("/vbaProject", "vbaProject.bin")
|
||||
|
||||
# Add the metadata file if required.
|
||||
if self.workbook.has_metadata:
|
||||
rels._add_document_relationship("/sheetMetadata", "metadata.xml")
|
||||
|
||||
rels._set_xml_writer(self._filename("xl/_rels/workbook.xml.rels"))
|
||||
rels._assemble_xml_file()
|
||||
|
||||
def _write_worksheet_rels_files(self):
|
||||
# Write data such as hyperlinks or drawings.
|
||||
index = 0
|
||||
for worksheet in self.workbook.worksheets():
|
||||
if worksheet.is_chartsheet:
|
||||
continue
|
||||
|
||||
index += 1
|
||||
|
||||
external_links = (
|
||||
worksheet.external_hyper_links
|
||||
+ worksheet.external_drawing_links
|
||||
+ worksheet.external_vml_links
|
||||
+ worksheet.external_background_links
|
||||
+ worksheet.external_table_links
|
||||
+ worksheet.external_comment_links
|
||||
)
|
||||
|
||||
if not external_links:
|
||||
continue
|
||||
|
||||
# Create the worksheet .rels dirs.
|
||||
rels = Relationships()
|
||||
|
||||
for link_data in external_links:
|
||||
rels._add_document_relationship(*link_data)
|
||||
|
||||
# Create .rels file such as /xl/worksheets/_rels/sheet1.xml.rels.
|
||||
rels._set_xml_writer(
|
||||
self._filename("xl/worksheets/_rels/sheet" + str(index) + ".xml.rels")
|
||||
)
|
||||
rels._assemble_xml_file()
|
||||
|
||||
def _write_chartsheet_rels_files(self):
|
||||
# Write the chartsheet .rels files for links to drawing files.
|
||||
index = 0
|
||||
for worksheet in self.workbook.worksheets():
|
||||
if not worksheet.is_chartsheet:
|
||||
continue
|
||||
|
||||
index += 1
|
||||
|
||||
external_links = worksheet.external_drawing_links
|
||||
|
||||
if not external_links:
|
||||
continue
|
||||
|
||||
# Create the chartsheet .rels xlsx_dir.
|
||||
rels = Relationships()
|
||||
|
||||
for link_data in external_links:
|
||||
rels._add_document_relationship(*link_data)
|
||||
|
||||
# Create .rels file such as /xl/chartsheets/_rels/sheet1.xml.rels.
|
||||
rels._set_xml_writer(
|
||||
self._filename("xl/chartsheets/_rels/sheet" + str(index) + ".xml.rels")
|
||||
)
|
||||
rels._assemble_xml_file()
|
||||
|
||||
def _write_drawing_rels_files(self):
|
||||
# Write the drawing .rels files for worksheets with charts or drawings.
|
||||
index = 0
|
||||
for worksheet in self.workbook.worksheets():
|
||||
if worksheet.drawing:
|
||||
index += 1
|
||||
|
||||
if not worksheet.drawing_links:
|
||||
continue
|
||||
|
||||
# Create the drawing .rels xlsx_dir.
|
||||
rels = Relationships()
|
||||
|
||||
for drawing_data in worksheet.drawing_links:
|
||||
rels._add_document_relationship(*drawing_data)
|
||||
|
||||
# Create .rels file such as /xl/drawings/_rels/sheet1.xml.rels.
|
||||
rels._set_xml_writer(
|
||||
self._filename("xl/drawings/_rels/drawing" + str(index) + ".xml.rels")
|
||||
)
|
||||
rels._assemble_xml_file()
|
||||
|
||||
def _write_vml_drawing_rels_file(self, worksheet, index):
|
||||
# Write the vmlDdrawing .rels files for worksheets with images in
|
||||
# headers or footers.
|
||||
|
||||
# Create the drawing .rels dir.
|
||||
rels = Relationships()
|
||||
|
||||
for drawing_data in worksheet.vml_drawing_links:
|
||||
rels._add_document_relationship(*drawing_data)
|
||||
|
||||
# Create .rels file such as /xl/drawings/_rels/vmlDrawing1.vml.rels.
|
||||
rels._set_xml_writer(
|
||||
self._filename("xl/drawings/_rels/vmlDrawing" + str(index) + ".vml.rels")
|
||||
)
|
||||
rels._assemble_xml_file()
|
||||
|
||||
def _add_image_files(self):
|
||||
# Write the /xl/media/image?.xml files.
|
||||
workbook = self.workbook
|
||||
index = 1
|
||||
|
||||
for image in workbook.images:
|
||||
filename = image[0]
|
||||
ext = "." + image[1]
|
||||
image_data = image[2]
|
||||
|
||||
xml_image_name = "xl/media/image" + str(index) + ext
|
||||
|
||||
if not self.in_memory:
|
||||
# In file mode we just write or copy the image file.
|
||||
os_filename = self._filename(xml_image_name)
|
||||
|
||||
if image_data:
|
||||
# The data is in a byte stream. Write it to the target.
|
||||
os_file = open(os_filename, mode="wb")
|
||||
os_file.write(image_data.getvalue())
|
||||
os_file.close()
|
||||
else:
|
||||
copy(filename, os_filename)
|
||||
|
||||
# Allow copies of Windows read-only images to be deleted.
|
||||
try:
|
||||
os.chmod(
|
||||
os_filename, os.stat(os_filename).st_mode | stat.S_IWRITE
|
||||
)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
# For in-memory mode we read the image into a stream.
|
||||
if image_data:
|
||||
# The data is already in a byte stream.
|
||||
os_filename = image_data
|
||||
else:
|
||||
image_file = open(filename, mode="rb")
|
||||
image_data = image_file.read()
|
||||
os_filename = BytesIO(image_data)
|
||||
image_file.close()
|
||||
|
||||
self.filenames.append((os_filename, xml_image_name, True))
|
||||
|
||||
index += 1
|
||||
|
||||
def _add_vba_project(self):
|
||||
# Copy in a vbaProject.bin file.
|
||||
vba_project = self.workbook.vba_project
|
||||
vba_is_stream = self.workbook.vba_is_stream
|
||||
|
||||
if not vba_project:
|
||||
return
|
||||
|
||||
xml_vba_name = "xl/vbaProject.bin"
|
||||
|
||||
if not self.in_memory:
|
||||
# In file mode we just write or copy the VBA file.
|
||||
os_filename = self._filename(xml_vba_name)
|
||||
|
||||
if vba_is_stream:
|
||||
# The data is in a byte stream. Write it to the target.
|
||||
os_file = open(os_filename, mode="wb")
|
||||
os_file.write(vba_project.getvalue())
|
||||
os_file.close()
|
||||
else:
|
||||
copy(vba_project, os_filename)
|
||||
|
||||
else:
|
||||
# For in-memory mode we read the vba into a stream.
|
||||
if vba_is_stream:
|
||||
# The data is already in a byte stream.
|
||||
os_filename = vba_project
|
||||
else:
|
||||
vba_file = open(vba_project, mode="rb")
|
||||
vba_data = vba_file.read()
|
||||
os_filename = BytesIO(vba_data)
|
||||
vba_file.close()
|
||||
|
||||
self.filenames.append((os_filename, xml_vba_name, True))
|
||||
115
billinglayer/python/xlsxwriter/relationships.py
Normal file
115
billinglayer/python/xlsxwriter/relationships.py
Normal file
@@ -0,0 +1,115 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Relationships - A class for writing the Excel XLSX Worksheet file.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
# Package imports.
|
||||
from . import xmlwriter
|
||||
|
||||
# Long namespace strings used in the class.
|
||||
schema_root = "http://schemas.openxmlformats.org"
|
||||
package_schema = schema_root + "/package/2006/relationships"
|
||||
document_schema = schema_root + "/officeDocument/2006/relationships"
|
||||
|
||||
|
||||
class Relationships(xmlwriter.XMLwriter):
|
||||
"""
|
||||
A class for writing the Excel XLSX Relationships file.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
|
||||
super(Relationships, self).__init__()
|
||||
|
||||
self.relationships = []
|
||||
self.id = 1
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _assemble_xml_file(self):
|
||||
# Assemble and write the XML file.
|
||||
|
||||
# Write the XML declaration.
|
||||
self._xml_declaration()
|
||||
|
||||
self._write_relationships()
|
||||
|
||||
# Close the file.
|
||||
self._xml_close()
|
||||
|
||||
def _add_document_relationship(self, rel_type, target, target_mode=None):
|
||||
# Add container relationship to XLSX .rels xml files.
|
||||
rel_type = document_schema + rel_type
|
||||
|
||||
self.relationships.append((rel_type, target, target_mode))
|
||||
|
||||
def _add_package_relationship(self, rel_type, target):
|
||||
# Add container relationship to XLSX .rels xml files.
|
||||
rel_type = package_schema + rel_type
|
||||
|
||||
self.relationships.append((rel_type, target, None))
|
||||
|
||||
def _add_ms_package_relationship(self, rel_type, target):
|
||||
# Add container relationship to XLSX .rels xml files. Uses MS schema.
|
||||
schema = "http://schemas.microsoft.com/office/2006/relationships"
|
||||
rel_type = schema + rel_type
|
||||
|
||||
self.relationships.append((rel_type, target, None))
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_relationships(self):
|
||||
# Write the <Relationships> element.
|
||||
attributes = [
|
||||
(
|
||||
"xmlns",
|
||||
package_schema,
|
||||
)
|
||||
]
|
||||
|
||||
self._xml_start_tag("Relationships", attributes)
|
||||
|
||||
for relationship in self.relationships:
|
||||
self._write_relationship(relationship)
|
||||
|
||||
self._xml_end_tag("Relationships")
|
||||
|
||||
def _write_relationship(self, relationship):
|
||||
# Write the <Relationship> element.
|
||||
rel_type, target, target_mode = relationship
|
||||
|
||||
attributes = [
|
||||
("Id", "rId" + str(self.id)),
|
||||
("Type", rel_type),
|
||||
("Target", target),
|
||||
]
|
||||
|
||||
self.id += 1
|
||||
|
||||
if target_mode:
|
||||
attributes.append(("TargetMode", target_mode))
|
||||
|
||||
self._xml_empty_tag("Relationship", attributes)
|
||||
414
billinglayer/python/xlsxwriter/shape.py
Normal file
414
billinglayer/python/xlsxwriter/shape.py
Normal file
@@ -0,0 +1,414 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Shape - A class for to represent Excel XLSX shape objects.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
import copy
|
||||
from warnings import warn
|
||||
|
||||
|
||||
class Shape(object):
|
||||
"""
|
||||
A class for to represent Excel XLSX shape objects.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self, shape_type, name, options):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
super(Shape, self).__init__()
|
||||
self.name = name
|
||||
self.shape_type = shape_type
|
||||
self.connect = 0
|
||||
self.drawing = 0
|
||||
self.edit_as = ""
|
||||
self.id = 0
|
||||
self.text = ""
|
||||
self.textlink = ""
|
||||
self.stencil = 1
|
||||
self.element = -1
|
||||
self.start = None
|
||||
self.start_index = None
|
||||
self.end = None
|
||||
self.end_index = None
|
||||
self.adjustments = []
|
||||
self.start_side = ""
|
||||
self.end_side = ""
|
||||
self.flip_h = 0
|
||||
self.flip_v = 0
|
||||
self.rotation = 0
|
||||
self.text_rotation = 0
|
||||
self.textbox = False
|
||||
|
||||
self.align = None
|
||||
self.fill = None
|
||||
self.font = None
|
||||
self.format = None
|
||||
self.line = None
|
||||
self.url_rel_index = None
|
||||
self.tip = None
|
||||
|
||||
self._set_options(options)
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _set_options(self, options):
|
||||
self.align = self._get_align_properties(options.get("align"))
|
||||
self.fill = self._get_fill_properties(options.get("fill"))
|
||||
self.font = self._get_font_properties(options.get("font"))
|
||||
self.gradient = self._get_gradient_properties(options.get("gradient"))
|
||||
self.line = self._get_line_properties(options.get("line"))
|
||||
|
||||
self.text_rotation = options.get("text_rotation", 0)
|
||||
|
||||
self.textlink = options.get("textlink", "")
|
||||
if self.textlink.startswith("="):
|
||||
self.textlink = self.textlink.lstrip("=")
|
||||
|
||||
if options.get("border"):
|
||||
self.line = self._get_line_properties(options["border"])
|
||||
|
||||
# Gradient fill overrides solid fill.
|
||||
if self.gradient:
|
||||
self.fill = None
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Static methods for processing chart/shape style properties.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
@staticmethod
|
||||
def _get_line_properties(line):
|
||||
# Convert user line properties to the structure required internally.
|
||||
|
||||
if not line:
|
||||
return {"defined": False}
|
||||
|
||||
# Copy the user defined properties since they will be modified.
|
||||
line = copy.deepcopy(line)
|
||||
|
||||
dash_types = {
|
||||
"solid": "solid",
|
||||
"round_dot": "sysDot",
|
||||
"square_dot": "sysDash",
|
||||
"dash": "dash",
|
||||
"dash_dot": "dashDot",
|
||||
"long_dash": "lgDash",
|
||||
"long_dash_dot": "lgDashDot",
|
||||
"long_dash_dot_dot": "lgDashDotDot",
|
||||
"dot": "dot",
|
||||
"system_dash_dot": "sysDashDot",
|
||||
"system_dash_dot_dot": "sysDashDotDot",
|
||||
}
|
||||
|
||||
# Check the dash type.
|
||||
dash_type = line.get("dash_type")
|
||||
|
||||
if dash_type is not None:
|
||||
if dash_type in dash_types:
|
||||
line["dash_type"] = dash_types[dash_type]
|
||||
else:
|
||||
warn("Unknown dash type '%s'" % dash_type)
|
||||
return
|
||||
|
||||
line["defined"] = True
|
||||
|
||||
return line
|
||||
|
||||
@staticmethod
|
||||
def _get_fill_properties(fill):
|
||||
# Convert user fill properties to the structure required internally.
|
||||
|
||||
if not fill:
|
||||
return {"defined": False}
|
||||
|
||||
# Copy the user defined properties since they will be modified.
|
||||
fill = copy.deepcopy(fill)
|
||||
|
||||
fill["defined"] = True
|
||||
|
||||
return fill
|
||||
|
||||
@staticmethod
|
||||
def _get_pattern_properties(pattern):
|
||||
# Convert user defined pattern to the structure required internally.
|
||||
|
||||
if not pattern:
|
||||
return
|
||||
|
||||
# Copy the user defined properties since they will be modified.
|
||||
pattern = copy.deepcopy(pattern)
|
||||
|
||||
if not pattern.get("pattern"):
|
||||
warn("Pattern must include 'pattern'")
|
||||
return
|
||||
|
||||
if not pattern.get("fg_color"):
|
||||
warn("Pattern must include 'fg_color'")
|
||||
return
|
||||
|
||||
types = {
|
||||
"percent_5": "pct5",
|
||||
"percent_10": "pct10",
|
||||
"percent_20": "pct20",
|
||||
"percent_25": "pct25",
|
||||
"percent_30": "pct30",
|
||||
"percent_40": "pct40",
|
||||
"percent_50": "pct50",
|
||||
"percent_60": "pct60",
|
||||
"percent_70": "pct70",
|
||||
"percent_75": "pct75",
|
||||
"percent_80": "pct80",
|
||||
"percent_90": "pct90",
|
||||
"light_downward_diagonal": "ltDnDiag",
|
||||
"light_upward_diagonal": "ltUpDiag",
|
||||
"dark_downward_diagonal": "dkDnDiag",
|
||||
"dark_upward_diagonal": "dkUpDiag",
|
||||
"wide_downward_diagonal": "wdDnDiag",
|
||||
"wide_upward_diagonal": "wdUpDiag",
|
||||
"light_vertical": "ltVert",
|
||||
"light_horizontal": "ltHorz",
|
||||
"narrow_vertical": "narVert",
|
||||
"narrow_horizontal": "narHorz",
|
||||
"dark_vertical": "dkVert",
|
||||
"dark_horizontal": "dkHorz",
|
||||
"dashed_downward_diagonal": "dashDnDiag",
|
||||
"dashed_upward_diagonal": "dashUpDiag",
|
||||
"dashed_horizontal": "dashHorz",
|
||||
"dashed_vertical": "dashVert",
|
||||
"small_confetti": "smConfetti",
|
||||
"large_confetti": "lgConfetti",
|
||||
"zigzag": "zigZag",
|
||||
"wave": "wave",
|
||||
"diagonal_brick": "diagBrick",
|
||||
"horizontal_brick": "horzBrick",
|
||||
"weave": "weave",
|
||||
"plaid": "plaid",
|
||||
"divot": "divot",
|
||||
"dotted_grid": "dotGrid",
|
||||
"dotted_diamond": "dotDmnd",
|
||||
"shingle": "shingle",
|
||||
"trellis": "trellis",
|
||||
"sphere": "sphere",
|
||||
"small_grid": "smGrid",
|
||||
"large_grid": "lgGrid",
|
||||
"small_check": "smCheck",
|
||||
"large_check": "lgCheck",
|
||||
"outlined_diamond": "openDmnd",
|
||||
"solid_diamond": "solidDmnd",
|
||||
}
|
||||
|
||||
# Check for valid types.
|
||||
if pattern["pattern"] not in types:
|
||||
warn("unknown pattern type '%s'" % pattern["pattern"])
|
||||
return
|
||||
else:
|
||||
pattern["pattern"] = types[pattern["pattern"]]
|
||||
|
||||
# Specify a default background color.
|
||||
pattern["bg_color"] = pattern.get("bg_color", "#FFFFFF")
|
||||
|
||||
return pattern
|
||||
|
||||
@staticmethod
|
||||
def _get_gradient_properties(gradient):
|
||||
# Convert user defined gradient to the structure required internally.
|
||||
|
||||
if not gradient:
|
||||
return
|
||||
|
||||
# Copy the user defined properties since they will be modified.
|
||||
gradient = copy.deepcopy(gradient)
|
||||
|
||||
types = {
|
||||
"linear": "linear",
|
||||
"radial": "circle",
|
||||
"rectangular": "rect",
|
||||
"path": "shape",
|
||||
}
|
||||
|
||||
# Check the colors array exists and is valid.
|
||||
if "colors" not in gradient or type(gradient["colors"]) != list:
|
||||
warn("Gradient must include colors list")
|
||||
return
|
||||
|
||||
# Check the colors array has the required number of entries.
|
||||
if not 2 <= len(gradient["colors"]) <= 10:
|
||||
warn("Gradient colors list must at least 2 values and not more than 10")
|
||||
return
|
||||
|
||||
if "positions" in gradient:
|
||||
# Check the positions array has the right number of entries.
|
||||
if len(gradient["positions"]) != len(gradient["colors"]):
|
||||
warn("Gradient positions not equal to number of colors")
|
||||
return
|
||||
|
||||
# Check the positions are in the correct range.
|
||||
for pos in gradient["positions"]:
|
||||
if not 0 <= pos <= 100:
|
||||
warn("Gradient position must be in the range 0 <= position <= 100")
|
||||
return
|
||||
else:
|
||||
# Use the default gradient positions.
|
||||
if len(gradient["colors"]) == 2:
|
||||
gradient["positions"] = [0, 100]
|
||||
|
||||
elif len(gradient["colors"]) == 3:
|
||||
gradient["positions"] = [0, 50, 100]
|
||||
|
||||
elif len(gradient["colors"]) == 4:
|
||||
gradient["positions"] = [0, 33, 66, 100]
|
||||
|
||||
else:
|
||||
warn("Must specify gradient positions")
|
||||
return
|
||||
|
||||
angle = gradient.get("angle")
|
||||
if angle:
|
||||
if not 0 <= angle < 360:
|
||||
warn("Gradient angle must be in the range 0 <= angle < 360")
|
||||
return
|
||||
else:
|
||||
gradient["angle"] = 90
|
||||
|
||||
# Check for valid types.
|
||||
gradient_type = gradient.get("type")
|
||||
|
||||
if gradient_type is not None:
|
||||
if gradient_type in types:
|
||||
gradient["type"] = types[gradient_type]
|
||||
else:
|
||||
warn("Unknown gradient type '%s" % gradient_type)
|
||||
return
|
||||
else:
|
||||
gradient["type"] = "linear"
|
||||
|
||||
return gradient
|
||||
|
||||
@staticmethod
|
||||
def _get_font_properties(options):
|
||||
# Convert user defined font values into private dict values.
|
||||
if options is None:
|
||||
options = {}
|
||||
|
||||
font = {
|
||||
"name": options.get("name"),
|
||||
"color": options.get("color"),
|
||||
"size": options.get("size", 11),
|
||||
"bold": options.get("bold"),
|
||||
"italic": options.get("italic"),
|
||||
"underline": options.get("underline"),
|
||||
"pitch_family": options.get("pitch_family"),
|
||||
"charset": options.get("charset"),
|
||||
"baseline": options.get("baseline", -1),
|
||||
"lang": options.get("lang", "en-US"),
|
||||
}
|
||||
|
||||
# Convert font size units.
|
||||
if font["size"]:
|
||||
font["size"] = int(font["size"] * 100)
|
||||
|
||||
return font
|
||||
|
||||
@staticmethod
|
||||
def _get_font_style_attributes(font):
|
||||
# _get_font_style_attributes.
|
||||
attributes = []
|
||||
|
||||
if not font:
|
||||
return attributes
|
||||
|
||||
if font.get("size"):
|
||||
attributes.append(("sz", font["size"]))
|
||||
|
||||
if font.get("bold") is not None:
|
||||
attributes.append(("b", 0 + font["bold"]))
|
||||
|
||||
if font.get("italic") is not None:
|
||||
attributes.append(("i", 0 + font["italic"]))
|
||||
|
||||
if font.get("underline") is not None:
|
||||
attributes.append(("u", "sng"))
|
||||
|
||||
if font.get("baseline") != -1:
|
||||
attributes.append(("baseline", font["baseline"]))
|
||||
|
||||
return attributes
|
||||
|
||||
@staticmethod
|
||||
def _get_font_latin_attributes(font):
|
||||
# _get_font_latin_attributes.
|
||||
attributes = []
|
||||
|
||||
if not font:
|
||||
return attributes
|
||||
|
||||
if font["name"] is not None:
|
||||
attributes.append(("typeface", font["name"]))
|
||||
|
||||
if font["pitch_family"] is not None:
|
||||
attributes.append(("pitchFamily", font["pitch_family"]))
|
||||
|
||||
if font["charset"] is not None:
|
||||
attributes.append(("charset", font["charset"]))
|
||||
|
||||
return attributes
|
||||
|
||||
@staticmethod
|
||||
def _get_align_properties(align):
|
||||
# Convert user defined align to the structure required internally.
|
||||
if not align:
|
||||
return {"defined": False}
|
||||
|
||||
# Copy the user defined properties since they will be modified.
|
||||
align = copy.deepcopy(align)
|
||||
|
||||
if "vertical" in align:
|
||||
align_type = align["vertical"]
|
||||
|
||||
align_types = {
|
||||
"top": "top",
|
||||
"middle": "middle",
|
||||
"bottom": "bottom",
|
||||
}
|
||||
|
||||
if align_type in align_types:
|
||||
align["vertical"] = align_types[align_type]
|
||||
else:
|
||||
warn("Unknown alignment type '%s'" % align_type)
|
||||
return {"defined": False}
|
||||
|
||||
if "horizontal" in align:
|
||||
align_type = align["horizontal"]
|
||||
|
||||
align_types = {
|
||||
"left": "left",
|
||||
"center": "center",
|
||||
"right": "right",
|
||||
}
|
||||
|
||||
if align_type in align_types:
|
||||
align["horizontal"] = align_types[align_type]
|
||||
else:
|
||||
warn("Unknown alignment type '%s'" % align_type)
|
||||
return {"defined": False}
|
||||
|
||||
align["defined"] = True
|
||||
|
||||
return align
|
||||
158
billinglayer/python/xlsxwriter/sharedstrings.py
Normal file
158
billinglayer/python/xlsxwriter/sharedstrings.py
Normal file
@@ -0,0 +1,158 @@
|
||||
###############################################################################
|
||||
#
|
||||
# SharedStrings - A class for writing the Excel XLSX sharedStrings file.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
# Standard packages.
|
||||
import re
|
||||
|
||||
# Package imports.
|
||||
from . import xmlwriter
|
||||
from .utility import preserve_whitespace
|
||||
|
||||
# Compile performance critical regular expressions.
|
||||
re_control_chars_1 = re.compile("(_x[0-9a-fA-F]{4}_)")
|
||||
re_control_chars_2 = re.compile(r"([\x00-\x08\x0b-\x1f])")
|
||||
|
||||
|
||||
class SharedStrings(xmlwriter.XMLwriter):
|
||||
"""
|
||||
A class for writing the Excel XLSX sharedStrings file.
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
|
||||
super(SharedStrings, self).__init__()
|
||||
|
||||
self.string_table = None
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _assemble_xml_file(self):
|
||||
# Assemble and write the XML file.
|
||||
|
||||
# Write the XML declaration.
|
||||
self._xml_declaration()
|
||||
|
||||
# Write the sst element.
|
||||
self._write_sst()
|
||||
|
||||
# Write the sst strings.
|
||||
self._write_sst_strings()
|
||||
|
||||
# Close the sst tag.
|
||||
self._xml_end_tag("sst")
|
||||
|
||||
# Close the file.
|
||||
self._xml_close()
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_sst(self):
|
||||
# Write the <sst> element.
|
||||
xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
|
||||
attributes = [
|
||||
("xmlns", xmlns),
|
||||
("count", self.string_table.count),
|
||||
("uniqueCount", self.string_table.unique_count),
|
||||
]
|
||||
|
||||
self._xml_start_tag("sst", attributes)
|
||||
|
||||
def _write_sst_strings(self):
|
||||
# Write the sst string elements.
|
||||
|
||||
for string in self.string_table.string_array:
|
||||
self._write_si(string)
|
||||
|
||||
def _write_si(self, string):
|
||||
# Write the <si> element.
|
||||
attributes = []
|
||||
|
||||
# Excel escapes control characters with _xHHHH_ and also escapes any
|
||||
# literal strings of that type by encoding the leading underscore.
|
||||
# So "\0" -> _x0000_ and "_x0000_" -> _x005F_x0000_.
|
||||
# The following substitutions deal with those cases.
|
||||
|
||||
# Escape the escape.
|
||||
string = re_control_chars_1.sub(r"_x005F\1", string)
|
||||
|
||||
# Convert control character to the _xHHHH_ escape.
|
||||
string = re_control_chars_2.sub(
|
||||
lambda match: "_x%04X_" % ord(match.group(1)), string
|
||||
)
|
||||
|
||||
# Escapes non characters in strings.
|
||||
string = string.replace("\uFFFE", "_xFFFE_")
|
||||
string = string.replace("\uFFFF", "_xFFFF_")
|
||||
|
||||
# Add attribute to preserve leading or trailing whitespace.
|
||||
if preserve_whitespace(string):
|
||||
attributes.append(("xml:space", "preserve"))
|
||||
|
||||
# Write any rich strings without further tags.
|
||||
if string.startswith("<r>") and string.endswith("</r>"):
|
||||
self._xml_rich_si_element(string)
|
||||
else:
|
||||
self._xml_si_element(string, attributes)
|
||||
|
||||
|
||||
# A metadata class to store Excel strings between worksheets.
|
||||
class SharedStringTable(object):
|
||||
"""
|
||||
A class to track Excel shared strings between worksheets.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.count = 0
|
||||
self.unique_count = 0
|
||||
self.string_table = {}
|
||||
self.string_array = []
|
||||
|
||||
def _get_shared_string_index(self, string):
|
||||
""" " Get the index of the string in the Shared String table."""
|
||||
if string not in self.string_table:
|
||||
# String isn't already stored in the table so add it.
|
||||
index = self.unique_count
|
||||
self.string_table[string] = index
|
||||
self.count += 1
|
||||
self.unique_count += 1
|
||||
return index
|
||||
else:
|
||||
# String exists in the table.
|
||||
index = self.string_table[string]
|
||||
self.count += 1
|
||||
return index
|
||||
|
||||
def _get_shared_string(self, index):
|
||||
""" " Get a shared string from the index."""
|
||||
return self.string_array[index]
|
||||
|
||||
def _sort_string_data(self):
|
||||
""" " Sort the shared string data and convert from dict to list."""
|
||||
self.string_array = sorted(self.string_table, key=self.string_table.__getitem__)
|
||||
self.string_table = {}
|
||||
754
billinglayer/python/xlsxwriter/styles.py
Normal file
754
billinglayer/python/xlsxwriter/styles.py
Normal file
@@ -0,0 +1,754 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Styles - A class for writing the Excel XLSX Worksheet file.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
# Package imports.
|
||||
from . import xmlwriter
|
||||
|
||||
|
||||
class Styles(xmlwriter.XMLwriter):
|
||||
"""
|
||||
A class for writing the Excel XLSX Styles file.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
|
||||
super(Styles, self).__init__()
|
||||
|
||||
self.xf_formats = []
|
||||
self.palette = []
|
||||
self.font_count = 0
|
||||
self.num_formats = []
|
||||
self.border_count = 0
|
||||
self.fill_count = 0
|
||||
self.custom_colors = []
|
||||
self.dxf_formats = []
|
||||
self.has_hyperlink = False
|
||||
self.hyperlink_font_id = 0
|
||||
self.has_comments = False
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _assemble_xml_file(self):
|
||||
# Assemble and write the XML file.
|
||||
|
||||
# Write the XML declaration.
|
||||
self._xml_declaration()
|
||||
|
||||
# Add the style sheet.
|
||||
self._write_style_sheet()
|
||||
|
||||
# Write the number formats.
|
||||
self._write_num_fmts()
|
||||
|
||||
# Write the fonts.
|
||||
self._write_fonts()
|
||||
|
||||
# Write the fills.
|
||||
self._write_fills()
|
||||
|
||||
# Write the borders element.
|
||||
self._write_borders()
|
||||
|
||||
# Write the cellStyleXfs element.
|
||||
self._write_cell_style_xfs()
|
||||
|
||||
# Write the cellXfs element.
|
||||
self._write_cell_xfs()
|
||||
|
||||
# Write the cellStyles element.
|
||||
self._write_cell_styles()
|
||||
|
||||
# Write the dxfs element.
|
||||
self._write_dxfs()
|
||||
|
||||
# Write the tableStyles element.
|
||||
self._write_table_styles()
|
||||
|
||||
# Write the colors element.
|
||||
self._write_colors()
|
||||
|
||||
# Close the style sheet tag.
|
||||
self._xml_end_tag("styleSheet")
|
||||
|
||||
# Close the file.
|
||||
self._xml_close()
|
||||
|
||||
def _set_style_properties(self, properties):
|
||||
# Pass in the Format objects and other properties used in the styles.
|
||||
|
||||
self.xf_formats = properties[0]
|
||||
self.palette = properties[1]
|
||||
self.font_count = properties[2]
|
||||
self.num_formats = properties[3]
|
||||
self.border_count = properties[4]
|
||||
self.fill_count = properties[5]
|
||||
self.custom_colors = properties[6]
|
||||
self.dxf_formats = properties[7]
|
||||
self.has_comments = properties[8]
|
||||
|
||||
def _get_palette_color(self, color):
|
||||
# Special handling for automatic color.
|
||||
if color == "Automatic":
|
||||
return color
|
||||
|
||||
# Convert the RGB color.
|
||||
if color[0] == "#":
|
||||
color = color[1:]
|
||||
|
||||
return "FF" + color.upper()
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_style_sheet(self):
|
||||
# Write the <styleSheet> element.
|
||||
xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
|
||||
attributes = [("xmlns", xmlns)]
|
||||
self._xml_start_tag("styleSheet", attributes)
|
||||
|
||||
def _write_num_fmts(self):
|
||||
# Write the <numFmts> element.
|
||||
if not self.num_formats:
|
||||
return
|
||||
|
||||
attributes = [("count", len(self.num_formats))]
|
||||
self._xml_start_tag("numFmts", attributes)
|
||||
|
||||
# Write the numFmts elements.
|
||||
for index, num_format in enumerate(self.num_formats, 164):
|
||||
self._write_num_fmt(index, num_format)
|
||||
|
||||
self._xml_end_tag("numFmts")
|
||||
|
||||
def _write_num_fmt(self, num_fmt_id, format_code):
|
||||
# Write the <numFmt> element.
|
||||
format_codes = {
|
||||
0: "General",
|
||||
1: "0",
|
||||
2: "0.00",
|
||||
3: "#,##0",
|
||||
4: "#,##0.00",
|
||||
5: "($#,##0_);($#,##0)",
|
||||
6: "($#,##0_);[Red]($#,##0)",
|
||||
7: "($#,##0.00_);($#,##0.00)",
|
||||
8: "($#,##0.00_);[Red]($#,##0.00)",
|
||||
9: "0%",
|
||||
10: "0.00%",
|
||||
11: "0.00E+00",
|
||||
12: "# ?/?",
|
||||
13: "# ??/??",
|
||||
14: "m/d/yy",
|
||||
15: "d-mmm-yy",
|
||||
16: "d-mmm",
|
||||
17: "mmm-yy",
|
||||
18: "h:mm AM/PM",
|
||||
19: "h:mm:ss AM/PM",
|
||||
20: "h:mm",
|
||||
21: "h:mm:ss",
|
||||
22: "m/d/yy h:mm",
|
||||
37: "(#,##0_);(#,##0)",
|
||||
38: "(#,##0_);[Red](#,##0)",
|
||||
39: "(#,##0.00_);(#,##0.00)",
|
||||
40: "(#,##0.00_);[Red](#,##0.00)",
|
||||
41: '_(* #,##0_);_(* (#,##0);_(* "-"_);_(_)',
|
||||
42: '_($* #,##0_);_($* (#,##0);_($* "-"_);_(_)',
|
||||
43: '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(_)',
|
||||
44: '_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(_)',
|
||||
45: "mm:ss",
|
||||
46: "[h]:mm:ss",
|
||||
47: "mm:ss.0",
|
||||
48: "##0.0E+0",
|
||||
49: "@",
|
||||
}
|
||||
|
||||
# Set the format code for built-in number formats.
|
||||
if num_fmt_id < 164:
|
||||
if num_fmt_id in format_codes:
|
||||
format_code = format_codes[num_fmt_id]
|
||||
else:
|
||||
format_code = "General"
|
||||
|
||||
attributes = [
|
||||
("numFmtId", num_fmt_id),
|
||||
("formatCode", format_code),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("numFmt", attributes)
|
||||
|
||||
def _write_fonts(self):
|
||||
# Write the <fonts> element.
|
||||
if self.has_comments:
|
||||
# Add extra font for comments.
|
||||
attributes = [("count", self.font_count + 1)]
|
||||
else:
|
||||
attributes = [("count", self.font_count)]
|
||||
|
||||
self._xml_start_tag("fonts", attributes)
|
||||
|
||||
# Write the font elements for xf_format objects that have them.
|
||||
for xf_format in self.xf_formats:
|
||||
if xf_format.has_font:
|
||||
self._write_font(xf_format)
|
||||
|
||||
if self.has_comments:
|
||||
self._write_comment_font()
|
||||
|
||||
self._xml_end_tag("fonts")
|
||||
|
||||
def _write_font(self, xf_format, is_dxf_format=False):
|
||||
# Write the <font> element.
|
||||
self._xml_start_tag("font")
|
||||
|
||||
# The condense and extend elements are mainly used in dxf formats.
|
||||
if xf_format.font_condense:
|
||||
self._write_condense()
|
||||
|
||||
if xf_format.font_extend:
|
||||
self._write_extend()
|
||||
|
||||
if xf_format.bold:
|
||||
self._xml_empty_tag("b")
|
||||
|
||||
if xf_format.italic:
|
||||
self._xml_empty_tag("i")
|
||||
|
||||
if xf_format.font_strikeout:
|
||||
self._xml_empty_tag("strike")
|
||||
|
||||
if xf_format.font_outline:
|
||||
self._xml_empty_tag("outline")
|
||||
|
||||
if xf_format.font_shadow:
|
||||
self._xml_empty_tag("shadow")
|
||||
|
||||
# Handle the underline variants.
|
||||
if xf_format.underline:
|
||||
self._write_underline(xf_format.underline)
|
||||
|
||||
if xf_format.font_script == 1:
|
||||
self._write_vert_align("superscript")
|
||||
|
||||
if xf_format.font_script == 2:
|
||||
self._write_vert_align("subscript")
|
||||
|
||||
if not is_dxf_format:
|
||||
self._xml_empty_tag("sz", [("val", xf_format.font_size)])
|
||||
|
||||
if xf_format.theme == -1:
|
||||
# Ignore for excel2003_style.
|
||||
pass
|
||||
elif xf_format.theme:
|
||||
self._write_color("theme", xf_format.theme)
|
||||
elif xf_format.color_indexed:
|
||||
self._write_color("indexed", xf_format.color_indexed)
|
||||
elif xf_format.font_color:
|
||||
color = self._get_palette_color(xf_format.font_color)
|
||||
if color != "Automatic":
|
||||
self._write_color("rgb", color)
|
||||
elif not is_dxf_format:
|
||||
self._write_color("theme", 1)
|
||||
|
||||
if not is_dxf_format:
|
||||
self._xml_empty_tag("name", [("val", xf_format.font_name)])
|
||||
|
||||
if xf_format.font_family:
|
||||
self._xml_empty_tag("family", [("val", xf_format.font_family)])
|
||||
|
||||
if xf_format.font_charset:
|
||||
self._xml_empty_tag("charset", [("val", xf_format.font_charset)])
|
||||
|
||||
if xf_format.font_name == "Calibri" and not xf_format.hyperlink:
|
||||
self._xml_empty_tag("scheme", [("val", xf_format.font_scheme)])
|
||||
|
||||
if xf_format.hyperlink:
|
||||
self.has_hyperlink = True
|
||||
if self.hyperlink_font_id == 0:
|
||||
self.hyperlink_font_id = xf_format.font_index
|
||||
|
||||
self._xml_end_tag("font")
|
||||
|
||||
def _write_comment_font(self):
|
||||
# Write the <font> element for comments.
|
||||
self._xml_start_tag("font")
|
||||
|
||||
self._xml_empty_tag("sz", [("val", 8)])
|
||||
self._write_color("indexed", 81)
|
||||
self._xml_empty_tag("name", [("val", "Tahoma")])
|
||||
self._xml_empty_tag("family", [("val", 2)])
|
||||
|
||||
self._xml_end_tag("font")
|
||||
|
||||
def _write_underline(self, underline):
|
||||
# Write the underline font element.
|
||||
|
||||
if underline == 2:
|
||||
attributes = [("val", "double")]
|
||||
elif underline == 33:
|
||||
attributes = [("val", "singleAccounting")]
|
||||
elif underline == 34:
|
||||
attributes = [("val", "doubleAccounting")]
|
||||
else:
|
||||
# Default to single underline.
|
||||
attributes = []
|
||||
|
||||
self._xml_empty_tag("u", attributes)
|
||||
|
||||
def _write_vert_align(self, val):
|
||||
# Write the <vertAlign> font sub-element.
|
||||
attributes = [("val", val)]
|
||||
|
||||
self._xml_empty_tag("vertAlign", attributes)
|
||||
|
||||
def _write_color(self, name, value):
|
||||
# Write the <color> element.
|
||||
attributes = [(name, value)]
|
||||
|
||||
self._xml_empty_tag("color", attributes)
|
||||
|
||||
def _write_fills(self):
|
||||
# Write the <fills> element.
|
||||
attributes = [("count", self.fill_count)]
|
||||
|
||||
self._xml_start_tag("fills", attributes)
|
||||
|
||||
# Write the default fill element.
|
||||
self._write_default_fill("none")
|
||||
self._write_default_fill("gray125")
|
||||
|
||||
# Write the fill elements for xf_format objects that have them.
|
||||
for xf_format in self.xf_formats:
|
||||
if xf_format.has_fill:
|
||||
self._write_fill(xf_format)
|
||||
|
||||
self._xml_end_tag("fills")
|
||||
|
||||
def _write_default_fill(self, pattern_type):
|
||||
# Write the <fill> element for the default fills.
|
||||
self._xml_start_tag("fill")
|
||||
self._xml_empty_tag("patternFill", [("patternType", pattern_type)])
|
||||
self._xml_end_tag("fill")
|
||||
|
||||
def _write_fill(self, xf_format, is_dxf_format=False):
|
||||
# Write the <fill> element.
|
||||
pattern = xf_format.pattern
|
||||
bg_color = xf_format.bg_color
|
||||
fg_color = xf_format.fg_color
|
||||
|
||||
# Colors for dxf formats are handled differently from normal formats
|
||||
# since the normal xf_format reverses the meaning of BG and FG for
|
||||
# solid fills.
|
||||
if is_dxf_format:
|
||||
bg_color = xf_format.dxf_bg_color
|
||||
fg_color = xf_format.dxf_fg_color
|
||||
|
||||
patterns = (
|
||||
"none",
|
||||
"solid",
|
||||
"mediumGray",
|
||||
"darkGray",
|
||||
"lightGray",
|
||||
"darkHorizontal",
|
||||
"darkVertical",
|
||||
"darkDown",
|
||||
"darkUp",
|
||||
"darkGrid",
|
||||
"darkTrellis",
|
||||
"lightHorizontal",
|
||||
"lightVertical",
|
||||
"lightDown",
|
||||
"lightUp",
|
||||
"lightGrid",
|
||||
"lightTrellis",
|
||||
"gray125",
|
||||
"gray0625",
|
||||
)
|
||||
|
||||
# Special handling for pattern only case.
|
||||
if not fg_color and not bg_color and patterns[pattern]:
|
||||
self._write_default_fill(patterns[pattern])
|
||||
return
|
||||
|
||||
self._xml_start_tag("fill")
|
||||
|
||||
# The "none" pattern is handled differently for dxf formats.
|
||||
if is_dxf_format and pattern <= 1:
|
||||
self._xml_start_tag("patternFill")
|
||||
else:
|
||||
self._xml_start_tag("patternFill", [("patternType", patterns[pattern])])
|
||||
|
||||
if fg_color:
|
||||
fg_color = self._get_palette_color(fg_color)
|
||||
if fg_color != "Automatic":
|
||||
self._xml_empty_tag("fgColor", [("rgb", fg_color)])
|
||||
|
||||
if bg_color:
|
||||
bg_color = self._get_palette_color(bg_color)
|
||||
if bg_color != "Automatic":
|
||||
self._xml_empty_tag("bgColor", [("rgb", bg_color)])
|
||||
else:
|
||||
if not is_dxf_format and pattern <= 1:
|
||||
self._xml_empty_tag("bgColor", [("indexed", 64)])
|
||||
|
||||
self._xml_end_tag("patternFill")
|
||||
self._xml_end_tag("fill")
|
||||
|
||||
def _write_borders(self):
|
||||
# Write the <borders> element.
|
||||
attributes = [("count", self.border_count)]
|
||||
|
||||
self._xml_start_tag("borders", attributes)
|
||||
|
||||
# Write the border elements for xf_format objects that have them.
|
||||
for xf_format in self.xf_formats:
|
||||
if xf_format.has_border:
|
||||
self._write_border(xf_format)
|
||||
|
||||
self._xml_end_tag("borders")
|
||||
|
||||
def _write_border(self, xf_format, is_dxf_format=False):
|
||||
# Write the <border> element.
|
||||
attributes = []
|
||||
|
||||
# Diagonal borders add attributes to the <border> element.
|
||||
if xf_format.diag_type == 1:
|
||||
attributes.append(("diagonalUp", 1))
|
||||
elif xf_format.diag_type == 2:
|
||||
attributes.append(("diagonalDown", 1))
|
||||
elif xf_format.diag_type == 3:
|
||||
attributes.append(("diagonalUp", 1))
|
||||
attributes.append(("diagonalDown", 1))
|
||||
|
||||
# Ensure that a default diag border is set if the diag type is set.
|
||||
if xf_format.diag_type and not xf_format.diag_border:
|
||||
xf_format.diag_border = 1
|
||||
|
||||
# Write the start border tag.
|
||||
self._xml_start_tag("border", attributes)
|
||||
|
||||
# Write the <border> sub elements.
|
||||
self._write_sub_border("left", xf_format.left, xf_format.left_color)
|
||||
|
||||
self._write_sub_border("right", xf_format.right, xf_format.right_color)
|
||||
|
||||
self._write_sub_border("top", xf_format.top, xf_format.top_color)
|
||||
|
||||
self._write_sub_border("bottom", xf_format.bottom, xf_format.bottom_color)
|
||||
|
||||
# Condition DXF formats don't allow diagonal borders.
|
||||
if not is_dxf_format:
|
||||
self._write_sub_border(
|
||||
"diagonal", xf_format.diag_border, xf_format.diag_color
|
||||
)
|
||||
|
||||
if is_dxf_format:
|
||||
self._write_sub_border("vertical", None, None)
|
||||
self._write_sub_border("horizontal", None, None)
|
||||
|
||||
self._xml_end_tag("border")
|
||||
|
||||
def _write_sub_border(self, border_type, style, color):
|
||||
# Write the <border> sub elements such as <right>, <top>, etc.
|
||||
attributes = []
|
||||
|
||||
if not style:
|
||||
self._xml_empty_tag(border_type)
|
||||
return
|
||||
|
||||
border_styles = (
|
||||
"none",
|
||||
"thin",
|
||||
"medium",
|
||||
"dashed",
|
||||
"dotted",
|
||||
"thick",
|
||||
"double",
|
||||
"hair",
|
||||
"mediumDashed",
|
||||
"dashDot",
|
||||
"mediumDashDot",
|
||||
"dashDotDot",
|
||||
"mediumDashDotDot",
|
||||
"slantDashDot",
|
||||
)
|
||||
|
||||
attributes.append(("style", border_styles[style]))
|
||||
|
||||
self._xml_start_tag(border_type, attributes)
|
||||
|
||||
if color and color != "Automatic":
|
||||
color = self._get_palette_color(color)
|
||||
self._xml_empty_tag("color", [("rgb", color)])
|
||||
else:
|
||||
self._xml_empty_tag("color", [("auto", 1)])
|
||||
|
||||
self._xml_end_tag(border_type)
|
||||
|
||||
def _write_cell_style_xfs(self):
|
||||
# Write the <cellStyleXfs> element.
|
||||
count = 1
|
||||
|
||||
if self.has_hyperlink:
|
||||
count = 2
|
||||
|
||||
attributes = [("count", count)]
|
||||
|
||||
self._xml_start_tag("cellStyleXfs", attributes)
|
||||
self._write_style_xf()
|
||||
|
||||
if self.has_hyperlink:
|
||||
self._write_style_xf(True, self.hyperlink_font_id)
|
||||
|
||||
self._xml_end_tag("cellStyleXfs")
|
||||
|
||||
def _write_cell_xfs(self):
|
||||
# Write the <cellXfs> element.
|
||||
formats = self.xf_formats
|
||||
|
||||
# Workaround for when the last xf_format is used for the comment font
|
||||
# and shouldn't be used for cellXfs.
|
||||
last_format = formats[-1]
|
||||
if last_format.font_only:
|
||||
formats.pop()
|
||||
|
||||
attributes = [("count", len(formats))]
|
||||
self._xml_start_tag("cellXfs", attributes)
|
||||
|
||||
# Write the xf elements.
|
||||
for xf_format in formats:
|
||||
self._write_xf(xf_format)
|
||||
|
||||
self._xml_end_tag("cellXfs")
|
||||
|
||||
def _write_style_xf(self, has_hyperlink=False, font_id=0):
|
||||
# Write the style <xf> element.
|
||||
num_fmt_id = 0
|
||||
fill_id = 0
|
||||
border_id = 0
|
||||
|
||||
attributes = [
|
||||
("numFmtId", num_fmt_id),
|
||||
("fontId", font_id),
|
||||
("fillId", fill_id),
|
||||
("borderId", border_id),
|
||||
]
|
||||
|
||||
if has_hyperlink:
|
||||
attributes.append(("applyNumberFormat", 0))
|
||||
attributes.append(("applyFill", 0))
|
||||
attributes.append(("applyBorder", 0))
|
||||
attributes.append(("applyAlignment", 0))
|
||||
attributes.append(("applyProtection", 0))
|
||||
|
||||
self._xml_start_tag("xf", attributes)
|
||||
self._xml_empty_tag("alignment", [("vertical", "top")])
|
||||
self._xml_empty_tag("protection", [("locked", 0)])
|
||||
self._xml_end_tag("xf")
|
||||
|
||||
else:
|
||||
self._xml_empty_tag("xf", attributes)
|
||||
|
||||
def _write_xf(self, xf_format):
|
||||
# Write the <xf> element.
|
||||
num_fmt_id = xf_format.num_format_index
|
||||
font_id = xf_format.font_index
|
||||
fill_id = xf_format.fill_index
|
||||
border_id = xf_format.border_index
|
||||
xf_id = xf_format.xf_id
|
||||
has_align = 0
|
||||
has_protect = 0
|
||||
|
||||
attributes = [
|
||||
("numFmtId", num_fmt_id),
|
||||
("fontId", font_id),
|
||||
("fillId", fill_id),
|
||||
("borderId", border_id),
|
||||
("xfId", xf_id),
|
||||
]
|
||||
|
||||
if xf_format.quote_prefix:
|
||||
attributes.append(("quotePrefix", 1))
|
||||
|
||||
if xf_format.num_format_index > 0:
|
||||
attributes.append(("applyNumberFormat", 1))
|
||||
|
||||
# Add applyFont attribute if XF format uses a font element.
|
||||
if xf_format.font_index > 0 and not xf_format.hyperlink:
|
||||
attributes.append(("applyFont", 1))
|
||||
|
||||
# Add applyFill attribute if XF format uses a fill element.
|
||||
if xf_format.fill_index > 0:
|
||||
attributes.append(("applyFill", 1))
|
||||
|
||||
# Add applyBorder attribute if XF format uses a border element.
|
||||
if xf_format.border_index > 0:
|
||||
attributes.append(("applyBorder", 1))
|
||||
|
||||
# Check if XF format has alignment properties set.
|
||||
(apply_align, align) = xf_format._get_align_properties()
|
||||
|
||||
# Check if an alignment sub-element should be written.
|
||||
if apply_align and align:
|
||||
has_align = 1
|
||||
|
||||
# We can also have applyAlignment without a sub-element.
|
||||
if apply_align or xf_format.hyperlink:
|
||||
attributes.append(("applyAlignment", 1))
|
||||
|
||||
# Check for cell protection properties.
|
||||
protection = xf_format._get_protection_properties()
|
||||
|
||||
if protection or xf_format.hyperlink:
|
||||
attributes.append(("applyProtection", 1))
|
||||
|
||||
if not xf_format.hyperlink:
|
||||
has_protect = 1
|
||||
|
||||
# Write XF with sub-elements if required.
|
||||
if has_align or has_protect:
|
||||
self._xml_start_tag("xf", attributes)
|
||||
if has_align:
|
||||
self._xml_empty_tag("alignment", align)
|
||||
if has_protect:
|
||||
self._xml_empty_tag("protection", protection)
|
||||
self._xml_end_tag("xf")
|
||||
else:
|
||||
self._xml_empty_tag("xf", attributes)
|
||||
|
||||
def _write_cell_styles(self):
|
||||
# Write the <cellStyles> element.
|
||||
count = 1
|
||||
|
||||
if self.has_hyperlink:
|
||||
count = 2
|
||||
|
||||
attributes = [("count", count)]
|
||||
|
||||
self._xml_start_tag("cellStyles", attributes)
|
||||
|
||||
if self.has_hyperlink:
|
||||
self._write_cell_style("Hyperlink", 1, 8)
|
||||
|
||||
self._write_cell_style()
|
||||
|
||||
self._xml_end_tag("cellStyles")
|
||||
|
||||
def _write_cell_style(self, name="Normal", xf_id=0, builtin_id=0):
|
||||
# Write the <cellStyle> element.
|
||||
attributes = [
|
||||
("name", name),
|
||||
("xfId", xf_id),
|
||||
("builtinId", builtin_id),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("cellStyle", attributes)
|
||||
|
||||
def _write_dxfs(self):
|
||||
# Write the <dxfs> element.
|
||||
formats = self.dxf_formats
|
||||
count = len(formats)
|
||||
|
||||
attributes = [("count", len(formats))]
|
||||
|
||||
if count:
|
||||
self._xml_start_tag("dxfs", attributes)
|
||||
|
||||
# Write the font elements for xf_format objects that have them.
|
||||
for xf_format in self.dxf_formats:
|
||||
self._xml_start_tag("dxf")
|
||||
if xf_format.has_dxf_font:
|
||||
self._write_font(xf_format, True)
|
||||
|
||||
if xf_format.num_format_index:
|
||||
self._write_num_fmt(
|
||||
xf_format.num_format_index, xf_format.num_format
|
||||
)
|
||||
|
||||
if xf_format.has_dxf_fill:
|
||||
self._write_fill(xf_format, True)
|
||||
if xf_format.has_dxf_border:
|
||||
self._write_border(xf_format, True)
|
||||
self._xml_end_tag("dxf")
|
||||
|
||||
self._xml_end_tag("dxfs")
|
||||
else:
|
||||
self._xml_empty_tag("dxfs", attributes)
|
||||
|
||||
def _write_table_styles(self):
|
||||
# Write the <tableStyles> element.
|
||||
count = 0
|
||||
default_table_style = "TableStyleMedium9"
|
||||
default_pivot_style = "PivotStyleLight16"
|
||||
|
||||
attributes = [
|
||||
("count", count),
|
||||
("defaultTableStyle", default_table_style),
|
||||
("defaultPivotStyle", default_pivot_style),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("tableStyles", attributes)
|
||||
|
||||
def _write_colors(self):
|
||||
# Write the <colors> element.
|
||||
custom_colors = self.custom_colors
|
||||
|
||||
if not custom_colors:
|
||||
return
|
||||
|
||||
self._xml_start_tag("colors")
|
||||
self._write_mru_colors(custom_colors)
|
||||
self._xml_end_tag("colors")
|
||||
|
||||
def _write_mru_colors(self, custom_colors):
|
||||
# Write the <mruColors> element for the most recently used colors.
|
||||
|
||||
# Write the custom custom_colors in reverse order.
|
||||
custom_colors.reverse()
|
||||
|
||||
# Limit the mruColors to the last 10.
|
||||
if len(custom_colors) > 10:
|
||||
custom_colors = custom_colors[0:10]
|
||||
|
||||
self._xml_start_tag("mruColors")
|
||||
|
||||
# Write the custom custom_colors in reverse order.
|
||||
for color in custom_colors:
|
||||
self._write_color("rgb", color)
|
||||
|
||||
self._xml_end_tag("mruColors")
|
||||
|
||||
def _write_condense(self):
|
||||
# Write the <condense> element.
|
||||
attributes = [("val", 0)]
|
||||
|
||||
self._xml_empty_tag("condense", attributes)
|
||||
|
||||
def _write_extend(self):
|
||||
# Write the <extend> element.
|
||||
attributes = [("val", 0)]
|
||||
|
||||
self._xml_empty_tag("extend", attributes)
|
||||
184
billinglayer/python/xlsxwriter/table.py
Normal file
184
billinglayer/python/xlsxwriter/table.py
Normal file
@@ -0,0 +1,184 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Table - A class for writing the Excel XLSX Worksheet file.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
from . import xmlwriter
|
||||
|
||||
|
||||
class Table(xmlwriter.XMLwriter):
|
||||
"""
|
||||
A class for writing the Excel XLSX Table file.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
|
||||
super(Table, self).__init__()
|
||||
|
||||
self.properties = {}
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _assemble_xml_file(self):
|
||||
# Assemble and write the XML file.
|
||||
|
||||
# Write the XML declaration.
|
||||
self._xml_declaration()
|
||||
|
||||
# Write the table element.
|
||||
self._write_table()
|
||||
|
||||
# Write the autoFilter element.
|
||||
self._write_auto_filter()
|
||||
|
||||
# Write the tableColumns element.
|
||||
self._write_table_columns()
|
||||
|
||||
# Write the tableStyleInfo element.
|
||||
self._write_table_style_info()
|
||||
|
||||
# Close the table tag.
|
||||
self._xml_end_tag("table")
|
||||
|
||||
# Close the file.
|
||||
self._xml_close()
|
||||
|
||||
def _set_properties(self, properties):
|
||||
# Set the document properties.
|
||||
self.properties = properties
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def _write_table(self):
|
||||
# Write the <table> element.
|
||||
schema = "http://schemas.openxmlformats.org/"
|
||||
xmlns = schema + "spreadsheetml/2006/main"
|
||||
table_id = self.properties["id"]
|
||||
name = self.properties["name"]
|
||||
display_name = self.properties["name"]
|
||||
ref = self.properties["range"]
|
||||
totals_row_shown = self.properties["totals_row_shown"]
|
||||
header_row_count = self.properties["header_row_count"]
|
||||
|
||||
attributes = [
|
||||
("xmlns", xmlns),
|
||||
("id", table_id),
|
||||
("name", name),
|
||||
("displayName", display_name),
|
||||
("ref", ref),
|
||||
]
|
||||
|
||||
if not header_row_count:
|
||||
attributes.append(("headerRowCount", 0))
|
||||
|
||||
if totals_row_shown:
|
||||
attributes.append(("totalsRowCount", 1))
|
||||
else:
|
||||
attributes.append(("totalsRowShown", 0))
|
||||
|
||||
self._xml_start_tag("table", attributes)
|
||||
|
||||
def _write_auto_filter(self):
|
||||
# Write the <autoFilter> element.
|
||||
autofilter = self.properties.get("autofilter", 0)
|
||||
|
||||
if not autofilter:
|
||||
return
|
||||
|
||||
attributes = [
|
||||
(
|
||||
"ref",
|
||||
autofilter,
|
||||
)
|
||||
]
|
||||
|
||||
self._xml_empty_tag("autoFilter", attributes)
|
||||
|
||||
def _write_table_columns(self):
|
||||
# Write the <tableColumns> element.
|
||||
columns = self.properties["columns"]
|
||||
|
||||
count = len(columns)
|
||||
|
||||
attributes = [("count", count)]
|
||||
|
||||
self._xml_start_tag("tableColumns", attributes)
|
||||
|
||||
for col_data in columns:
|
||||
# Write the tableColumn element.
|
||||
self._write_table_column(col_data)
|
||||
|
||||
self._xml_end_tag("tableColumns")
|
||||
|
||||
def _write_table_column(self, col_data):
|
||||
# Write the <tableColumn> element.
|
||||
attributes = [
|
||||
("id", col_data["id"]),
|
||||
("name", col_data["name"]),
|
||||
]
|
||||
|
||||
if col_data.get("total_string"):
|
||||
attributes.append(("totalsRowLabel", col_data["total_string"]))
|
||||
elif col_data.get("total_function"):
|
||||
attributes.append(("totalsRowFunction", col_data["total_function"]))
|
||||
|
||||
if "format" in col_data and col_data["format"] is not None:
|
||||
attributes.append(("dataDxfId", col_data["format"]))
|
||||
|
||||
if col_data.get("formula"):
|
||||
self._xml_start_tag("tableColumn", attributes)
|
||||
|
||||
# Write the calculatedColumnFormula element.
|
||||
self._write_calculated_column_formula(col_data["formula"])
|
||||
|
||||
self._xml_end_tag("tableColumn")
|
||||
else:
|
||||
self._xml_empty_tag("tableColumn", attributes)
|
||||
|
||||
def _write_table_style_info(self):
|
||||
# Write the <tableStyleInfo> element.
|
||||
props = self.properties
|
||||
attributes = []
|
||||
|
||||
name = props["style"]
|
||||
show_first_column = 0 + props["show_first_col"]
|
||||
show_last_column = 0 + props["show_last_col"]
|
||||
show_row_stripes = 0 + props["show_row_stripes"]
|
||||
show_column_stripes = 0 + props["show_col_stripes"]
|
||||
|
||||
if name is not None and name != "" and name != "None":
|
||||
attributes.append(("name", name))
|
||||
|
||||
attributes.append(("showFirstColumn", show_first_column))
|
||||
attributes.append(("showLastColumn", show_last_column))
|
||||
attributes.append(("showRowStripes", show_row_stripes))
|
||||
attributes.append(("showColumnStripes", show_column_stripes))
|
||||
|
||||
self._xml_empty_tag("tableStyleInfo", attributes)
|
||||
|
||||
def _write_calculated_column_formula(self, formula):
|
||||
# Write the <calculatedColumnFormula> element.
|
||||
self._xml_data_element("calculatedColumnFormula", formula)
|
||||
66
billinglayer/python/xlsxwriter/theme.py
Normal file
66
billinglayer/python/xlsxwriter/theme.py
Normal file
File diff suppressed because one or more lines are too long
875
billinglayer/python/xlsxwriter/utility.py
Normal file
875
billinglayer/python/xlsxwriter/utility.py
Normal file
@@ -0,0 +1,875 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Worksheet - A class for writing Excel Worksheets.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
import re
|
||||
import datetime
|
||||
from warnings import warn
|
||||
|
||||
COL_NAMES = {}
|
||||
|
||||
CHAR_WIDTHS = {
|
||||
" ": 3,
|
||||
"!": 5,
|
||||
'"': 6,
|
||||
"#": 7,
|
||||
"$": 7,
|
||||
"%": 11,
|
||||
"&": 10,
|
||||
"'": 3,
|
||||
"(": 5,
|
||||
")": 5,
|
||||
"*": 7,
|
||||
"+": 7,
|
||||
",": 4,
|
||||
"-": 5,
|
||||
".": 4,
|
||||
"/": 6,
|
||||
"0": 7,
|
||||
"1": 7,
|
||||
"2": 7,
|
||||
"3": 7,
|
||||
"4": 7,
|
||||
"5": 7,
|
||||
"6": 7,
|
||||
"7": 7,
|
||||
"8": 7,
|
||||
"9": 7,
|
||||
":": 4,
|
||||
";": 4,
|
||||
"<": 7,
|
||||
"=": 7,
|
||||
">": 7,
|
||||
"?": 7,
|
||||
"@": 13,
|
||||
"A": 9,
|
||||
"B": 8,
|
||||
"C": 8,
|
||||
"D": 9,
|
||||
"E": 7,
|
||||
"F": 7,
|
||||
"G": 9,
|
||||
"H": 9,
|
||||
"I": 4,
|
||||
"J": 5,
|
||||
"K": 8,
|
||||
"L": 6,
|
||||
"M": 12,
|
||||
"N": 10,
|
||||
"O": 10,
|
||||
"P": 8,
|
||||
"Q": 10,
|
||||
"R": 8,
|
||||
"S": 7,
|
||||
"T": 7,
|
||||
"U": 9,
|
||||
"V": 9,
|
||||
"W": 13,
|
||||
"X": 8,
|
||||
"Y": 7,
|
||||
"Z": 7,
|
||||
"[": 5,
|
||||
"\\": 6,
|
||||
"]": 5,
|
||||
"^": 7,
|
||||
"_": 7,
|
||||
"`": 4,
|
||||
"a": 7,
|
||||
"b": 8,
|
||||
"c": 6,
|
||||
"d": 8,
|
||||
"e": 8,
|
||||
"f": 5,
|
||||
"g": 7,
|
||||
"h": 8,
|
||||
"i": 4,
|
||||
"j": 4,
|
||||
"k": 7,
|
||||
"l": 4,
|
||||
"m": 12,
|
||||
"n": 8,
|
||||
"o": 8,
|
||||
"p": 8,
|
||||
"q": 8,
|
||||
"r": 5,
|
||||
"s": 6,
|
||||
"t": 5,
|
||||
"u": 8,
|
||||
"v": 7,
|
||||
"w": 11,
|
||||
"x": 7,
|
||||
"y": 7,
|
||||
"z": 6,
|
||||
"{": 5,
|
||||
"|": 7,
|
||||
"}": 5,
|
||||
"~": 7,
|
||||
}
|
||||
|
||||
# Compile performance critical regular expressions.
|
||||
re_leading = re.compile(r"^\s")
|
||||
re_trailing = re.compile(r"\s$")
|
||||
re_range_parts = re.compile(r"(\$?)([A-Z]{1,3})(\$?)(\d+)")
|
||||
|
||||
|
||||
def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
|
||||
"""
|
||||
Convert a zero indexed row and column cell reference to a A1 style string.
|
||||
|
||||
Args:
|
||||
row: The cell row. Int.
|
||||
col: The cell column. Int.
|
||||
row_abs: Optional flag to make the row absolute. Bool.
|
||||
col_abs: Optional flag to make the column absolute. Bool.
|
||||
|
||||
Returns:
|
||||
A1 style string.
|
||||
|
||||
"""
|
||||
if row < 0:
|
||||
warn("Row number %d must be >= 0" % row)
|
||||
return None
|
||||
|
||||
if col < 0:
|
||||
warn("Col number %d must be >= 0" % col)
|
||||
return None
|
||||
|
||||
row += 1 # Change to 1-index.
|
||||
row_abs = "$" if row_abs else ""
|
||||
|
||||
col_str = xl_col_to_name(col, col_abs)
|
||||
|
||||
return col_str + row_abs + str(row)
|
||||
|
||||
|
||||
def xl_rowcol_to_cell_fast(row, col):
|
||||
"""
|
||||
Optimized version of the xl_rowcol_to_cell function. Only used internally.
|
||||
|
||||
Args:
|
||||
row: The cell row. Int.
|
||||
col: The cell column. Int.
|
||||
|
||||
Returns:
|
||||
A1 style string.
|
||||
|
||||
"""
|
||||
if col in COL_NAMES:
|
||||
col_str = COL_NAMES[col]
|
||||
else:
|
||||
col_str = xl_col_to_name(col)
|
||||
COL_NAMES[col] = col_str
|
||||
|
||||
return col_str + str(row + 1)
|
||||
|
||||
|
||||
def xl_col_to_name(col, col_abs=False):
|
||||
"""
|
||||
Convert a zero indexed column cell reference to a string.
|
||||
|
||||
Args:
|
||||
col: The cell column. Int.
|
||||
col_abs: Optional flag to make the column absolute. Bool.
|
||||
|
||||
Returns:
|
||||
Column style string.
|
||||
|
||||
"""
|
||||
col_num = col
|
||||
if col_num < 0:
|
||||
warn("Col number %d must be >= 0" % col_num)
|
||||
return None
|
||||
|
||||
col_num += 1 # Change to 1-index.
|
||||
col_str = ""
|
||||
col_abs = "$" if col_abs else ""
|
||||
|
||||
while col_num:
|
||||
# Set remainder from 1 .. 26
|
||||
remainder = col_num % 26
|
||||
|
||||
if remainder == 0:
|
||||
remainder = 26
|
||||
|
||||
# Convert the remainder to a character.
|
||||
col_letter = chr(ord("A") + remainder - 1)
|
||||
|
||||
# Accumulate the column letters, right to left.
|
||||
col_str = col_letter + col_str
|
||||
|
||||
# Get the next order of magnitude.
|
||||
col_num = int((col_num - 1) / 26)
|
||||
|
||||
return col_abs + col_str
|
||||
|
||||
|
||||
def xl_cell_to_rowcol(cell_str):
|
||||
"""
|
||||
Convert a cell reference in A1 notation to a zero indexed row and column.
|
||||
|
||||
Args:
|
||||
cell_str: A1 style string.
|
||||
|
||||
Returns:
|
||||
row, col: Zero indexed cell row and column indices.
|
||||
|
||||
"""
|
||||
if not cell_str:
|
||||
return 0, 0
|
||||
|
||||
match = re_range_parts.match(cell_str)
|
||||
col_str = match.group(2)
|
||||
row_str = match.group(4)
|
||||
|
||||
# Convert base26 column string to number.
|
||||
expn = 0
|
||||
col = 0
|
||||
for char in reversed(col_str):
|
||||
col += (ord(char) - ord("A") + 1) * (26**expn)
|
||||
expn += 1
|
||||
|
||||
# Convert 1-index to zero-index
|
||||
row = int(row_str) - 1
|
||||
col -= 1
|
||||
|
||||
return row, col
|
||||
|
||||
|
||||
def xl_cell_to_rowcol_abs(cell_str):
|
||||
"""
|
||||
Convert an absolute cell reference in A1 notation to a zero indexed
|
||||
row and column, with True/False values for absolute rows or columns.
|
||||
|
||||
Args:
|
||||
cell_str: A1 style string.
|
||||
|
||||
Returns:
|
||||
row, col, row_abs, col_abs: Zero indexed cell row and column indices.
|
||||
|
||||
"""
|
||||
if not cell_str:
|
||||
return 0, 0, False, False
|
||||
|
||||
match = re_range_parts.match(cell_str)
|
||||
|
||||
col_abs = match.group(1)
|
||||
col_str = match.group(2)
|
||||
row_abs = match.group(3)
|
||||
row_str = match.group(4)
|
||||
|
||||
if col_abs:
|
||||
col_abs = True
|
||||
else:
|
||||
col_abs = False
|
||||
|
||||
if row_abs:
|
||||
row_abs = True
|
||||
else:
|
||||
row_abs = False
|
||||
|
||||
# Convert base26 column string to number.
|
||||
expn = 0
|
||||
col = 0
|
||||
for char in reversed(col_str):
|
||||
col += (ord(char) - ord("A") + 1) * (26**expn)
|
||||
expn += 1
|
||||
|
||||
# Convert 1-index to zero-index
|
||||
row = int(row_str) - 1
|
||||
col -= 1
|
||||
|
||||
return row, col, row_abs, col_abs
|
||||
|
||||
|
||||
def xl_range(first_row, first_col, last_row, last_col):
|
||||
"""
|
||||
Convert zero indexed row and col cell references to a A1:B1 range string.
|
||||
|
||||
Args:
|
||||
first_row: The first cell row. Int.
|
||||
first_col: The first cell column. Int.
|
||||
last_row: The last cell row. Int.
|
||||
last_col: The last cell column. Int.
|
||||
|
||||
Returns:
|
||||
A1:B1 style range string.
|
||||
|
||||
"""
|
||||
range1 = xl_rowcol_to_cell(first_row, first_col)
|
||||
range2 = xl_rowcol_to_cell(last_row, last_col)
|
||||
|
||||
if range1 is None or range2 is None:
|
||||
warn("Row and column numbers must be >= 0")
|
||||
return None
|
||||
|
||||
if range1 == range2:
|
||||
return range1
|
||||
else:
|
||||
return range1 + ":" + range2
|
||||
|
||||
|
||||
def xl_range_abs(first_row, first_col, last_row, last_col):
|
||||
"""
|
||||
Convert zero indexed row and col cell references to a $A$1:$B$1 absolute
|
||||
range string.
|
||||
|
||||
Args:
|
||||
first_row: The first cell row. Int.
|
||||
first_col: The first cell column. Int.
|
||||
last_row: The last cell row. Int.
|
||||
last_col: The last cell column. Int.
|
||||
|
||||
Returns:
|
||||
$A$1:$B$1 style range string.
|
||||
|
||||
"""
|
||||
range1 = xl_rowcol_to_cell(first_row, first_col, True, True)
|
||||
range2 = xl_rowcol_to_cell(last_row, last_col, True, True)
|
||||
|
||||
if range1 is None or range2 is None:
|
||||
warn("Row and column numbers must be >= 0")
|
||||
return None
|
||||
|
||||
if range1 == range2:
|
||||
return range1
|
||||
else:
|
||||
return range1 + ":" + range2
|
||||
|
||||
|
||||
def xl_range_formula(sheetname, first_row, first_col, last_row, last_col):
|
||||
"""
|
||||
Convert worksheet name and zero indexed row and col cell references to
|
||||
a Sheet1!A1:B1 range formula string.
|
||||
|
||||
Args:
|
||||
sheetname: The worksheet name. String.
|
||||
first_row: The first cell row. Int.
|
||||
first_col: The first cell column. Int.
|
||||
last_row: The last cell row. Int.
|
||||
last_col: The last cell column. Int.
|
||||
|
||||
Returns:
|
||||
A1:B1 style range string.
|
||||
|
||||
"""
|
||||
cell_range = xl_range_abs(first_row, first_col, last_row, last_col)
|
||||
sheetname = quote_sheetname(sheetname)
|
||||
|
||||
return sheetname + "!" + cell_range
|
||||
|
||||
|
||||
def quote_sheetname(sheetname):
|
||||
"""
|
||||
Convert a worksheet name to a quoted name if it contains spaces or
|
||||
special characters.
|
||||
|
||||
Args:
|
||||
sheetname: The worksheet name. String.
|
||||
|
||||
Returns:
|
||||
A quoted worksheet string.
|
||||
|
||||
"""
|
||||
|
||||
if not sheetname.isalnum() and not sheetname.startswith("'"):
|
||||
# Double quote any single quotes.
|
||||
sheetname = sheetname.replace("'", "''")
|
||||
|
||||
# Single quote the sheet name.
|
||||
sheetname = "'%s'" % sheetname
|
||||
|
||||
return sheetname
|
||||
|
||||
|
||||
def xl_pixel_width(string):
|
||||
"""
|
||||
Get the pixel width of a string based on individual character widths taken
|
||||
from Excel. UTF8 characters, and other unhandled characters, are given a
|
||||
default width of 8.
|
||||
|
||||
Args:
|
||||
string: The string to calculate the width for. String.
|
||||
|
||||
Returns:
|
||||
The string width in pixels. Note, Excel adds an additional 7 pixels of
|
||||
padding in the cell.
|
||||
|
||||
"""
|
||||
length = 0
|
||||
for char in string:
|
||||
length += CHAR_WIDTHS.get(char, 8)
|
||||
|
||||
return length
|
||||
|
||||
|
||||
def xl_color(color):
|
||||
# Used in conjunction with the XlsxWriter *color() methods to convert
|
||||
# a color name into an RGB formatted string. These colors are for
|
||||
# backward compatibility with older versions of Excel.
|
||||
named_colors = {
|
||||
"black": "#000000",
|
||||
"blue": "#0000FF",
|
||||
"brown": "#800000",
|
||||
"cyan": "#00FFFF",
|
||||
"gray": "#808080",
|
||||
"green": "#008000",
|
||||
"lime": "#00FF00",
|
||||
"magenta": "#FF00FF",
|
||||
"navy": "#000080",
|
||||
"orange": "#FF6600",
|
||||
"pink": "#FF00FF",
|
||||
"purple": "#800080",
|
||||
"red": "#FF0000",
|
||||
"silver": "#C0C0C0",
|
||||
"white": "#FFFFFF",
|
||||
"yellow": "#FFFF00",
|
||||
}
|
||||
|
||||
if color in named_colors:
|
||||
color = named_colors[color]
|
||||
|
||||
if not re.match("#[0-9a-fA-F]{6}", color):
|
||||
warn("Color '%s' isn't a valid Excel color" % color)
|
||||
|
||||
# Convert the RGB color to the Excel ARGB format.
|
||||
return "FF" + color.lstrip("#").upper()
|
||||
|
||||
|
||||
def get_rgb_color(color):
|
||||
# Convert the user specified color to an RGB color.
|
||||
rgb_color = xl_color(color)
|
||||
|
||||
# Remove leading FF from RGB color for charts.
|
||||
rgb_color = re.sub(r"^FF", "", rgb_color)
|
||||
|
||||
return rgb_color
|
||||
|
||||
|
||||
def get_sparkline_style(style_id):
|
||||
styles = [
|
||||
{
|
||||
"series": {"theme": "4", "tint": "-0.499984740745262"},
|
||||
"negative": {"theme": "5"},
|
||||
"markers": {"theme": "4", "tint": "-0.499984740745262"},
|
||||
"first": {"theme": "4", "tint": "0.39997558519241921"},
|
||||
"last": {"theme": "4", "tint": "0.39997558519241921"},
|
||||
"high": {"theme": "4"},
|
||||
"low": {"theme": "4"},
|
||||
}, # 0
|
||||
{
|
||||
"series": {"theme": "4", "tint": "-0.499984740745262"},
|
||||
"negative": {"theme": "5"},
|
||||
"markers": {"theme": "4", "tint": "-0.499984740745262"},
|
||||
"first": {"theme": "4", "tint": "0.39997558519241921"},
|
||||
"last": {"theme": "4", "tint": "0.39997558519241921"},
|
||||
"high": {"theme": "4"},
|
||||
"low": {"theme": "4"},
|
||||
}, # 1
|
||||
{
|
||||
"series": {"theme": "5", "tint": "-0.499984740745262"},
|
||||
"negative": {"theme": "6"},
|
||||
"markers": {"theme": "5", "tint": "-0.499984740745262"},
|
||||
"first": {"theme": "5", "tint": "0.39997558519241921"},
|
||||
"last": {"theme": "5", "tint": "0.39997558519241921"},
|
||||
"high": {"theme": "5"},
|
||||
"low": {"theme": "5"},
|
||||
}, # 2
|
||||
{
|
||||
"series": {"theme": "6", "tint": "-0.499984740745262"},
|
||||
"negative": {"theme": "7"},
|
||||
"markers": {"theme": "6", "tint": "-0.499984740745262"},
|
||||
"first": {"theme": "6", "tint": "0.39997558519241921"},
|
||||
"last": {"theme": "6", "tint": "0.39997558519241921"},
|
||||
"high": {"theme": "6"},
|
||||
"low": {"theme": "6"},
|
||||
}, # 3
|
||||
{
|
||||
"series": {"theme": "7", "tint": "-0.499984740745262"},
|
||||
"negative": {"theme": "8"},
|
||||
"markers": {"theme": "7", "tint": "-0.499984740745262"},
|
||||
"first": {"theme": "7", "tint": "0.39997558519241921"},
|
||||
"last": {"theme": "7", "tint": "0.39997558519241921"},
|
||||
"high": {"theme": "7"},
|
||||
"low": {"theme": "7"},
|
||||
}, # 4
|
||||
{
|
||||
"series": {"theme": "8", "tint": "-0.499984740745262"},
|
||||
"negative": {"theme": "9"},
|
||||
"markers": {"theme": "8", "tint": "-0.499984740745262"},
|
||||
"first": {"theme": "8", "tint": "0.39997558519241921"},
|
||||
"last": {"theme": "8", "tint": "0.39997558519241921"},
|
||||
"high": {"theme": "8"},
|
||||
"low": {"theme": "8"},
|
||||
}, # 5
|
||||
{
|
||||
"series": {"theme": "9", "tint": "-0.499984740745262"},
|
||||
"negative": {"theme": "4"},
|
||||
"markers": {"theme": "9", "tint": "-0.499984740745262"},
|
||||
"first": {"theme": "9", "tint": "0.39997558519241921"},
|
||||
"last": {"theme": "9", "tint": "0.39997558519241921"},
|
||||
"high": {"theme": "9"},
|
||||
"low": {"theme": "9"},
|
||||
}, # 6
|
||||
{
|
||||
"series": {"theme": "4", "tint": "-0.249977111117893"},
|
||||
"negative": {"theme": "5"},
|
||||
"markers": {"theme": "5", "tint": "-0.249977111117893"},
|
||||
"first": {"theme": "5", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "5", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "5", "tint": "-0.249977111117893"},
|
||||
"low": {"theme": "5", "tint": "-0.249977111117893"},
|
||||
}, # 7
|
||||
{
|
||||
"series": {"theme": "5", "tint": "-0.249977111117893"},
|
||||
"negative": {"theme": "6"},
|
||||
"markers": {"theme": "6", "tint": "-0.249977111117893"},
|
||||
"first": {"theme": "6", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "6", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "6", "tint": "-0.249977111117893"},
|
||||
"low": {"theme": "6", "tint": "-0.249977111117893"},
|
||||
}, # 8
|
||||
{
|
||||
"series": {"theme": "6", "tint": "-0.249977111117893"},
|
||||
"negative": {"theme": "7"},
|
||||
"markers": {"theme": "7", "tint": "-0.249977111117893"},
|
||||
"first": {"theme": "7", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "7", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "7", "tint": "-0.249977111117893"},
|
||||
"low": {"theme": "7", "tint": "-0.249977111117893"},
|
||||
}, # 9
|
||||
{
|
||||
"series": {"theme": "7", "tint": "-0.249977111117893"},
|
||||
"negative": {"theme": "8"},
|
||||
"markers": {"theme": "8", "tint": "-0.249977111117893"},
|
||||
"first": {"theme": "8", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "8", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "8", "tint": "-0.249977111117893"},
|
||||
"low": {"theme": "8", "tint": "-0.249977111117893"},
|
||||
}, # 10
|
||||
{
|
||||
"series": {"theme": "8", "tint": "-0.249977111117893"},
|
||||
"negative": {"theme": "9"},
|
||||
"markers": {"theme": "9", "tint": "-0.249977111117893"},
|
||||
"first": {"theme": "9", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "9", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "9", "tint": "-0.249977111117893"},
|
||||
"low": {"theme": "9", "tint": "-0.249977111117893"},
|
||||
}, # 11
|
||||
{
|
||||
"series": {"theme": "9", "tint": "-0.249977111117893"},
|
||||
"negative": {"theme": "4"},
|
||||
"markers": {"theme": "4", "tint": "-0.249977111117893"},
|
||||
"first": {"theme": "4", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "4", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "4", "tint": "-0.249977111117893"},
|
||||
"low": {"theme": "4", "tint": "-0.249977111117893"},
|
||||
}, # 12
|
||||
{
|
||||
"series": {"theme": "4"},
|
||||
"negative": {"theme": "5"},
|
||||
"markers": {"theme": "4", "tint": "-0.249977111117893"},
|
||||
"first": {"theme": "4", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "4", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "4", "tint": "-0.249977111117893"},
|
||||
"low": {"theme": "4", "tint": "-0.249977111117893"},
|
||||
}, # 13
|
||||
{
|
||||
"series": {"theme": "5"},
|
||||
"negative": {"theme": "6"},
|
||||
"markers": {"theme": "5", "tint": "-0.249977111117893"},
|
||||
"first": {"theme": "5", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "5", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "5", "tint": "-0.249977111117893"},
|
||||
"low": {"theme": "5", "tint": "-0.249977111117893"},
|
||||
}, # 14
|
||||
{
|
||||
"series": {"theme": "6"},
|
||||
"negative": {"theme": "7"},
|
||||
"markers": {"theme": "6", "tint": "-0.249977111117893"},
|
||||
"first": {"theme": "6", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "6", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "6", "tint": "-0.249977111117893"},
|
||||
"low": {"theme": "6", "tint": "-0.249977111117893"},
|
||||
}, # 15
|
||||
{
|
||||
"series": {"theme": "7"},
|
||||
"negative": {"theme": "8"},
|
||||
"markers": {"theme": "7", "tint": "-0.249977111117893"},
|
||||
"first": {"theme": "7", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "7", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "7", "tint": "-0.249977111117893"},
|
||||
"low": {"theme": "7", "tint": "-0.249977111117893"},
|
||||
}, # 16
|
||||
{
|
||||
"series": {"theme": "8"},
|
||||
"negative": {"theme": "9"},
|
||||
"markers": {"theme": "8", "tint": "-0.249977111117893"},
|
||||
"first": {"theme": "8", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "8", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "8", "tint": "-0.249977111117893"},
|
||||
"low": {"theme": "8", "tint": "-0.249977111117893"},
|
||||
}, # 17
|
||||
{
|
||||
"series": {"theme": "9"},
|
||||
"negative": {"theme": "4"},
|
||||
"markers": {"theme": "9", "tint": "-0.249977111117893"},
|
||||
"first": {"theme": "9", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "9", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "9", "tint": "-0.249977111117893"},
|
||||
"low": {"theme": "9", "tint": "-0.249977111117893"},
|
||||
}, # 18
|
||||
{
|
||||
"series": {"theme": "4", "tint": "0.39997558519241921"},
|
||||
"negative": {"theme": "0", "tint": "-0.499984740745262"},
|
||||
"markers": {"theme": "4", "tint": "0.79998168889431442"},
|
||||
"first": {"theme": "4", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "4", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "4", "tint": "-0.499984740745262"},
|
||||
"low": {"theme": "4", "tint": "-0.499984740745262"},
|
||||
}, # 19
|
||||
{
|
||||
"series": {"theme": "5", "tint": "0.39997558519241921"},
|
||||
"negative": {"theme": "0", "tint": "-0.499984740745262"},
|
||||
"markers": {"theme": "5", "tint": "0.79998168889431442"},
|
||||
"first": {"theme": "5", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "5", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "5", "tint": "-0.499984740745262"},
|
||||
"low": {"theme": "5", "tint": "-0.499984740745262"},
|
||||
}, # 20
|
||||
{
|
||||
"series": {"theme": "6", "tint": "0.39997558519241921"},
|
||||
"negative": {"theme": "0", "tint": "-0.499984740745262"},
|
||||
"markers": {"theme": "6", "tint": "0.79998168889431442"},
|
||||
"first": {"theme": "6", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "6", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "6", "tint": "-0.499984740745262"},
|
||||
"low": {"theme": "6", "tint": "-0.499984740745262"},
|
||||
}, # 21
|
||||
{
|
||||
"series": {"theme": "7", "tint": "0.39997558519241921"},
|
||||
"negative": {"theme": "0", "tint": "-0.499984740745262"},
|
||||
"markers": {"theme": "7", "tint": "0.79998168889431442"},
|
||||
"first": {"theme": "7", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "7", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "7", "tint": "-0.499984740745262"},
|
||||
"low": {"theme": "7", "tint": "-0.499984740745262"},
|
||||
}, # 22
|
||||
{
|
||||
"series": {"theme": "8", "tint": "0.39997558519241921"},
|
||||
"negative": {"theme": "0", "tint": "-0.499984740745262"},
|
||||
"markers": {"theme": "8", "tint": "0.79998168889431442"},
|
||||
"first": {"theme": "8", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "8", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "8", "tint": "-0.499984740745262"},
|
||||
"low": {"theme": "8", "tint": "-0.499984740745262"},
|
||||
}, # 23
|
||||
{
|
||||
"series": {"theme": "9", "tint": "0.39997558519241921"},
|
||||
"negative": {"theme": "0", "tint": "-0.499984740745262"},
|
||||
"markers": {"theme": "9", "tint": "0.79998168889431442"},
|
||||
"first": {"theme": "9", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "9", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "9", "tint": "-0.499984740745262"},
|
||||
"low": {"theme": "9", "tint": "-0.499984740745262"},
|
||||
}, # 24
|
||||
{
|
||||
"series": {"theme": "1", "tint": "0.499984740745262"},
|
||||
"negative": {"theme": "1", "tint": "0.249977111117893"},
|
||||
"markers": {"theme": "1", "tint": "0.249977111117893"},
|
||||
"first": {"theme": "1", "tint": "0.249977111117893"},
|
||||
"last": {"theme": "1", "tint": "0.249977111117893"},
|
||||
"high": {"theme": "1", "tint": "0.249977111117893"},
|
||||
"low": {"theme": "1", "tint": "0.249977111117893"},
|
||||
}, # 25
|
||||
{
|
||||
"series": {"theme": "1", "tint": "0.34998626667073579"},
|
||||
"negative": {"theme": "0", "tint": "-0.249977111117893"},
|
||||
"markers": {"theme": "0", "tint": "-0.249977111117893"},
|
||||
"first": {"theme": "0", "tint": "-0.249977111117893"},
|
||||
"last": {"theme": "0", "tint": "-0.249977111117893"},
|
||||
"high": {"theme": "0", "tint": "-0.249977111117893"},
|
||||
"low": {"theme": "0", "tint": "-0.249977111117893"},
|
||||
}, # 26
|
||||
{
|
||||
"series": {"rgb": "FF323232"},
|
||||
"negative": {"rgb": "FFD00000"},
|
||||
"markers": {"rgb": "FFD00000"},
|
||||
"first": {"rgb": "FFD00000"},
|
||||
"last": {"rgb": "FFD00000"},
|
||||
"high": {"rgb": "FFD00000"},
|
||||
"low": {"rgb": "FFD00000"},
|
||||
}, # 27
|
||||
{
|
||||
"series": {"rgb": "FF000000"},
|
||||
"negative": {"rgb": "FF0070C0"},
|
||||
"markers": {"rgb": "FF0070C0"},
|
||||
"first": {"rgb": "FF0070C0"},
|
||||
"last": {"rgb": "FF0070C0"},
|
||||
"high": {"rgb": "FF0070C0"},
|
||||
"low": {"rgb": "FF0070C0"},
|
||||
}, # 28
|
||||
{
|
||||
"series": {"rgb": "FF376092"},
|
||||
"negative": {"rgb": "FFD00000"},
|
||||
"markers": {"rgb": "FFD00000"},
|
||||
"first": {"rgb": "FFD00000"},
|
||||
"last": {"rgb": "FFD00000"},
|
||||
"high": {"rgb": "FFD00000"},
|
||||
"low": {"rgb": "FFD00000"},
|
||||
}, # 29
|
||||
{
|
||||
"series": {"rgb": "FF0070C0"},
|
||||
"negative": {"rgb": "FF000000"},
|
||||
"markers": {"rgb": "FF000000"},
|
||||
"first": {"rgb": "FF000000"},
|
||||
"last": {"rgb": "FF000000"},
|
||||
"high": {"rgb": "FF000000"},
|
||||
"low": {"rgb": "FF000000"},
|
||||
}, # 30
|
||||
{
|
||||
"series": {"rgb": "FF5F5F5F"},
|
||||
"negative": {"rgb": "FFFFB620"},
|
||||
"markers": {"rgb": "FFD70077"},
|
||||
"first": {"rgb": "FF5687C2"},
|
||||
"last": {"rgb": "FF359CEB"},
|
||||
"high": {"rgb": "FF56BE79"},
|
||||
"low": {"rgb": "FFFF5055"},
|
||||
}, # 31
|
||||
{
|
||||
"series": {"rgb": "FF5687C2"},
|
||||
"negative": {"rgb": "FFFFB620"},
|
||||
"markers": {"rgb": "FFD70077"},
|
||||
"first": {"rgb": "FF777777"},
|
||||
"last": {"rgb": "FF359CEB"},
|
||||
"high": {"rgb": "FF56BE79"},
|
||||
"low": {"rgb": "FFFF5055"},
|
||||
}, # 32
|
||||
{
|
||||
"series": {"rgb": "FFC6EFCE"},
|
||||
"negative": {"rgb": "FFFFC7CE"},
|
||||
"markers": {"rgb": "FF8CADD6"},
|
||||
"first": {"rgb": "FFFFDC47"},
|
||||
"last": {"rgb": "FFFFEB9C"},
|
||||
"high": {"rgb": "FF60D276"},
|
||||
"low": {"rgb": "FFFF5367"},
|
||||
}, # 33
|
||||
{
|
||||
"series": {"rgb": "FF00B050"},
|
||||
"negative": {"rgb": "FFFF0000"},
|
||||
"markers": {"rgb": "FF0070C0"},
|
||||
"first": {"rgb": "FFFFC000"},
|
||||
"last": {"rgb": "FFFFC000"},
|
||||
"high": {"rgb": "FF00B050"},
|
||||
"low": {"rgb": "FFFF0000"},
|
||||
}, # 34
|
||||
{
|
||||
"series": {"theme": "3"},
|
||||
"negative": {"theme": "9"},
|
||||
"markers": {"theme": "8"},
|
||||
"first": {"theme": "4"},
|
||||
"last": {"theme": "5"},
|
||||
"high": {"theme": "6"},
|
||||
"low": {"theme": "7"},
|
||||
}, # 35
|
||||
{
|
||||
"series": {"theme": "1"},
|
||||
"negative": {"theme": "9"},
|
||||
"markers": {"theme": "8"},
|
||||
"first": {"theme": "4"},
|
||||
"last": {"theme": "5"},
|
||||
"high": {"theme": "6"},
|
||||
"low": {"theme": "7"},
|
||||
}, # 36
|
||||
]
|
||||
|
||||
return styles[style_id]
|
||||
|
||||
|
||||
def supported_datetime(dt):
|
||||
# Determine is an argument is a supported datetime object.
|
||||
return isinstance(
|
||||
dt, (datetime.datetime, datetime.date, datetime.time, datetime.timedelta)
|
||||
)
|
||||
|
||||
|
||||
def remove_datetime_timezone(dt_obj, remove_timezone):
|
||||
# Excel doesn't support timezones in datetimes/times so we remove the
|
||||
# tzinfo from the object if the user has specified that option in the
|
||||
# constructor.
|
||||
if remove_timezone:
|
||||
dt_obj = dt_obj.replace(tzinfo=None)
|
||||
else:
|
||||
if dt_obj.tzinfo:
|
||||
raise TypeError(
|
||||
"Excel doesn't support timezones in datetimes. "
|
||||
"Set the tzinfo in the datetime/time object to None or "
|
||||
"use the 'remove_timezone' Workbook() option"
|
||||
)
|
||||
|
||||
return dt_obj
|
||||
|
||||
|
||||
def datetime_to_excel_datetime(dt_obj, date_1904, remove_timezone):
|
||||
# Convert a datetime object to an Excel serial date and time. The integer
|
||||
# part of the number stores the number of days since the epoch and the
|
||||
# fractional part stores the percentage of the day.
|
||||
date_type = dt_obj
|
||||
is_timedelta = False
|
||||
|
||||
if date_1904:
|
||||
# Excel for Mac date epoch.
|
||||
epoch = datetime.datetime(1904, 1, 1)
|
||||
else:
|
||||
# Default Excel epoch.
|
||||
epoch = datetime.datetime(1899, 12, 31)
|
||||
|
||||
# We handle datetime .datetime, .date and .time objects but convert
|
||||
# them to datetime.datetime objects and process them in the same way.
|
||||
if isinstance(dt_obj, datetime.datetime):
|
||||
dt_obj = remove_datetime_timezone(dt_obj, remove_timezone)
|
||||
delta = dt_obj - epoch
|
||||
elif isinstance(dt_obj, datetime.date):
|
||||
dt_obj = datetime.datetime.fromordinal(dt_obj.toordinal())
|
||||
delta = dt_obj - epoch
|
||||
elif isinstance(dt_obj, datetime.time):
|
||||
dt_obj = datetime.datetime.combine(epoch, dt_obj)
|
||||
dt_obj = remove_datetime_timezone(dt_obj, remove_timezone)
|
||||
delta = dt_obj - epoch
|
||||
elif isinstance(dt_obj, datetime.timedelta):
|
||||
is_timedelta = True
|
||||
delta = dt_obj
|
||||
else:
|
||||
raise TypeError("Unknown or unsupported datetime type")
|
||||
|
||||
# Convert a Python datetime.datetime value to an Excel date number.
|
||||
excel_time = delta.days + (
|
||||
float(delta.seconds) + float(delta.microseconds) / 1e6
|
||||
) / (60 * 60 * 24)
|
||||
|
||||
# The following is a workaround for the fact that in Excel a time only
|
||||
# value is represented as 1899-12-31+time whereas in datetime.datetime()
|
||||
# it is 1900-1-1+time so we need to subtract the 1 day difference.
|
||||
if isinstance(date_type, datetime.datetime) and dt_obj.isocalendar() == (
|
||||
1900,
|
||||
1,
|
||||
1,
|
||||
):
|
||||
excel_time -= 1
|
||||
|
||||
# Account for Excel erroneously treating 1900 as a leap year.
|
||||
if not date_1904 and not is_timedelta and excel_time > 59:
|
||||
excel_time += 1
|
||||
|
||||
return excel_time
|
||||
|
||||
|
||||
def preserve_whitespace(string):
|
||||
# Check if a string has leading or trailing whitespace that requires a
|
||||
# "preserve" attribute.
|
||||
if re_leading.search(string) or re_trailing.search(string):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
720
billinglayer/python/xlsxwriter/vml.py
Normal file
720
billinglayer/python/xlsxwriter/vml.py
Normal file
@@ -0,0 +1,720 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Vml - A class for writing the Excel XLSX Vml file.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
# Package imports.
|
||||
from . import xmlwriter
|
||||
|
||||
|
||||
class Vml(xmlwriter.XMLwriter):
|
||||
"""
|
||||
A class for writing the Excel XLSX Vml file.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Public API.
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
"""
|
||||
|
||||
super(Vml, self).__init__()
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Private API.
|
||||
#
|
||||
###########################################################################
|
||||
def _assemble_xml_file(
|
||||
self,
|
||||
data_id,
|
||||
vml_shape_id,
|
||||
comments_data=None,
|
||||
buttons_data=None,
|
||||
header_images_data=None,
|
||||
):
|
||||
# Assemble and write the XML file.
|
||||
z_index = 1
|
||||
|
||||
self._write_xml_namespace()
|
||||
|
||||
# Write the o:shapelayout element.
|
||||
self._write_shapelayout(data_id)
|
||||
|
||||
if buttons_data:
|
||||
# Write the v:shapetype element.
|
||||
self._write_button_shapetype()
|
||||
|
||||
for button in buttons_data:
|
||||
# Write the v:shape element.
|
||||
vml_shape_id += 1
|
||||
self._write_button_shape(vml_shape_id, z_index, button)
|
||||
z_index += 1
|
||||
|
||||
if comments_data:
|
||||
# Write the v:shapetype element.
|
||||
self._write_comment_shapetype()
|
||||
|
||||
for comment in comments_data:
|
||||
# Write the v:shape element.
|
||||
vml_shape_id += 1
|
||||
self._write_comment_shape(vml_shape_id, z_index, comment)
|
||||
z_index += 1
|
||||
|
||||
if header_images_data:
|
||||
# Write the v:shapetype element.
|
||||
self._write_image_shapetype()
|
||||
|
||||
index = 1
|
||||
for image in header_images_data:
|
||||
# Write the v:shape element.
|
||||
vml_shape_id += 1
|
||||
self._write_image_shape(vml_shape_id, index, image)
|
||||
index += 1
|
||||
|
||||
self._xml_end_tag("xml")
|
||||
|
||||
# Close the XML writer filehandle.
|
||||
self._xml_close()
|
||||
|
||||
def _pixels_to_points(self, vertices):
|
||||
# Convert comment vertices from pixels to points.
|
||||
|
||||
left, top, width, height = vertices[8:12]
|
||||
|
||||
# Scale to pixels.
|
||||
left *= 0.75
|
||||
top *= 0.75
|
||||
width *= 0.75
|
||||
height *= 0.75
|
||||
|
||||
return left, top, width, height
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# XML methods.
|
||||
#
|
||||
###########################################################################
|
||||
def _write_xml_namespace(self):
|
||||
# Write the <xml> element. This is the root element of VML.
|
||||
schema = "urn:schemas-microsoft-com:"
|
||||
xmlns = schema + "vml"
|
||||
xmlns_o = schema + "office:office"
|
||||
xmlns_x = schema + "office:excel"
|
||||
|
||||
attributes = [
|
||||
("xmlns:v", xmlns),
|
||||
("xmlns:o", xmlns_o),
|
||||
("xmlns:x", xmlns_x),
|
||||
]
|
||||
|
||||
self._xml_start_tag("xml", attributes)
|
||||
|
||||
def _write_shapelayout(self, data_id):
|
||||
# Write the <o:shapelayout> element.
|
||||
attributes = [("v:ext", "edit")]
|
||||
|
||||
self._xml_start_tag("o:shapelayout", attributes)
|
||||
|
||||
# Write the o:idmap element.
|
||||
self._write_idmap(data_id)
|
||||
|
||||
self._xml_end_tag("o:shapelayout")
|
||||
|
||||
def _write_idmap(self, data_id):
|
||||
# Write the <o:idmap> element.
|
||||
attributes = [
|
||||
("v:ext", "edit"),
|
||||
("data", data_id),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("o:idmap", attributes)
|
||||
|
||||
def _write_comment_shapetype(self):
|
||||
# Write the <v:shapetype> element.
|
||||
shape_id = "_x0000_t202"
|
||||
coordsize = "21600,21600"
|
||||
spt = 202
|
||||
path = "m,l,21600r21600,l21600,xe"
|
||||
|
||||
attributes = [
|
||||
("id", shape_id),
|
||||
("coordsize", coordsize),
|
||||
("o:spt", spt),
|
||||
("path", path),
|
||||
]
|
||||
|
||||
self._xml_start_tag("v:shapetype", attributes)
|
||||
|
||||
# Write the v:stroke element.
|
||||
self._write_stroke()
|
||||
|
||||
# Write the v:path element.
|
||||
self._write_comment_path("t", "rect")
|
||||
|
||||
self._xml_end_tag("v:shapetype")
|
||||
|
||||
def _write_button_shapetype(self):
|
||||
# Write the <v:shapetype> element.
|
||||
shape_id = "_x0000_t201"
|
||||
coordsize = "21600,21600"
|
||||
spt = 201
|
||||
path = "m,l,21600r21600,l21600,xe"
|
||||
|
||||
attributes = [
|
||||
("id", shape_id),
|
||||
("coordsize", coordsize),
|
||||
("o:spt", spt),
|
||||
("path", path),
|
||||
]
|
||||
|
||||
self._xml_start_tag("v:shapetype", attributes)
|
||||
|
||||
# Write the v:stroke element.
|
||||
self._write_stroke()
|
||||
|
||||
# Write the v:path element.
|
||||
self._write_button_path()
|
||||
|
||||
# Write the o:lock element.
|
||||
self._write_shapetype_lock()
|
||||
|
||||
self._xml_end_tag("v:shapetype")
|
||||
|
||||
def _write_image_shapetype(self):
|
||||
# Write the <v:shapetype> element.
|
||||
shape_id = "_x0000_t75"
|
||||
coordsize = "21600,21600"
|
||||
spt = 75
|
||||
o_preferrelative = "t"
|
||||
path = "m@4@5l@4@11@9@11@9@5xe"
|
||||
filled = "f"
|
||||
stroked = "f"
|
||||
|
||||
attributes = [
|
||||
("id", shape_id),
|
||||
("coordsize", coordsize),
|
||||
("o:spt", spt),
|
||||
("o:preferrelative", o_preferrelative),
|
||||
("path", path),
|
||||
("filled", filled),
|
||||
("stroked", stroked),
|
||||
]
|
||||
|
||||
self._xml_start_tag("v:shapetype", attributes)
|
||||
|
||||
# Write the v:stroke element.
|
||||
self._write_stroke()
|
||||
|
||||
# Write the v:formulas element.
|
||||
self._write_formulas()
|
||||
|
||||
# Write the v:path element.
|
||||
self._write_image_path()
|
||||
|
||||
# Write the o:lock element.
|
||||
self._write_aspect_ratio_lock()
|
||||
|
||||
self._xml_end_tag("v:shapetype")
|
||||
|
||||
def _write_stroke(self):
|
||||
# Write the <v:stroke> element.
|
||||
joinstyle = "miter"
|
||||
|
||||
attributes = [("joinstyle", joinstyle)]
|
||||
|
||||
self._xml_empty_tag("v:stroke", attributes)
|
||||
|
||||
def _write_comment_path(self, gradientshapeok, connecttype):
|
||||
# Write the <v:path> element.
|
||||
attributes = []
|
||||
|
||||
if gradientshapeok:
|
||||
attributes.append(("gradientshapeok", "t"))
|
||||
|
||||
attributes.append(("o:connecttype", connecttype))
|
||||
|
||||
self._xml_empty_tag("v:path", attributes)
|
||||
|
||||
def _write_button_path(self):
|
||||
# Write the <v:path> element.
|
||||
shadowok = "f"
|
||||
extrusionok = "f"
|
||||
strokeok = "f"
|
||||
fillok = "f"
|
||||
connecttype = "rect"
|
||||
|
||||
attributes = [
|
||||
("shadowok", shadowok),
|
||||
("o:extrusionok", extrusionok),
|
||||
("strokeok", strokeok),
|
||||
("fillok", fillok),
|
||||
("o:connecttype", connecttype),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("v:path", attributes)
|
||||
|
||||
def _write_image_path(self):
|
||||
# Write the <v:path> element.
|
||||
extrusionok = "f"
|
||||
gradientshapeok = "t"
|
||||
connecttype = "rect"
|
||||
|
||||
attributes = [
|
||||
("o:extrusionok", extrusionok),
|
||||
("gradientshapeok", gradientshapeok),
|
||||
("o:connecttype", connecttype),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("v:path", attributes)
|
||||
|
||||
def _write_shapetype_lock(self):
|
||||
# Write the <o:lock> element.
|
||||
ext = "edit"
|
||||
shapetype = "t"
|
||||
|
||||
attributes = [
|
||||
("v:ext", ext),
|
||||
("shapetype", shapetype),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("o:lock", attributes)
|
||||
|
||||
def _write_rotation_lock(self):
|
||||
# Write the <o:lock> element.
|
||||
ext = "edit"
|
||||
rotation = "t"
|
||||
|
||||
attributes = [
|
||||
("v:ext", ext),
|
||||
("rotation", rotation),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("o:lock", attributes)
|
||||
|
||||
def _write_aspect_ratio_lock(self):
|
||||
# Write the <o:lock> element.
|
||||
ext = "edit"
|
||||
aspectratio = "t"
|
||||
|
||||
attributes = [
|
||||
("v:ext", ext),
|
||||
("aspectratio", aspectratio),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("o:lock", attributes)
|
||||
|
||||
def _write_comment_shape(self, shape_id, z_index, comment):
|
||||
# Write the <v:shape> element.
|
||||
shape_type = "#_x0000_t202"
|
||||
insetmode = "auto"
|
||||
visibility = "hidden"
|
||||
|
||||
# Set the shape index.
|
||||
shape_id = "_x0000_s" + str(shape_id)
|
||||
|
||||
# Get the comment parameters
|
||||
row = comment[0]
|
||||
col = comment[1]
|
||||
visible = comment[4]
|
||||
fillcolor = comment[5]
|
||||
vertices = comment[9]
|
||||
|
||||
(left, top, width, height) = self._pixels_to_points(vertices)
|
||||
|
||||
# Set the visibility.
|
||||
if visible:
|
||||
visibility = "visible"
|
||||
|
||||
style = (
|
||||
"position:absolute;"
|
||||
"margin-left:%.15gpt;"
|
||||
"margin-top:%.15gpt;"
|
||||
"width:%.15gpt;"
|
||||
"height:%.15gpt;"
|
||||
"z-index:%d;"
|
||||
"visibility:%s" % (left, top, width, height, z_index, visibility)
|
||||
)
|
||||
|
||||
attributes = [
|
||||
("id", shape_id),
|
||||
("type", shape_type),
|
||||
("style", style),
|
||||
("fillcolor", fillcolor),
|
||||
("o:insetmode", insetmode),
|
||||
]
|
||||
|
||||
self._xml_start_tag("v:shape", attributes)
|
||||
|
||||
# Write the v:fill element.
|
||||
self._write_comment_fill()
|
||||
|
||||
# Write the v:shadow element.
|
||||
self._write_shadow()
|
||||
|
||||
# Write the v:path element.
|
||||
self._write_comment_path(None, "none")
|
||||
|
||||
# Write the v:textbox element.
|
||||
self._write_comment_textbox()
|
||||
|
||||
# Write the x:ClientData element.
|
||||
self._write_comment_client_data(row, col, visible, vertices)
|
||||
|
||||
self._xml_end_tag("v:shape")
|
||||
|
||||
def _write_button_shape(self, shape_id, z_index, button):
|
||||
# Write the <v:shape> element.
|
||||
shape_type = "#_x0000_t201"
|
||||
|
||||
# Set the shape index.
|
||||
shape_id = "_x0000_s" + str(shape_id)
|
||||
|
||||
# Get the button parameters.
|
||||
# row = button["_row"]
|
||||
# col = button["_col"]
|
||||
vertices = button["vertices"]
|
||||
|
||||
(left, top, width, height) = self._pixels_to_points(vertices)
|
||||
|
||||
style = (
|
||||
"position:absolute;"
|
||||
"margin-left:%.15gpt;"
|
||||
"margin-top:%.15gpt;"
|
||||
"width:%.15gpt;"
|
||||
"height:%.15gpt;"
|
||||
"z-index:%d;"
|
||||
"mso-wrap-style:tight" % (left, top, width, height, z_index)
|
||||
)
|
||||
|
||||
attributes = [
|
||||
("id", shape_id),
|
||||
("type", shape_type),
|
||||
]
|
||||
|
||||
if button.get("description"):
|
||||
attributes.append(("alt", button["description"]))
|
||||
|
||||
attributes.append(("style", style))
|
||||
attributes.append(("o:button", "t"))
|
||||
attributes.append(("fillcolor", "buttonFace [67]"))
|
||||
attributes.append(("strokecolor", "windowText [64]"))
|
||||
attributes.append(("o:insetmode", "auto"))
|
||||
|
||||
self._xml_start_tag("v:shape", attributes)
|
||||
|
||||
# Write the v:fill element.
|
||||
self._write_button_fill()
|
||||
|
||||
# Write the o:lock element.
|
||||
self._write_rotation_lock()
|
||||
|
||||
# Write the v:textbox element.
|
||||
self._write_button_textbox(button["font"])
|
||||
|
||||
# Write the x:ClientData element.
|
||||
self._write_button_client_data(button)
|
||||
|
||||
self._xml_end_tag("v:shape")
|
||||
|
||||
def _write_image_shape(self, shape_id, z_index, image_data):
|
||||
# Write the <v:shape> element.
|
||||
shape_type = "#_x0000_t75"
|
||||
|
||||
# Set the shape index.
|
||||
shape_id = "_x0000_s" + str(shape_id)
|
||||
|
||||
# Get the image parameters
|
||||
width = image_data[0]
|
||||
height = image_data[1]
|
||||
name = image_data[2]
|
||||
position = image_data[3]
|
||||
x_dpi = image_data[4]
|
||||
y_dpi = image_data[5]
|
||||
ref_id = image_data[6]
|
||||
|
||||
# Scale the height/width by the resolution, relative to 72dpi.
|
||||
width = width * 72.0 / x_dpi
|
||||
height = height * 72.0 / y_dpi
|
||||
|
||||
# Excel uses a rounding based around 72 and 96 dpi.
|
||||
width = 72.0 / 96 * int(width * 96.0 / 72 + 0.25)
|
||||
height = 72.0 / 96 * int(height * 96.0 / 72 + 0.25)
|
||||
|
||||
style = (
|
||||
"position:absolute;"
|
||||
"margin-left:0;"
|
||||
"margin-top:0;"
|
||||
"width:%.15gpt;"
|
||||
"height:%.15gpt;"
|
||||
"z-index:%d" % (width, height, z_index)
|
||||
)
|
||||
|
||||
attributes = [
|
||||
("id", position),
|
||||
("o:spid", shape_id),
|
||||
("type", shape_type),
|
||||
("style", style),
|
||||
]
|
||||
|
||||
self._xml_start_tag("v:shape", attributes)
|
||||
|
||||
# Write the v:imagedata element.
|
||||
self._write_imagedata(ref_id, name)
|
||||
|
||||
# Write the o:lock element.
|
||||
self._write_rotation_lock()
|
||||
|
||||
self._xml_end_tag("v:shape")
|
||||
|
||||
def _write_comment_fill(self):
|
||||
# Write the <v:fill> element.
|
||||
color_2 = "#ffffe1"
|
||||
|
||||
attributes = [("color2", color_2)]
|
||||
|
||||
self._xml_empty_tag("v:fill", attributes)
|
||||
|
||||
def _write_button_fill(self):
|
||||
# Write the <v:fill> element.
|
||||
color_2 = "buttonFace [67]"
|
||||
detectmouseclick = "t"
|
||||
|
||||
attributes = [
|
||||
("color2", color_2),
|
||||
("o:detectmouseclick", detectmouseclick),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("v:fill", attributes)
|
||||
|
||||
def _write_shadow(self):
|
||||
# Write the <v:shadow> element.
|
||||
on = "t"
|
||||
color = "black"
|
||||
obscured = "t"
|
||||
|
||||
attributes = [
|
||||
("on", on),
|
||||
("color", color),
|
||||
("obscured", obscured),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("v:shadow", attributes)
|
||||
|
||||
def _write_comment_textbox(self):
|
||||
# Write the <v:textbox> element.
|
||||
style = "mso-direction-alt:auto"
|
||||
|
||||
attributes = [("style", style)]
|
||||
|
||||
self._xml_start_tag("v:textbox", attributes)
|
||||
|
||||
# Write the div element.
|
||||
self._write_div("left")
|
||||
|
||||
self._xml_end_tag("v:textbox")
|
||||
|
||||
def _write_button_textbox(self, font):
|
||||
# Write the <v:textbox> element.
|
||||
style = "mso-direction-alt:auto"
|
||||
|
||||
attributes = [("style", style), ("o:singleclick", "f")]
|
||||
|
||||
self._xml_start_tag("v:textbox", attributes)
|
||||
|
||||
# Write the div element.
|
||||
self._write_div("center", font)
|
||||
|
||||
self._xml_end_tag("v:textbox")
|
||||
|
||||
def _write_div(self, align, font=None):
|
||||
# Write the <div> element.
|
||||
|
||||
style = "text-align:" + align
|
||||
|
||||
attributes = [("style", style)]
|
||||
|
||||
self._xml_start_tag("div", attributes)
|
||||
|
||||
if font:
|
||||
# Write the font element.
|
||||
self._write_font(font)
|
||||
|
||||
self._xml_end_tag("div")
|
||||
|
||||
def _write_font(self, font):
|
||||
# Write the <font> element.
|
||||
caption = font["caption"]
|
||||
face = "Calibri"
|
||||
size = 220
|
||||
color = "#000000"
|
||||
|
||||
attributes = [
|
||||
("face", face),
|
||||
("size", size),
|
||||
("color", color),
|
||||
]
|
||||
|
||||
self._xml_data_element("font", caption, attributes)
|
||||
|
||||
def _write_comment_client_data(self, row, col, visible, vertices):
|
||||
# Write the <x:ClientData> element.
|
||||
object_type = "Note"
|
||||
|
||||
attributes = [("ObjectType", object_type)]
|
||||
|
||||
self._xml_start_tag("x:ClientData", attributes)
|
||||
|
||||
# Write the x:MoveWithCells element.
|
||||
self._write_move_with_cells()
|
||||
|
||||
# Write the x:SizeWithCells element.
|
||||
self._write_size_with_cells()
|
||||
|
||||
# Write the x:Anchor element.
|
||||
self._write_anchor(vertices)
|
||||
|
||||
# Write the x:AutoFill element.
|
||||
self._write_auto_fill()
|
||||
|
||||
# Write the x:Row element.
|
||||
self._write_row(row)
|
||||
|
||||
# Write the x:Column element.
|
||||
self._write_column(col)
|
||||
|
||||
# Write the x:Visible element.
|
||||
if visible:
|
||||
self._write_visible()
|
||||
|
||||
self._xml_end_tag("x:ClientData")
|
||||
|
||||
def _write_button_client_data(self, button):
|
||||
# Write the <x:ClientData> element.
|
||||
macro = button["macro"]
|
||||
vertices = button["vertices"]
|
||||
|
||||
object_type = "Button"
|
||||
|
||||
attributes = [("ObjectType", object_type)]
|
||||
|
||||
self._xml_start_tag("x:ClientData", attributes)
|
||||
|
||||
# Write the x:Anchor element.
|
||||
self._write_anchor(vertices)
|
||||
|
||||
# Write the x:PrintObject element.
|
||||
self._write_print_object()
|
||||
|
||||
# Write the x:AutoFill element.
|
||||
self._write_auto_fill()
|
||||
|
||||
# Write the x:FmlaMacro element.
|
||||
self._write_fmla_macro(macro)
|
||||
|
||||
# Write the x:TextHAlign element.
|
||||
self._write_text_halign()
|
||||
|
||||
# Write the x:TextVAlign element.
|
||||
self._write_text_valign()
|
||||
|
||||
self._xml_end_tag("x:ClientData")
|
||||
|
||||
def _write_move_with_cells(self):
|
||||
# Write the <x:MoveWithCells> element.
|
||||
self._xml_empty_tag("x:MoveWithCells")
|
||||
|
||||
def _write_size_with_cells(self):
|
||||
# Write the <x:SizeWithCells> element.
|
||||
self._xml_empty_tag("x:SizeWithCells")
|
||||
|
||||
def _write_visible(self):
|
||||
# Write the <x:Visible> element.
|
||||
self._xml_empty_tag("x:Visible")
|
||||
|
||||
def _write_anchor(self, vertices):
|
||||
# Write the <x:Anchor> element.
|
||||
(col_start, row_start, x1, y1, col_end, row_end, x2, y2) = vertices[:8]
|
||||
|
||||
strings = [col_start, x1, row_start, y1, col_end, x2, row_end, y2]
|
||||
strings = [str(i) for i in strings]
|
||||
|
||||
data = ", ".join(strings)
|
||||
|
||||
self._xml_data_element("x:Anchor", data)
|
||||
|
||||
def _write_auto_fill(self):
|
||||
# Write the <x:AutoFill> element.
|
||||
data = "False"
|
||||
|
||||
self._xml_data_element("x:AutoFill", data)
|
||||
|
||||
def _write_row(self, data):
|
||||
# Write the <x:Row> element.
|
||||
self._xml_data_element("x:Row", data)
|
||||
|
||||
def _write_column(self, data):
|
||||
# Write the <x:Column> element.
|
||||
self._xml_data_element("x:Column", data)
|
||||
|
||||
def _write_print_object(self):
|
||||
# Write the <x:PrintObject> element.
|
||||
self._xml_data_element("x:PrintObject", "False")
|
||||
|
||||
def _write_text_halign(self):
|
||||
# Write the <x:TextHAlign> element.
|
||||
self._xml_data_element("x:TextHAlign", "Center")
|
||||
|
||||
def _write_text_valign(self):
|
||||
# Write the <x:TextVAlign> element.
|
||||
self._xml_data_element("x:TextVAlign", "Center")
|
||||
|
||||
def _write_fmla_macro(self, data):
|
||||
# Write the <x:FmlaMacro> element.
|
||||
self._xml_data_element("x:FmlaMacro", data)
|
||||
|
||||
def _write_imagedata(self, ref_id, o_title):
|
||||
# Write the <v:imagedata> element.
|
||||
attributes = [
|
||||
("o:relid", "rId" + str(ref_id)),
|
||||
("o:title", o_title),
|
||||
]
|
||||
|
||||
self._xml_empty_tag("v:imagedata", attributes)
|
||||
|
||||
def _write_formulas(self):
|
||||
# Write the <v:formulas> element.
|
||||
self._xml_start_tag("v:formulas")
|
||||
|
||||
# Write the v:f elements.
|
||||
self._write_formula("if lineDrawn pixelLineWidth 0")
|
||||
self._write_formula("sum @0 1 0")
|
||||
self._write_formula("sum 0 0 @1")
|
||||
self._write_formula("prod @2 1 2")
|
||||
self._write_formula("prod @3 21600 pixelWidth")
|
||||
self._write_formula("prod @3 21600 pixelHeight")
|
||||
self._write_formula("sum @0 0 1")
|
||||
self._write_formula("prod @6 1 2")
|
||||
self._write_formula("prod @7 21600 pixelWidth")
|
||||
self._write_formula("sum @8 21600 0")
|
||||
self._write_formula("prod @7 21600 pixelHeight")
|
||||
self._write_formula("sum @10 21600 0")
|
||||
|
||||
self._xml_end_tag("v:formulas")
|
||||
|
||||
def _write_formula(self, eqn):
|
||||
# Write the <v:f> element.
|
||||
attributes = [("eqn", eqn)]
|
||||
|
||||
self._xml_empty_tag("v:f", attributes)
|
||||
1968
billinglayer/python/xlsxwriter/workbook.py
Normal file
1968
billinglayer/python/xlsxwriter/workbook.py
Normal file
File diff suppressed because it is too large
Load Diff
8367
billinglayer/python/xlsxwriter/worksheet.py
Normal file
8367
billinglayer/python/xlsxwriter/worksheet.py
Normal file
File diff suppressed because it is too large
Load Diff
206
billinglayer/python/xlsxwriter/xmlwriter.py
Normal file
206
billinglayer/python/xlsxwriter/xmlwriter.py
Normal file
@@ -0,0 +1,206 @@
|
||||
###############################################################################
|
||||
#
|
||||
# XMLwriter - A base class for XlsxWriter classes.
|
||||
#
|
||||
# Used in conjunction with XlsxWriter.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
|
||||
#
|
||||
|
||||
# Standard packages.
|
||||
import re
|
||||
from io import StringIO
|
||||
|
||||
|
||||
class XMLwriter(object):
|
||||
"""
|
||||
Simple XML writer class.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.fh = None
|
||||
self.escapes = re.compile('["&<>\n]')
|
||||
self.internal_fh = False
|
||||
|
||||
def _set_filehandle(self, filehandle):
|
||||
# Set the writer filehandle directly. Mainly for testing.
|
||||
self.fh = filehandle
|
||||
self.internal_fh = False
|
||||
|
||||
def _set_xml_writer(self, filename):
|
||||
# Set the XML writer filehandle for the object.
|
||||
if isinstance(filename, StringIO):
|
||||
self.internal_fh = False
|
||||
self.fh = filename
|
||||
else:
|
||||
self.internal_fh = True
|
||||
self.fh = open(filename, "w", encoding="utf-8")
|
||||
|
||||
def _xml_close(self):
|
||||
# Close the XML filehandle if we created it.
|
||||
if self.internal_fh:
|
||||
self.fh.close()
|
||||
|
||||
def _xml_declaration(self):
|
||||
# Write the XML declaration.
|
||||
self.fh.write("""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n""")
|
||||
|
||||
def _xml_start_tag(self, tag, attributes=[]):
|
||||
# Write an XML start tag with optional attributes.
|
||||
for key, value in attributes:
|
||||
value = self._escape_attributes(value)
|
||||
tag += ' %s="%s"' % (key, value)
|
||||
|
||||
self.fh.write("<%s>" % tag)
|
||||
|
||||
def _xml_start_tag_unencoded(self, tag, attributes=[]):
|
||||
# Write an XML start tag with optional, unencoded, attributes.
|
||||
# This is a minor speed optimization for elements that don't
|
||||
# need encoding.
|
||||
for key, value in attributes:
|
||||
tag += ' %s="%s"' % (key, value)
|
||||
|
||||
self.fh.write("<%s>" % tag)
|
||||
|
||||
def _xml_end_tag(self, tag):
|
||||
# Write an XML end tag.
|
||||
self.fh.write("</%s>" % tag)
|
||||
|
||||
def _xml_empty_tag(self, tag, attributes=[]):
|
||||
# Write an empty XML tag with optional attributes.
|
||||
for key, value in attributes:
|
||||
value = self._escape_attributes(value)
|
||||
tag += ' %s="%s"' % (key, value)
|
||||
|
||||
self.fh.write("<%s/>" % tag)
|
||||
|
||||
def _xml_empty_tag_unencoded(self, tag, attributes=[]):
|
||||
# Write an empty XML tag with optional, unencoded, attributes.
|
||||
# This is a minor speed optimization for elements that don't
|
||||
# need encoding.
|
||||
for key, value in attributes:
|
||||
tag += ' %s="%s"' % (key, value)
|
||||
|
||||
self.fh.write("<%s/>" % tag)
|
||||
|
||||
def _xml_data_element(self, tag, data, attributes=[]):
|
||||
# Write an XML element containing data with optional attributes.
|
||||
end_tag = tag
|
||||
|
||||
for key, value in attributes:
|
||||
value = self._escape_attributes(value)
|
||||
tag += ' %s="%s"' % (key, value)
|
||||
|
||||
data = self._escape_data(data)
|
||||
self.fh.write("<%s>%s</%s>" % (tag, data, end_tag))
|
||||
|
||||
def _xml_string_element(self, index, attributes=[]):
|
||||
# Optimized tag writer for <c> cell string elements in the inner loop.
|
||||
attr = ""
|
||||
|
||||
for key, value in attributes:
|
||||
value = self._escape_attributes(value)
|
||||
attr += ' %s="%s"' % (key, value)
|
||||
|
||||
self.fh.write("""<c%s t="s"><v>%d</v></c>""" % (attr, index))
|
||||
|
||||
def _xml_si_element(self, string, attributes=[]):
|
||||
# Optimized tag writer for shared strings <si> elements.
|
||||
attr = ""
|
||||
|
||||
for key, value in attributes:
|
||||
value = self._escape_attributes(value)
|
||||
attr += ' %s="%s"' % (key, value)
|
||||
|
||||
string = self._escape_data(string)
|
||||
|
||||
self.fh.write("""<si><t%s>%s</t></si>""" % (attr, string))
|
||||
|
||||
def _xml_rich_si_element(self, string):
|
||||
# Optimized tag writer for shared strings <si> rich string elements.
|
||||
|
||||
self.fh.write("""<si>%s</si>""" % string)
|
||||
|
||||
def _xml_number_element(self, number, attributes=[]):
|
||||
# Optimized tag writer for <c> cell number elements in the inner loop.
|
||||
attr = ""
|
||||
|
||||
for key, value in attributes:
|
||||
value = self._escape_attributes(value)
|
||||
attr += ' %s="%s"' % (key, value)
|
||||
|
||||
self.fh.write("""<c%s><v>%.16G</v></c>""" % (attr, number))
|
||||
|
||||
def _xml_formula_element(self, formula, result, attributes=[]):
|
||||
# Optimized tag writer for <c> cell formula elements in the inner loop.
|
||||
attr = ""
|
||||
|
||||
for key, value in attributes:
|
||||
value = self._escape_attributes(value)
|
||||
attr += ' %s="%s"' % (key, value)
|
||||
|
||||
self.fh.write(
|
||||
"""<c%s><f>%s</f><v>%s</v></c>"""
|
||||
% (attr, self._escape_data(formula), self._escape_data(result))
|
||||
)
|
||||
|
||||
def _xml_inline_string(self, string, preserve, attributes=[]):
|
||||
# Optimized tag writer for inlineStr cell elements in the inner loop.
|
||||
attr = ""
|
||||
t_attr = ""
|
||||
|
||||
# Set the <t> attribute to preserve whitespace.
|
||||
if preserve:
|
||||
t_attr = ' xml:space="preserve"'
|
||||
|
||||
for key, value in attributes:
|
||||
value = self._escape_attributes(value)
|
||||
attr += ' %s="%s"' % (key, value)
|
||||
|
||||
string = self._escape_data(string)
|
||||
|
||||
self.fh.write(
|
||||
"""<c%s t="inlineStr"><is><t%s>%s</t></is></c>""" % (attr, t_attr, string)
|
||||
)
|
||||
|
||||
def _xml_rich_inline_string(self, string, attributes=[]):
|
||||
# Optimized tag writer for rich inlineStr in the inner loop.
|
||||
attr = ""
|
||||
|
||||
for key, value in attributes:
|
||||
value = self._escape_attributes(value)
|
||||
attr += ' %s="%s"' % (key, value)
|
||||
|
||||
self.fh.write("""<c%s t="inlineStr"><is>%s</is></c>""" % (attr, string))
|
||||
|
||||
def _escape_attributes(self, attribute):
|
||||
# Escape XML characters in attributes.
|
||||
try:
|
||||
if not self.escapes.search(attribute):
|
||||
return attribute
|
||||
except TypeError:
|
||||
return attribute
|
||||
|
||||
attribute = (
|
||||
attribute.replace("&", "&")
|
||||
.replace('"', """)
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("\n", "
")
|
||||
)
|
||||
return attribute
|
||||
|
||||
def _escape_data(self, data):
|
||||
# Escape XML characters in data sections of tags. Note, this
|
||||
# is different from _escape_attributes() in that double quotes
|
||||
# are not escaped by Excel.
|
||||
try:
|
||||
if not self.escapes.search(data):
|
||||
return data
|
||||
except TypeError:
|
||||
return data
|
||||
|
||||
data = data.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
return data
|
||||
Reference in New Issue
Block a user