XlsxWriter-1.1.2/0000755000076500000240000000000013362632140013667 5ustar Johnstaff00000000000000XlsxWriter-1.1.2/Changes0000644000076500000240000010451213362630222015164 0ustar Johnstaff00000000000000.. Note:: Support for end of life Pythons 2.5, 2.6, 3.1, 3.2 and 3.3 will be dropped in early 2019. Release 1.1.2 - October 20 2018 ------------------------------- * Fix for issue where ``in_memory`` files weren't compressed. Issue `#573 `_. * Fix ``write()`` so that it handles array formulas as documented. Issue `#418 `_. * Fix for issue with special characters in worksheet table functions. Issue `#442 `_. * Added warnings for input issues in :func:`write_rich_string()` such as blank strings, double formats or insufficient parameters. Issue `#425 `_. Release 1.1.1 - September 22 2018 --------------------------------- * Added comment font name and size options. Issue `#201 `_. * Fix for issue when using text boxes in the same workbook as a chartsheet. Issue `#420 `_. Release 1.1.0 - September 2 2018 -------------------------------- * Added functionality to align chart category axis labels. See the ``label_align`` property of the :func:`set_x_axis()` method. * Added worksheet :func:`hide_row_col_headers()` method to turn off worksheet row and column headings. Issue `#480 `_. * Added the :func:`set_tab_ratio()` method to set the ratio between the worksheet tabs and the horizontal slider. Issue `#481 `_. * Fixed issue with icon conditional formats when the values were zero. Issue `#565 `_. Release 1.0.9 - August 27 2018 ------------------------------ * Fix for issue with formulas quoted as strings in conditional formats, introduced in version 1.0.7. Issue `#564 `_. Release 1.0.8 - August 27 2018 ------------------------------ * Added named exceptions to XlsxWriter. See :ref:`exceptions`. * Removed the implicit :func:`close()` in the destructor since it wasn't guaranteed to work correctly and raised a confusing exception when any other exception was triggered. **Note that this is a backward incompatible change.** The ``with`` context manager is a better way to close automatically, see :func:`close()`. * Added border, fill, pattern and gradient formatting options to :func:`set_legend()`. Issue `#545 `_. * Added ``top_right`` position to :func:`set_legend()`. Issue `#537 `_. Release 1.0.7 - August 16 2018 ------------------------------ * Fix for unicode type error in Python 3. Issue `#554 `_. Release 1.0.6 - August 15 2018 ------------------------------ * Added some performance improvements. PR `#551 `_. Release 1.0.5 - May 19 2018 --------------------------- * Added example of how to subclass the Workbook and Worksheet objects. See :ref:`ex_inheritance1` and :ref:`ex_inheritance2`. * Added support for WMF and EMF image formats to the Worksheet :func:`add_image` method. Release 1.0.4 - April 14 2018 ----------------------------- * Set the xlsx internal file member datetimes to 1980-01-01 00:00:00 like Excel so that apps can produce a consistent binary file once the workbook :func:`set_properties` ``created`` date is set. Pull request `#495 `_. * Fix for jpeg images that reported unknown height/width due to unusual SOF markers. Issue `#506 `_. * Added support for blanks in list autofilter. Issue `#505 `_. Release 1.0.3 - April 10 2018 ----------------------------- * Added Excel 2010 data bar features such as solid fills and control over the display of negative values. See :ref:`working_with_conditional_formats` and :ref:`ex_cond_format`. Feature request `#502 `_. * Fixed :func:`set_column` parameter names to match docs and other methods. Note, this is a backward incompatible change. Issue `#504 `_. * Fixed missing plotarea formatting in pie/doughnut charts. Release 1.0.2 - October 14 2017 ------------------------------- * Fix for cases where the hyperlink style added in the previous release didn't work. Feature request `#455 `_. Release 1.0.1 - October 14 2017 ------------------------------- * Changed default :func:`write_url` format to the Excel hyperlink style so that it changes when the theme is changed and also so that it indicates that the link has been clicked. Feature request `#455 `_. Release 1.0.0 - September 16 2017 --------------------------------- * Added icon sets to conditional formatting. See :ref:`working_with_conditional_formats` and :ref:`ex_cond_format`. Feature request `#387 `_. Release 0.9.9 - September 5 2017 -------------------------------- * Added ``stop_if_true`` parameter to conditional formatting. Feature request `#386 `_. Release 0.9.8 - July 1 2017 --------------------------- * Fixed issue where spurious deprecation warning was raised in ``-Werror`` mode. Issue `#451 `_. Release 0.9.7 - June 25 2017 ---------------------------- * Minor bug and doc fixes. Release 0.9.6 - Dec 26 2016 --------------------------- * Fix for table with data but without a header. Issue `#405 `_. * Add a warning when the number of series in a chart exceeds Excel's limit of 255. Issue `#399 `_. Release 0.9.5 - Dec 24 2016 --------------------------- * Fix for missing `remove_timezone` option in Chart class. PR from Thomas Arnhold `#404 `_. Release 0.9.4 - Dec 2 2016 -------------------------- * Added user definable removal of timezones in datetimes. See the :func:`Workbook` constructor option ``remove_timezone`` and :ref:`Timezone Handling in XlsxWriter `. Issue `#257 `_. * Fix duplicate header warning in :func:`add_table` when there is only one user defined header. Issue `#380 `_. * Fix for `center_across` property in :func:`add_format`. Issue `#381 `_. Release 0.9.3 - July 8 2016 --------------------------- * Added check to :func:`add_table` to prevent duplicate header names which leads to a corrupt Excel file. Issue `#362 `_. Release 0.9.2 - June 13 2016 ---------------------------- * Added workbook :func:`set_size` method to set the workbook window size. Release 0.9.1 - June 8 2016 --------------------------- * Added font support to chart :func:`set_table`. * Documented used of font rotation in chart :ref:`data labels `. Issue `#337 `_. Release 0.9.0 - June 7 2016 --------------------------- * Added :ref:`trendline properties `: ``intercept``, ``display_equation`` and ``display_r_squared``. Feature request `#357 `_. Release 0.8.9 - June 1 2016 --------------------------- * Fix for :func:`insert_image` issue when handling images with zero dpi. Issue `#356 `_. Release 0.8.8 - May 31 2016 --------------------------- * Added workbook :func:`set_custom_property` method to set custom document properties. Feature request `#355 `_. Release 0.8.7 - May 13 2016 --------------------------- * Fix for issue when inserting read-only images on Windows. Issue `#352 `_. * Added :func:`get_worksheet_by_name()` method to allow the retrieval of a worksheet from a workbook via its name. * Fixed issue where internal file creation and modification dates were in the local timezone instead of UTC. Release 0.8.6 - April 27 2016 ----------------------------- * Fix for ``external:`` urls where the target/anchor contains spaces. Issue `#350 `_. Release 0.8.5 - April 17 2016 ----------------------------- * Added additional documentation on :ref:`ewx_pandas` and :ref:`pandas_examples`. * Added fix for :func:`set_center_across` format method. Release 0.8.4 - January 16 2016 ------------------------------- * Fix for :func:`write_url` exception when the URL contains two ``#`` location/anchors. Note, URLs like this aren't strictly valid and cannot be entered manually in Excel. Issue `#330 `_. Release 0.8.3 - January 14 2016 ------------------------------- * Added options to configure chart axis tick placement. See :func:`set_x_axis()`. Release 0.8.2 - January 13 2016 ------------------------------- * Added transparency option to solid fill colors in chart areas (:ref:`chart_formatting_fill`). Feature request `#298 `_. Release 0.8.1 - January 12 2016 ------------------------------- * Added option to set chart tick interval. Feature request `#251 `_. Release 0.8.0 - January 10 2016 ------------------------------- * Added additional documentation on :ref:`working_with_formulas`. Release 0.7.9 - January 9 2016 ------------------------------ * Added chart pattern fills, see :ref:`chart_formatting_pattern` and :ref:`ex_chart_pattern`. Feature request `#268 `_. Release 0.7.8 - January 6 2016 ------------------------------ * Add checks for valid and non-duplicate worksheet table names. Issue `#319 `_. Release 0.7.7 - October 19 2015 ------------------------------- * Added support for table header formatting and a fix for wrapped lines in the header. Feature request `#287 `_. Release 0.7.6 - October 7 2015 ------------------------------ * Fix for images with negative offsets. Issue `#273 `_. Release 0.7.5 - October 4 2015 ------------------------------ * Allow hyperlinks longer than 255 characters when the link and anchor are each less than or equal to 255 characters. * Added ``hyperlink_base`` document property. Feature request `#306 `_. Release 0.7.4 - September 29 2015 --------------------------------- * Added option to allow data validation input messages with the 'any' validate parameter. * Fixed url encoding of links to external files and directories. Issue `#278 `_. Release 0.7.3 - May 7 2015 -------------------------- * Added documentation on :ref:`ewx_pandas` and :ref:`pandas_examples`. * Added support for ``with`` context manager. Pull request `#239 `_. Release 0.7.2 - March 29 2015 ----------------------------- * Added support for textboxes in worksheets. See :func:`insert_textbox()` and :ref:`working_with_textboxes` for more details. Feature request `#107 `_. Release 0.7.1 - March 23 2015 ----------------------------- * Added gradient fills to chart objects such as the plot area of columns. See :ref:`chart_formatting_gradient` and :ref:`ex_chart_gradient`. Feature request `#228 `_. Release 0.7.0 - March 21 2015 ----------------------------- * Added support for display units in chart axes. See :func:`set_x_axis()`. Feature request `#185 `_. * Added ``nan_inf_to_errors`` :func:`Workbook` constructor option to allow mapping of Python `nan/inf` value to Excel error formulas in ``write()`` and ``write_number()``. Feature request `#150 `_. Release 0.6.9 - March 19 2015 ----------------------------- * Added support for clustered category charts. See :ref:`ex_chart_clustered` for details. Feature request `#180 `_. * Refactored the :ref:`format` and formatting documentation. Release 0.6.8 - March 17 2015 ----------------------------- * Added option to combine two different chart types. See the :ref:`chart_combined_charts` section and :ref:`ex_chart_combined` and :ref:`ex_chart_pareto` for more details. Feature request `#72 `_. Release 0.6.7 - March 1 2015 ---------------------------- * Added option to add function value in worksheet :func:`add_table`. Feature request `#216 `_. * Fix for A1 row/col numbers below lower bound. Issue `#212 `_. Release 0.6.6 - January 16 2015 ------------------------------- * Fix for incorrect shebang line in `vba_extract.py` packaged in wheel. Issue `#211 `_. * Added docs and example for diagonal cell border. See :ref:`ex_diagonal_border`. Release 0.6.5 - December 31 2014 -------------------------------- * Added worksheet quoting for chart names in lists. Issue `#205 `_. * Added docs on how to find and set VBA codenames. Issue `#202 `_. * Fix Python3 issue with unused charts. Issue `#200 `_. * Enabled warning for missing category is scatter chart. Issue `#197 `_. * Fix for upper chart style limit. Increased the chart style limit from 42 to the correct 48. Issue `#192 `_. * Raise warning if a chart is inserted more than once. Issue `#184 `_. Release 0.6.4 - November 15 2014 -------------------------------- * Fix for issue where fonts applied to data labels raised exception. Issue `#179 `_. * Added option to allow explicit text axis types for charts, similar to date axes. Feature request `#178 `_. * Fix for issue where the bar/column chart gap and overlap weren't applied to the secondary axis. Issue `#177 `_. Release 0.6.3 - November 6 2014 ------------------------------- * Added support for adding VBA macros to workbooks. See :ref:`macros`. Feature request `#126 `_. Release 0.6.2 - November 1 2014 ------------------------------- * Added chart axis line and fill properties. Feature request `#88 `_. Release 0.6.1 - October 29 2014 ------------------------------- * Added chart specific handling of data label positions since not all positions are available for all chart types. Issue `#170 `_. * Added number formatting (issue `#130 `_), font handling, separator and legend key for data labels. See :ref:`chart_series_option_data_labels` * Fix for non-quoted worksheet names containing spaces and non-alphanumeric characters. Issue `#167 `_. Release 0.6.0 - October 15 2014 ------------------------------- * Added option to add images to headers and footers. See :ref:`ex_headers_footers`. Feature request `#133 `_. * Fixed issue where non 96dpi images weren't scaled properly in Excel. Issue `#164 `_. * Added option to not scale header/footer with page. See :func:`set_header`. Feature request `#134 `_. Release 0.5.9 - October 11 2014 ------------------------------- * Removed ``egg_base`` requirement from ``setup.cfg`` which was preventing installation on Windows. Issue `#162 `_. * Fix for issue where X axis title formula was overwritten by the Y axis title. Issue `#161 `_. Release 0.5.8 - September 28 2014 --------------------------------- * Added support for Doughnut charts. Feature request `#157 `_. * Added support for wheel packages. Feature request `#156 `_. * Made the exception handling in ``write()`` clearer for unsupported types so that it raises a more accurate TypeError instead of a ValueError. Issue `#153 `_. Release 0.5.7 - August 13 2014 ------------------------------ * Added support for :func:`insert_image` images from byte streams to allow images from URLs and other sources. Feature request `#118 `_. * Added :func:`write_datetime` support for datetime.timedelta. Feature request `#128 `_. Release 0.5.6 - July 22 2014 ---------------------------- * Fix for spurious exception message when :func:`close()` isn't used. Issue `#131 `_. * Fix for formula string values that look like numbers. Issue `#122 `_. * Clarify :func:`print_area()` documentation for complete row/column ranges. Issue `#139 `_. * Fix for unicode strings in data validation lists. Issue `#135 `_. Release 0.5.5 - May 6 2014 -------------------------- * Fix for incorrect chart offsets in :func:`insert_chart()` and :func:`set_size()`. Release 0.5.4 - May 4 2014 -------------------------- * Added image positioning option to :func:`insert_image` to control how images are moved in relation to surrounding cells. Feature request `#117 `_. * Fix for chart ``error_bar`` exceptions. Issue `#115 `_. * Added clearer reporting of nested exceptions in ``write()`` methods. Issue `#108 `_. * Added support for ``inside_base`` data label position in charts. Release 0.5.3 - February 20 2014 -------------------------------- * Added checks and warnings for data validation limits. Issue `#89 `_. * Added option to add hyperlinks to images. Thanks to Paul Tax. * Added Python 3 Http server example. Thanks to Krystian Rosinski. * Added :func:`set_calc_mode` method to control automatic calculation of formulas when worksheet is opened. Thanks to Chris Tompkinson. * Added :func:`use_zip64` method to allow ZIP64 extensions when writing very large files. * Fix to handle '0' and other number like strings as number formats. Issue `#103 `_. * Fix for missing images in ``in_memory`` mode. Issue `#102 `_. Release 0.5.2 - December 31 2013 -------------------------------- * Added date axis handling to charts. See :ref:`ex_chart_date_axis`. Feature request `#73 `_. * Added support for non-contiguous chart ranges. Feature request `#44 `_. * Fix for low byte and control characters in strings. Issue `#86 `_. * Fix for chart titles with exclamation mark. Issue `#83 `_. * Fix to remove duplicate :func:`set_column` entries. Issue `#82 `_. Release 0.5.1 - December 2 2013 ------------------------------- * Added interval unit option for category axes. Feature request `#69 `_. * Fix for axis name font rotation. * Fix for several minor issues with Pie chart legends. Release 0.5.0 - November 17 2013 -------------------------------- * Added :ref:`Chartsheets ` to allow single worksheet charts. Feature request `#10 `_. Release 0.4.9 - November 17 2013 -------------------------------- * Added :ref:`chart object positioning and sizing ` to allow positioning of plotarea, legend, title and axis names. Feature request `#66 `_. * Added :func:`set_title()` ``none`` option to turn off automatic titles. * Improved :func:`define_name()` name validation. * Fix to prevent modification of user parameters in :func:`conditional_format()`. Release 0.4.8 - November 13 2013 -------------------------------- * Added ``in_memory`` :func:`Workbook` constructor option to allow XlsxWriter to work on Google App Engine. Feature request `#28 `_. Release 0.4.7 - November 9 2013 ------------------------------- * Added fix for markers on non-marker scatter charts. Issue `#62 `_. * Added custom error bar option. Thanks to input from Alex Birmingham. * Changed Html docs to Bootstrap theme. * Added :ref:`ex_merge_rich`. Release 0.4.6 - October 23 2013 ------------------------------- * Added font formatting to chart legends. Release 0.4.5 - October 21 2013 ------------------------------- * Added ``position_axis`` chart axis option. * Added optional list handling for chart names. Release 0.4.4 - October 16 2013 ------------------------------- * Documented use of :ref:`cell utility ` functions. * Fix for tables added in non-sequential order. Closes `#51 `_ reported by calfzhou. Release 0.4.3 - September 12 2013 --------------------------------- * Fix for comments overlying columns with non-default width. Issue `#45 `_. Release 0.4.2 - August 30 2013 ------------------------------ * Added a default blue underline hyperlink format for :func:`write_url()`. * Added :func:`Workbook` constructor options ``strings_to_formulas`` and ``strings_to_urls`` to override default conversion of strings in write(). Release 0.4.1 - August 28 2013 ------------------------------ * Fix for charts and images that cross rows and columns that are hidden or formatted but which don't have size changes. Issue `#42 `_ reported by Kristian Stobbe. Release 0.4.0 - August 26 2013 ------------------------------ * Added more generic support for JPEG files. Issue `#40 `_ reported by Simon Breuss. * Fix for harmless Python 3 installation warning. Issue `#41 `_ reported by James Reeves. Release 0.3.9 - August 24 2013 ------------------------------ * Added fix for minor issue with :func:`insert_image` for images that extend over several cells. * Added fix to ensure formula calculation on load regardless of Excel version. Release 0.3.8 - August 23 2013 ------------------------------ * Added handling for Decimal(), Fraction() and other float types to the :func:`write()` function. * Added Python 2.5 and Jython support. Thanks to Jonas Diemer for the patch. Release 0.3.7 - August 16 2013 ------------------------------ * Added :func:`write_boolean()` function to write Excel boolean values. Feature request `#37 `_. Also added explicit handling of Python bool values to the :func:`write()` function. * Changed :func:`Workbook` constructor option ``strings_to_numbers`` default option to False so that there is no implicit conversion of numbers in strings to numbers. The previous behavior can be obtained by setting the constructor option to True. **Note** This is a backward incompatibility. Release 0.3.6 - July 26 2013 ----------------------------- * Simplified import based on a suggestion from John Yeung. Feature request `#26 `_. * Fix for NAN/INF converted to invalid numbers in write(). Issue `#30 `_. * Added :func:`Workbook` constructor option ``strings_to_numbers`` to override default conversion of number strings to numbers in write(). * Added :func:`Workbook` constructor option ``default_date_format`` to allow a default date format string to be set. Feature request `#5 `_. Release 0.3.5 - June 28 2013 ----------------------------- * Reverted back to using codecs for file encoding (versions <= 0.3.1) to avoid numerous UTF-8 issues in Python2/3. Release 0.3.4 - June 27 2013 ----------------------------- * Added Chart line smoothing option. Thanks to Dieter Vandenbussche. * Added Http Server example (:ref:`ex_http_server`). Thanks to Alexander Afanasiev. * Fixed inaccurate column width calculation. Closes `#27 `_. Thanks to John Yeung. * Added chart axis font rotation. Release 0.3.3 - June 10 2013 ----------------------------- * Minor packaging fixes `#14 `_ and `#19 `_. * Fixed explicit UTF-8 file encoding for Python 3. PR from Alexandr Shadchin, `#15 `_. * Fixed invalid string formatting resulted in misleading stack trace. PR from Andrei Korostelev, `#21 `_. Release 0.3.2 - May 1 2013 ----------------------------- * Speed optimizations. The module is now 10-15% faster on average. Release 0.3.1 - April 27 2013 ----------------------------- * Added chart support. See the :ref:`chart_class`, :ref:`working_with_charts` and :ref:`chart_examples`. Release 0.3.0 - April 7 2013 ----------------------------- * Added worksheet sparklines. See :ref:`sparklines`, :ref:`ex_sparklines1` and :ref:`ex_sparklines2` Release 0.2.9 - April 7 2013 ----------------------------- * Added worksheet tables. See :ref:`tables` and :ref:`ex_tables`. * Tested with the new Python stable releases 2.7.4 and 3.3.1. All tests now pass in the following versions: * Python 2.6 * Python 2.7.2 * Python 2.7.3 * Python 2.7.4 * Python 3.1 * Python 3.2 * Python 3.3.0 * Python 3.3.1 * There are now over 700 unit tests including more than 170 tests that compare against the output of Excel. Release 0.2.8 - April 4 2013 ----------------------------- * Added worksheet outlines and grouping. See :ref:`outlines`. Release 0.2.7 - April 3 2013 ----------------------------- * Added :func:`set_default_row` method. See :ref:`ex_hide_row_col`. * Added hide_row_col.py, hide_sheet.py and text_indent.py examples. Release 0.2.6 - April 1 2013 ----------------------------- * Added :func:`freeze_panes` and :func:`split_panes` methods. See :ref:`ex_panes` . * Added :func:`set_selection` method to select worksheet cell or range of cells. Release 0.2.5 - April 1 2013 ----------------------------- * Added additional :func:`Workbook` parameters ``'tmpdir'`` and ``'date_1904'``. Release 0.2.4 - March 31 2013 ----------------------------- * Added :func:`Workbook` ``'constant_memory'`` constructor property to minimize memory usage when writing large files. See :ref:`memory_perf` for more details. * Fixed bug with handling of UTF-8 strings in worksheet names (and probably some other places as well). Reported by Josh English. * Fixed bug where temporary directory used to create xlsx files wasn't cleaned up after program close. Release 0.2.3 - March 27 2013 ----------------------------- * Fixed bug that was killing performance for medium sized files. The module is now 10x faster than previous versions. Reported by John Yeung. Release 0.2.2 - March 27 2013 ----------------------------- * Added worksheet data validation options. See the :func:`data_validation` method, :ref:`working_with_data_validation` and :ref:`ex_data_valid`. * There are now over 600 unit tests including more than 130 tests that compare against the output of Excel. Release 0.2.1 - March 25 2013 ----------------------------- * Added support for datetime.datetime, datetime.date and datetime.time to the :func:`write_datetime` method. GitHub issue `#3 `_. Thanks to Eduardo (eazb) and Josh English for the prompt. Release 0.2.0 - March 24 2013 ----------------------------- * Added conditional formatting. See the :func:`conditional_format` method, :ref:`working_with_conditional_formats` and :ref:`ex_cond_format`. Release 0.1.9 - March 19 2013 ----------------------------- * Added Python 2.6 support. All tests now pass in the following versions: * Python 2.6 * Python 2.7.2 * Python 2.7.3 * Python 3.1 * Python 3.2 * Python 3.3.0 Release 0.1.8 - March 18 2013 ----------------------------- * Fixed Python 3 support. Release 0.1.7 - March 18 2013 ----------------------------- * Added the option to write cell comments to a worksheet. See :func:`write_comment` and :ref:`cell_comments`. Release 0.1.6 - March 17 2013 ----------------------------- * Added :func:`insert_image` worksheet method to support inserting PNG and JPEG images into a worksheet. See also the example program :ref:`ex_insert_image`. * There are now over 500 unit tests including more than 100 tests that compare against the output of Excel. Release 0.1.5 - March 10 2013 ----------------------------- * Added the :func:`write_rich_string` worksheet method to allow writing of text with multiple formats to a cell. Also added example program: :ref:`ex_rich_strings`. * Added the :func:`hide` worksheet method to hide worksheets. * Added the :func:`set_first_sheet()` worksheet method. Release 0.1.4 - March 8 2013 ---------------------------- * Added the :func:`protect` worksheet method to allow protection of cells from editing. Also added example program: :ref:`ex_protection`. Release 0.1.3 - March 7 2013 ---------------------------- * Added worksheet methods: * :func:`set_zoom` for setting worksheet zoom levels. * :func:`right_to_left` for middle eastern versions of Excel. * :func:`hide_zero` for hiding zero values in cells. * :func:`set_tab_color` for setting the worksheet tab color. Release 0.1.2 - March 6 2013 ---------------------------- * Added autofilters. See :ref:`working_with_autofilters` for more details. * Added the :func:`write_row` and :func:`write_column` worksheet methods. Release 0.1.1 - March 3 2013 ---------------------------- * Added the :func:`write_url` worksheet method for writing hyperlinks to a worksheet. Release 0.1.0 - February 28 2013 -------------------------------- * Added the :func:`set_properties` workbook method for setting document properties. * Added several new examples programs with documentation. The examples now include: * array_formula.py * cell_indentation.py * datetimes.py * defined_name.py * demo.py * doc_properties.py * headers_footers.py * hello_world.py * merge1.py * tutorial1.py * tutorial2.py * tutorial3.py * unicode_polish_utf8.py * unicode_shift_jis.py Release 0.0.9 - February 27 2013 -------------------------------- * Added the :func:`define_name` method to create defined names and ranges in a workbook or worksheet. * Added the :func:`worksheets` method as an accessor for the worksheets in a workbook. Release 0.0.8 - February 26 2013 -------------------------------- * Added the :func:`merge_range` method to merge worksheet cells. Release 0.0.7 - February 25 2013 -------------------------------- * Added final page setup methods to complete the page setup section. * :func:`print_area` * :func:`fit_to_pages` * :func:`set_start_page` * :func:`set_print_scale` * :func:`set_h_pagebreaks` * :func:`set_v_pagebreaks` Release 0.0.6 - February 22 2013 -------------------------------- * Added page setup method. * :func:`print_row_col_headers` Release 0.0.5 - February 21 2013 -------------------------------- * Added page setup methods. * :func:`repeat_rows` * :func:`repeat_columns` Release 0.0.4 - February 20 2013 -------------------------------- * Added Python 3 support with help from John Evans. Tested with: * Python-2.7.2 * Python-2.7.3 * Python-3.2 * Python-3.3.0 * Added page setup methods. * :func:`center_horizontally` * :func:`center_vertically` * :func:`set_header` * :func:`set_footer` * :func:`hide_gridlines` Release 0.0.3 - February 19 2013 -------------------------------- * Added page setup method. * :func:`set_margins` Release 0.0.2 - February 18 2013 -------------------------------- * Added page setup methods. * :func:`set_landscape` * :func:`set_portrait` * :func:`set_page_view` * :func:`set_paper` * :func:`print_across` Release 0.0.1 - February 17 2013 -------------------------------- * First public release. XlsxWriter-1.1.2/docs/0000755000076500000240000000000013362632140014617 5ustar Johnstaff00000000000000XlsxWriter-1.1.2/docs/_static/0000755000076500000240000000000013362632140016245 5ustar Johnstaff00000000000000XlsxWriter-1.1.2/docs/_static/basic.css0000644000076500000240000002051213123763617020051 0ustar Johnstaff00000000000000/* * basic.css * ~~~~~~~~~ * * Sphinx stylesheet -- basic theme. * * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ /* -- main layout ----------------------------------------------------------- */ div.clearer { clear: both; } /* -- relbar ---------------------------------------------------------------- */ div.related { width: 100%; font-size: 90%; } div.related h3 { display: none; } div.related ul { margin: 0; padding: 0 0 0 10px; list-style: none; } div.related li { display: inline; } div.related li.right { float: right; margin-right: 5px; } /* -- sidebar --------------------------------------------------------------- */ div.sphinxsidebarwrapper { padding: 10px 5px 0 10px; } div.sphinxsidebar { float: left; width: 230px; margin-left: -100%; font-size: 90%; } div.sphinxsidebar ul { list-style: none; } div.sphinxsidebar ul ul, div.sphinxsidebar ul.want-points { margin-left: 20px; list-style: square; } div.sphinxsidebar ul ul { margin-top: 0; margin-bottom: 0; } div.sphinxsidebar form { margin-top: 10px; } div.sphinxsidebar input { border: 1px solid #98dbcc; font-family: sans-serif; font-size: 1em; } div.sphinxsidebar #searchbox input[type="text"] { width: 170px; } div.sphinxsidebar #searchbox input[type="submit"] { width: 30px; } img { border: 0; } /* -- search page ----------------------------------------------------------- */ ul.search { margin: 10px 0 0 20px; padding: 0; } ul.search li { padding: 5px 0 5px 20px; background-image: url(file.png); background-repeat: no-repeat; background-position: 0 7px; } ul.search li a { font-weight: bold; } ul.search li div.context { color: #888; margin: 2px 0 0 30px; text-align: left; } ul.keywordmatches li.goodmatch a { font-weight: bold; } /* -- index page ------------------------------------------------------------ */ table.contentstable { width: 90%; } table.contentstable p.biglink { line-height: 150%; } a.biglink { font-size: 1.3em; } span.linkdescr { font-style: italic; padding-top: 5px; font-size: 90%; } /* -- general index --------------------------------------------------------- */ table.indextable { width: 100%; } table.indextable td { text-align: left; vertical-align: top; } table.indextable dl, table.indextable dd { margin-top: 0; margin-bottom: 0; } table.indextable tr.pcap { height: 10px; } table.indextable tr.cap { margin-top: 10px; background-color: #f2f2f2; } img.toggler { margin-right: 3px; margin-top: 3px; cursor: pointer; } div.modindex-jumpbox { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; margin: 1em 0 1em 0; padding: 0.4em; } div.genindex-jumpbox { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; margin: 1em 0 1em 0; padding: 0.4em; } /* -- general body styles --------------------------------------------------- */ a.headerlink { visibility: hidden; } h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, h4:hover > a.headerlink, h5:hover > a.headerlink, h6:hover > a.headerlink, dt:hover > a.headerlink { visibility: visible; } div.body p.caption { text-align: inherit; } div.body td { text-align: left; } .field-list ul { padding-left: 1em; } .first { margin-top: 0 !important; } p.rubric { margin-top: 30px; font-weight: bold; } img.align-left, .figure.align-left, object.align-left { clear: left; float: left; margin-right: 1em; } img.align-right, .figure.align-right, object.align-right { clear: right; float: right; margin-left: 1em; } img.align-center, .figure.align-center, object.align-center { display: block; margin-left: auto; margin-right: auto; } .align-left { text-align: left; } .align-center { text-align: center; } .align-right { text-align: right; } /* -- sidebars -------------------------------------------------------------- */ div.sidebar { margin: 0 0 0.5em 1em; border: 1px solid #ddb; padding: 7px 7px 0 7px; background-color: #ffe; width: 40%; float: right; } p.sidebar-title { font-weight: bold; } /* -- topics ---------------------------------------------------------------- */ div.topic { border: 1px solid #ccc; padding: 7px 7px 0 7px; margin: 10px 0 10px 0; } p.topic-title { font-size: 1.1em; font-weight: bold; margin-top: 10px; } /* -- admonitions ----------------------------------------------------------- */ div.admonition { margin-top: 10px; margin-bottom: 10px; padding: 7px; } div.admonition dt { font-weight: bold; } div.admonition dl { margin-bottom: 0; } p.admonition-title { margin: 0px 10px 5px 0px; font-weight: bold; } div.body p.centered { text-align: center; margin-top: 25px; } /* -- tables ---------------------------------------------------------------- */ table.docutils { margin-left: 30px; border: 0; border-collapse: collapse; } table.docutils td, table.docutils th { padding: 1px 8px 1px 5px; border-top: 1px solid #aaa; border-left: 1px solid #aaa; border-right: 1px solid #aaa; border-bottom: 1px solid #aaa; } table.field-list td, table.field-list th { border: 0 !important; } table.footnote td, table.footnote th { border: 0 !important; } th { text-align: left; padding-right: 5px; } table.citation { border-left: solid 1px gray; margin-left: 1px; } table.citation td { border-bottom: none; } /* -- other body styles ----------------------------------------------------- */ ol.arabic { list-style: decimal; } ol.loweralpha { list-style: lower-alpha; } ol.upperalpha { list-style: upper-alpha; } ol.lowerroman { list-style: lower-roman; } ol.upperroman { list-style: upper-roman; } dl { margin-bottom: 30px; } dd p { margin-top: 0px; } dd ul, dd table { margin-bottom: 10px; } dd { margin-top: 3px; margin-bottom: 10px; margin-left: 30px; } dt:target, .highlighted { background-color: #fbe54e; } dl.glossary dt { font-weight: bold; font-size: 1.1em; } .field-list ul { margin: 0; padding-left: 1em; } .field-list p { margin: 0; } .refcount { color: #060; } .optional { font-size: 1.3em; } .versionmodified { font-style: italic; } .system-message { background-color: #fda; padding: 5px; border: 3px solid red; } .footnote:target { background-color: #ffa; } .line-block { display: block; margin-top: 1em; margin-bottom: 1em; } .line-block .line-block { margin-top: 0; margin-bottom: 0; margin-left: 1.5em; } .guilabel, .menuselection { font-family: sans-serif; } .accelerator { text-decoration: underline; } .classifier { font-style: oblique; } abbr, acronym { border-bottom: dotted 1px; cursor: help; } /* -- code displays --------------------------------------------------------- */ pre { overflow: auto; overflow-y: hidden; /* fixes display issues on Chrome browsers */ } td.linenos pre { padding: 5px 0px; border: 0; background-color: transparent; color: #aaa; } table.highlighttable { margin-left: 0.5em; } table.highlighttable td { padding: 0 0.5em 0 0.5em; } tt.descname { background-color: transparent; font-weight: bold; font-size: 1.2em; } tt.descclassname { background-color: transparent; } tt.xref, a tt { background-color: transparent; font-weight: bold; } h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { background-color: transparent; } .viewcode-link { float: right; } .viewcode-back { float: right; font-family: sans-serif; } div.viewcode-block:target { margin: -1px -10px; padding: 0 10px; } /* -- math display ---------------------------------------------------------- */ img.math { vertical-align: middle; } div.body div.math p { text-align: center; } span.eqno { float: right; } /* -- printout stylesheet --------------------------------------------------- */ @media print { div.document, div.documentwrapper, div.bodywrapper { margin: 0 !important; width: 100%; } div.sphinxsidebar, div.related, div.footer, #top-link { display: none; } }XlsxWriter-1.1.2/docs/_static/default.css0000644000076500000240000001011513123763617020412 0ustar Johnstaff00000000000000/* * default.css_t * ~~~~~~~~~~~~~ * * Sphinx stylesheet -- default theme. * * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif; font-size: 100%; background-color: #FFFFFF; color: #000; margin: 0; padding: 0; min-width: 800px; } div.document { background-color: #F2F2F2; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 280px; } div.body { background-color: #ffffff; color: #000000; padding: 0 20px 30px 20px; } div.footer { color: #9CB640; width: 100%; padding: 9px 0 9px 0; text-align: center; font-size: 75%; } div.footer a { color: #9CB640; text-decoration: underline; } div.related { background-color: #9CB640; line-height: 80px; color: #ffffff; } div.related a { color: #ffffff; } div.sphinxsidebar { } div.sphinxsidebar h3 { font-family: 'Trebuchet MS', sans-serif; color: #9CB640; font-size: 1.4em; font-weight: normal; margin: 0; padding: 0; } div.sphinxsidebar h3 a { color: #ffffff; } div.sphinxsidebar h4 { font-family: 'Trebuchet MS', sans-serif; color: #9CB640; font-size: 1.3em; font-weight: normal; margin: 5px 0 0 0; padding: 0; } div.sphinxsidebar p { color: #ffffff; } div.sphinxsidebar p.topless { margin: 5px 10px 10px 10px; } div.sphinxsidebar ul { margin: 10px; padding: 0; color: #ffffff; } div.sphinxsidebar a { color: #9CB640; } div.sphinxsidebar input { border: 1px solid #9CB640; font-family: sans-serif; font-size: 1em; } /* -- hyperlink styles ------------------------------------------------------ */ a { color: #9CB640; text-decoration: none; } a:visited { color: #9CB640; text-decoration: none; } a:hover { text-decoration: underline; } /* -- body styles ----------------------------------------------------------- */ div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Trebuchet MS', sans-serif; background-color: #f2f2f2; font-weight: normal; color: #9CB640; border-bottom: 1px solid #ccc; margin: 20px -20px 10px -20px; padding: 3px 0 3px 10px; } div.body h1 { margin-top: 0; font-size: 200%; padding: 20px;} div.body h2 { font-size: 160%; } div.body h3 { font-size: 140%; } div.body h4 { font-size: 120%; } div.body h5 { font-size: 110%; } div.body h6 { font-size: 100%; } a.headerlink { color: #c60f0f; font-size: 0.8em; padding: 0 4px 0 4px; text-decoration: none; } a.headerlink:hover { background-color: #c60f0f; color: white; } div.body p, div.body dd, div.body li { text-align: justify; line-height: 130%; } div.admonition p.admonition-title + p { display: inline; } div.admonition p { margin-bottom: 5px; } div.admonition pre { margin-bottom: 5px; } div.admonition ul, div.admonition ol { margin-bottom: 5px; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } div.warning { background-color: #ffe4e4; border: 1px solid #f66; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre { margin-left: 30px; width : 640px; padding: 15px; color: #333333; line-height: 120%; border: 1px solid #ac9; } cite, code, tt { font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.95em; letter-spacing: 0.01em; } th { background-color: #f2f2f2; } .warning tt { background: #efc2c2; } .note tt { background: #d6d6d6; } .viewcode-back { font-family: sans-serif; } div.viewcode-block:target { background-color: #f4debf; border-top: 1px solid #ac9; border-bottom: 1px solid #ac9; }XlsxWriter-1.1.2/docs/_static/hello01.png0000644000076500000240000017473613123763617020252 0ustar Johnstaff00000000000000PNG  IHDR>tEiCCPICC ProfileX YwTO֭̐s9sÐC((AQ@$#H  (( PT e]w_w^߮[]R:""c|n|gz")&Z} fn]oĐl~1_hr,H%.]aD0v\hosQ (/vxR\$HH`C &&s""v#D^DbIF!1"]x3UL|cH";fG!f}m#8qaNqX?毝cg]b#㿘eǏwس'Zw"F{kgd^_&{H[O,qbbL&٣m C@+@؀FE'ó>@,$#ŧ 'v]_l)z]@m  R9O} -`1  z܀7 `  qpp4t^p ` <`* B#B$A:1d Cn/EBqP tʃ rj.A-4=e @"LnBGX ^ >D" q QC\@t"n!5$@R"YHiitG"4d.YlA  bDQ( EBCQT'jZEBSВh N@gKWwЏѯ1 F!`0dL> ӊÌc1kX,+`Xl6 {{;}p&8w\$.WkM(()4(l()SPxHb Ok |)?JII)@NiGByrrrJʐʓ*U=US"zԱǨoS?AH#CcFONSAI3A󑖂VV֛6! !. =#< }}>}=%,1?C HFAFCFAƳw_3aD̘B.22230+1;3'2W0_ggALlr氶Nq岵=fdc7fc?!aǑQqcSę9ÅJ:5µm]}{G'2/#oo1Mw||||||\8ZQ-Q'LVgxA5@b~U!^!+fa a5`Sƒ""."EDDDDDEĨtՉMcī$%*!URh)uH:'T 2,222]2edeOS ;+7+ o.)#EABP0HhxU񳒤RRҴ2a~**deU!U_J'jLjjjChut^ XvOҚaMKZZZgڵ:|::uuuu/տ@΀lp`P0հidjk4j`d\nD$ȤdT4ٴ&XNqVUS,,,-^ZJX-{VVEVs֑]6̦晭>kv;[ 7)>M  gĜi=]\ ]]e]S]8Bܮcݝϻy{x9%uϛ;ѧM!*VISzo uKWB CC>BkBlv][#pݑ aQ`z! S.0A=YYjΦ&g?9yHȑŜ_JI-=s,hJAqS'tO4&.Yu;s^RI)S󥖥W˄ʎm?0h̩\򯚨֫nɫ<rzִN L7gS;x| M\M͈ .]"RҚ]4nߡrYr+PծnWǻͻ{4{\V[qz ;7nE ?{;CwM9={O^}]*Ý##W(?22P1q=;i69'KOß~ٚ=0}F9/ZU/-tx9HZ|*7oJm\RX]6Y{[+?T~xާUןɟweZM[߷shPt|.)ůh"{/ψ@@f[`xc$a_|D 1XV\E.ޟҀjzf>!1ɗىŊ՘̀ݐÀSmG }%vU<_GRQ )HL܊Mb%meZvL5{u~O9ZNty+P574711I0k0Oܴ>cdlbnqsK z5x"t4CdB##)}dX8ckړoLJzAsP2Kaa#~9qyiG+.<~DSa{Qwq>-{^M廪_k6kQu g*3=Vڐx[[.ѶKuX]R5}uv57oڸ2 n{]LJ=@r-SF&&+7 Efjxfkk~uqV1N\ο\^rs/}|;*I%2SBB#}"\",9bbbŕ7'\OۿIeKKW;@p?HJ;]r+7ǎN/X:zG(Ɲ-a9U*P&R.^!U)S%WPrZִLs+[Ǜ^6jaioӻўq++] WUz2lM>[. O tޙe}ထUgơ Gɒ#OjO#ff? [;y+-?u2-Fіد=-iɇ@@+W1g~8):E/Mm]$#&<}Nsl%95//%KpFC}2@I%) 5"}J$+')7 _(ԭbʠ:V!QC+^[]{[O7[JUAa1Ii،lڼ"R^ l\ll8;Z; ;m;?ritMwsrwp g΅R,s V8ڔL;8XҀ~&݂BVwb{'>)qMZ*MSFKG)^ bRrJjZq,8@mg<"ٲ͒^1é+{Gwo%8n_^#%26&8V)V~A} fO_׏?}A2(p% D@/g".!Hd2r%EFFˠIێ6)"(^)(SS-RG@4'iehӅ_fpg~2_deeag+``XlJ{/K@^'(tKNh4JC+r9 J*VfjuAHK[[QGLGYo5SИ2x$5,,-} ml8999wYG7_3HtTHVp툚(dtȾq,(#Si`Y|ҿe7ug6gmhjkJvݩcJ]Տ=G{Eܴ@)Tzp?tCՉIOfgI_= <4|v>wacե7[+A?~\G *'[hVϸ_lzvv TT2ӏwv-牝g`c?]2Wd/JBVuriTXtXML:com.adobe.xmp 574 454 7@IDATx]T>la+ރJEj{&bl%j%cDEXQ  (.˲|7{ٙ١ oϹ{mg{Emm|=/x n_hF|={<>8={>~C}={?ǀ|=x|{|?|={}~tP|={ wAl=BSl-|={`_o^ @ ߟ/}S|={`45}2={}105ovv-=+;~ 3_| h|gGۏD={@&!c(!mb l|=lDhADDD*jVr||8 Hm-@{DೃhhtBC{b⁰pu•=!ڭpXXc ~|DJfv'OSHS|Prx|B(xvM5oOv'FO@$ك FˎO={}M ^ٺwh | zpz0:̅+Rk|T۠pmy~0^0:m6vyF*H L&µaq|=@ &BJx8*s@4iF,4gzty[6^(> &Bҭλu6ƩE/R+o}g'<^ amtO0~0Kd#i|=gz X՚`am|@/:iMnlh>1z܁vg5ECߦ8m{k 6G)>v_xKצٸmӦ8eB{nA- `<:c2^`4wm젠lBަE-1H9ҚS·|={ 47>!ft#Ew.#O~vUP7't˃r,xH'iFǖst?{@py#F:!64;ot62̻=2 qiv;Oho<7FO|={hyw˓Gŷymhwmq0dBv- 8{ѠFyB6DY7yFu6fۧ.hHڸaZ)n7>{no`8uq#ߦ' mr6$N9켍;܆|GAy+'HЃ;ųiӆMqR3iz+ M=!Q'!N7={끦6t7-M|ziAAXVVvƍg̘7߼He?Ng;3_yq{'r6=G{פ I gq'=eHB\||!:?t8h )˷@6XBЁWhI> ̓|A])v)c`2: oM9@Aϫ#evY.d!s5 4ःupt/..^8uԇ?W[z pwE# 47GYJ1 |y 6 iO[nWrIGZy]ݻw Lc |E9m{ӵ77Aچ iaٔ8G.lΠQ8#n::m|ڴ֥dr -˶,M=X.l0"r%n۲m:YDچ ia;):pґgЅ ٖx l[Ҷ6d.}?﬷!!ǓCN?uֽ0rȇoFI%@fӁJ2)[.XK[ $pWMnܝH'nC[KVz=112 :' ą8s[PSy"pڢИ@C]:ٸ͇=e e O[6Ȁul,2=ʃi4$0e^2Y>e)2_yTUWIM-+XTm Im&2IRRR`o{ eؗ9KSNߟ3/^c 4ׯ9G"ݑvC3sef $&0O-Gɖa0xݼ]ȲH0ag:'nC[ƦueffIB+Ig9 MҐ'>p{A Ƀh"B:삇D=X6m2vݨ"y 8dm|>eȇ7n= fLa!& 4䉓6FNu@C\L9@ + E]QZ)%˥eytA7Sm2v(dIOy!:덼[6]&ذpeiy胆DIGI#n'q?&Chv9}pl:p$z[nx MM;أō3OFg )hx\yy9k2&MΦMdwիM/uO *5Fuj=`h4{r!%˿#?-NݱW0QҶ}WÁNt|ffd!,̍Zս ;X2F:ܠ2X{9@^l_EE^$Q-ń#|e@<&yП/˶IwӦA@ƶ}llB9};o,,@eRCU(Dˊ\ʌ,+*$5=]ڤ[`( ʪ(Ki2WC'v.^dj8_tFqJtç/+1Vs#Ÿs~칇<} )<HS'V"\4/E$4 #Н;r EzC1vD8$S !NeUߡ5qpA }'^{]6|hN]u䋅 %E2쳥ǁ=`@q뙯ʶ_JrA] /eÊ2_J 6:ź" eosHԯF6(oY"~/'u=bc 'oםrv" m"<',vX&ʠYmB8 hM?l[ZJOE )2R|Giڀ]v&`>2Eæo}V>y\9M_&_?|4Jyn]~ܕY ;Mփegj63O?.#_~>Щ+a'Yw==aL$ALu<ҝѡ/xSٰف. lX$i e؝!X,sQ뢿@\=rұsxI}%%ϓ̏gs}" *ܜ %l"lgWzreK#'Kr !ɖ˫Ͼ/_]nFXqg>{{4Y}\p2cc`SfVw̩6cz6ڏļ-G:h3 2hḦ́| p}>l ?s 䡇<呇, Ez,e]Bԇ,.#X 6֍qA2m' z'm5U M~yyyҺu]+-_NH2`@X#@o'uG>eelg dHTmK˖6P;xĩY\~>et;O:ez6X?Xgȑn~?1 &3X=$;˱46P >#:W(Ѡf>e y6tμ:吏H_a`_6S9k"kHUH|tK8`?y[e:[~͢e|Ϥj՗rIڂR%"!Q:?Py-uYN.B,urYu!_j޾ˆ$'}1'nV^Ly>H*BƲ`! rA{O]G9Mfm][eu@wNO3(8 #[ M 8' @Gb8h˥QWʣl7laò;,ztO[]O{۽ XVZ&_g)OķHIMM5"ԥ=/qzKǎS>O3}:2$- jkT05CRSu:࢏@ k22$^Yۆ]}E_LHmw?7('<V;Ƽ&gB8G8VDzq#}֬Yk!D#@[4m93lpd qM^-Y$ߩZX.ˋJdg_L9Q2 ]yLI% '73ٰHZZ%Iڟ$>ķL1+VCQAf%J?؁&!7uu֏yv:E~Z.m~zF PҔr2P{6I"-/北/ZFTb>+^mؿdnf5ଛIdreo5yߔ[^!z?$#x9n9ߟƑ>㜣_OY>]ڡ.6` 6x4ba<䁓hD5`589m;GEcA FeAG=G: y᠙Aȋb韚,i%e]{ߜc.੹$IyGWl=3$)$HWI\esUJBmAn,֧ةE\%ՅJ+Zګ,l0'"pLRG"osdY.2 - yA@.S˺PP8)eG] HyC:6pDb>mACa7Ȱ>JQ^xU >6` eHUU5ԾyU^YOnV9٦rY­ʢ?_|Nnh~a.ROx1(UhSj @.Փ/G^e/4u@ /L;[N(iկOJ癖,+IZ/qlEM'~"oӪBr 6a m`y~ Pb{Ag,l"B2'<ȱ\H}m |<ʃ `O:dPmDAgPGK B.8D!hEu /UMRsӥ}A:,mBZ [̯V4 `)9U Y固iM:'' 3>;[(xGmee1H߼MPfVNޮuo:9C gZ2Ӵx'>-K.w;=(>"?*9ȻLVde Jd{>/;탇!ePo1E W0O +ʑ ԃ}$@nQdPp$v2HG 6pA4Py蠮6NvCrAB<.ʁc=Ƀ]vBԳeA$\$|d3p eW3>8פ;m,U;X뭬bc*LCn"9%RQ'>|GOMiѢ?(EYZSQQ=h+>Q?2YdСhH2)9d9;`Dx~ޮˍag$a9J )6`LҠ? |H/AzciN|Rq2̷V>cO$OY3yS=tyU)~u+$9MnB|?2[^vگ3>AK?P>l.ݻmZ?,TU黉^,8H{}L <7?E?8 e)ruoo wliݖ |Y7N\)~s~_P篶CO|jRuyuZ:ʫ[bl^2]E~y}a)}\=q?={rߞN[X0QbbyqEhw&' 26G=BsD[] CŏW~K`/Y6!(835A2&r(<؁<ǺKȀ$TCr'ꅋe,dcOP&cm%ǎүv ?L꼧:9k, yWI-˖uY1}w鬏m]NJt*j߭['(J}Φuf.}{KJ6-vK'==M-ϑ՛L[K<;Ia >&Iɶ ]):"pO/Ǡ?37=qPB hK:xH{Ѝ`#qࣃ1C!ݸwVy'`It^fϞ= †M… %%ż>d( @8a) % ,۲eKC>APwu=nȣ/aQ :"z!WrY Ax & lAl{ 'H#OSuQRl$ r`cvϲ`8Au i2u vP:GC},I'd̘1Cfam{@|@ @OJ^&(u KPXO?/[u ȫmԉ~,HڏzKZӗB r ?;A8VN'Ǎ?h_qƝ|„wzJ6:A;Os O8u)) KLS<ڰmZp!ƃ汸a0c$I_qą dyrE G&dYE@!y,V@2@GYWȃCϮhAC}Xqd9,h+ |2H, : (m}gy'zŽQ`>> {%e ƺ P$6ϺQiӦߡ\dC_oxywFV1:Ŵn'}vF{zM7wm8^vv^w>/)Mk^jDutulB 4.LCH\aug69:@8H=[|PvGԣʅ_@::}\XHȳ͠l\Il+A.#G`Q\l7x؀[#ۉzh.(h_$"Oy؄.A|_|_"!](>Ebs! 0_Mcx]3 3@;1ϑrRߦ5onc7ƛd)oC҃AeeH/ͅ^` xb lQ|0υ&7r2:hX&xu 䡏rKpԍ>mcCvyB@,E!;ꌋuBY/ˇ,v;GX' E=` cΚFNpO5k k޺kw^B ';@= A!iش%M5fGh1-S(h`mԥS<'γ L{&PQl zEc$-[LI-7.c#,\;1@_l ljB 9C> aNC<ԕ C48E鏮eBVê@}zA!{u, u1C8h o] Fz%8mA`7m P_Sg#C 6nb~qͱT\wߜ},?ؔByҐ hv^F 6!sG2ÈWYviɳCy^n=v68 elZܛoilbCGȂbC 2^ <&إ.w<.آ>>i z.౞ c:ېCꊠuEzGy`倆D] Q9اgö0 <vYo,4z%>,4PuIJ9YzLB|!#qx>T9l65913P:Y 7{/$HfHwӜŨ6CH3 |"-0~رQGJYK`Oy YU62jRR^L+5)U@qIAOЏ џ9S=sEnhQ7sAߩ9 Vڦ8̻!y3Ob:; mz\QaB~giDM=-%8i;'} c4N>LxH b'}|"dm8R6wѨ6' e uQ<8:NcWq OBQ'c' h_7m,z*}+^{F8t }tJ2<9/锋 Q+JaC[Ʀ!OIkQ LE~hnR??Ӎz?qQz!s(~@jysO>:ם/jDmhV$[A dmHg#7 }(  'qAP<1Ё#XsOU_t| _8A.I/яbQ(g\ŎƽH-/)}t mhH#m^ !Fu6t/$ǥ 0x0[y/Z en78}F1a=eD.;]j ԭ^'.mh_'>쀟ɡ#7'3fI@&7HBIfWvV$%'IF M[ed#g;91K9ۥ}vD1b$]`3=y߷ߗ Ǎ:yJJJ%/7Of~p&usz3w~ :}H!,u d`|&wtfiF4iC6p$7$6ߦNr7ZygJ|uR#ގ$b~2婩2k+zu]cOzb,D|ߟ菳G5 CCl^TOV_Cz%=~k@<E6'~Ηs3è<9c (<`'JdyM_.ݺhXz땷e˦-zS+9GYQŽb_ [/ԣ,;,D"tr  džxEnQH|A'5Y7ʖ S~uR_GZv(WwR^GրI>A3{6g_(cƏpS)KLkP:./^$mT6e>|#oHYy,aM[MRn_B(?'+͑nV?iZJj&==h<# 79&\}JӀog6dnE.W2 6+b~lNnj=JrdժUtRp@ӿ6Wk?J,_r՝j?LyEM2"vL{ ^4u>%5ŨOm; ߨөljOH6t;زP,*aPp h6=;8wt]73qkVЃ B/)hƅ^fu/5y陗eW2ro!|${c9³cɥNΚ+,ASGn='eDYϠ j3E_6|;[sAt<_omۯKuh+W|j; 'HZz=v|dd]~z/w^L>89|bW93e#Y }Arpu"}h3.MҠ7jjeL_oٸEz){8x` /|d?vrɁEEnS6'^X71f܀ `jZIJ_J'˖,IF1B1H6o4o }@/@pBڮuýecoNBՙC,]q3y/sXx_7B=M;D.y՜ *9xҪs+0o~G3k.kpNp'ȤS^^ze3g'Q~$bY뚋ӥk-Ӎ{r =W3S䳏?#%?j=kt-k7>/jW~HQYwq˯[w'Wfxѱ 9k0W_CïM&|U#vh'RRhd['K.%7/W:w,ccs>#Y[mF[LeuÇq#Ϛ_ Ia?!^.w&`=V,Fn>.$68̇l(ҜGjK4:+Cl:4L8=։6WCG D'x2%0f02P_ln&[d/_4ʲJOf͗}dmʒu#ҹ1p|=ld_z/CL|pKm ї{)O.Y3>UruZh67yƛX(ɗǞyTN9]7ޖRW䴳Jt9L5n=fTľN}9>7[WV!Zot]*y? KE#Ξ'8L&=Acc5tݟRlHdmק&<|t5`?N.qezp)*-6eWiVR^I}δwi߾LC_;yk0A-568g>.o gly?vۃ(>9Cdaw>[p|gm++6AJUydi ㎖;긘aڐ&^p+ra >l /+/ACY=7餛.a:rN_TVb1=_N$FtmB:3\`)zG/U2q1CS`tO[%Nz[i- ٰncBt!F0?^*+d1GKdd kةq֙D0v:gl9^Z#-ky :ѤhLYh@,SʐN2ۗ6z6' 6 P֝c'H\UM ݎ;h=~5rHǜLk/ ^I~e>1?ac#Gȩ*#:vu[6䖛gY^yQx:R_Ɵ%''X$#^-kcO6s9Gu >j̑2#Q&@IDAT'W0| )Ї?q Y3'ikNVc2oBm=oTI'H|vmrr% ,4uBا_eѣM3q/qGCJ<#׾s{IHI|M֬YKѱSSzĺMNƬA_#}]w[-%ť?ѩ~ Lŧ9N ?tZIAi0=-ʕrHqS=zH/&)!:ͨ'ǻ$55U&NOd:(/FO~8-T^8/|Oe`" qʩ;Ma,\V7dE|BLo^zevwrY'K; n tTЗX J?!K-Ç :t'~rMIn^ 8P>7UnvxjE c1NzvTpx:Y8: rn<:`J3mRΙ疀{,o<32_/[uz3'(uk~L:X10f`fa`))w gsȓ}J8t'Yq~[6g;m(AREe8A^|l&g1 3*k+$8TͯZܗys _"y ҤUz+=5<^+˛7C8ן):Kz- p;w,qV(o 󫯽*֮s1'mߏ͸ҥs9 7PXZ cǎ7M3 ?i64pt9,8iv>6u']^ Bf!C$wjLn\HuߠpZ:|,jy~\ 9D-3[`R2z}wh30f3?f&#Ϣ̤n Wrl3W z}@.Ւ'&ʿx~^\z%UKwί-iq' 1zmʚUkdnGHO;׎^}ҾB ^ERs=^knYq#Fpo#(:%yplۃ?Vak~PrӏbJ~<2ϖMz{45m8x {믉9I>y{mڰY_uj bGpoỌ'_6x.@r˖IZ$G)g̑k/N;V?ryhqG63{,tQ=zWM?h|( JΦfڨ 1G|TOnrRz9o5`?ꛮu[RDFs |}2O>`Cg 0g]N㚀nHu)DlvRVQ&sNw^삭3Zcl"1J{¹5'oG֮]kLGe(s̓^+%Q{|8 2! < H'\mR|$Man ulG}5 H>h68.H mY|}cHB6uj9`'>^үK)od9'Fjr}@dO3C&K382nN2-ޝ_QuJTڮlhNLSNp,sjy:9uǚy ADiڞ&dj@K+Jdu?$kvet?Ez'Mʪ$>-9]Z'D/˗vWk|٭]w҇k$9paod "ٷK?}PvC;wrhK+J%).IJ+K%_ _"H</[o6,zg:HzROCގѡV՚E5QQU"NM'{zkNڒ%!e/n3fooۤeȚՒ ]v5^_Q]'9l dcJPQ ^ OҘUsR,|F랖.[SNZ׈mY  g^f6 ODZRI0K*%9!{N6{^_^%5źt,=.1!%~ ϛl+?%?\H?MRZq}܎^ݘ Fg9+EC1P󙩹Aƥʧ3g%)%IsEHYfɸ c ' ?2)"h@^Ȼin9Qz86u)9@'y|5x^^-<,0 eA| 'vH֋H"ڼ[86 Ɯ Q)51+q1ZC醉R(Y9^n,G;_5yO,#G_LjuMİ`o+*i/O}b YCԵb9ۨSo XFl6g 'mv5e @8a&/H? }"NC(?ܲNiò29͇}K내d vF*aӠ<6Z:jl^#/)ߘ!9afɓO})<0OP-](yCeSS/*I®nFbHp] c,ܶDp# nN nnb":rm7R<]0½i•Gl78> 6/ 9;(=yX#6!Ǡ:C <&/>y]‚ |`W> n9hA>e-Ax6z8dq>#ܙOJTWrwf^>p8vO؝jN>p#zifKӷ@\am4&vfʹ |`|/:hnW-*O^0gcPnX>v7O71kk7ryb _?q%''+Z5)MCvo+SұgKA7 D=[|uqKm64"Okm,ծe~grz8fΜ) ,0'|S&97}w ¯43ΐɓ'KJ&@AGñ .\]Hv,ӧT O+VI^~ow^^RvW|Pv?ق)pfIVO>csf߿58OSXz Ǔр^dpRKb],?R[O=v@ J4{&uyP=7Rz(h62be$Bցyʑ\yO)w!TyuV;'ɸAiTd=h!N~CH\@;٦zLoAN:{T%U3 u[LFfsƈ˗'|4D?yT6[^VfSp=e:4?8wУզq%0шd7bF@̀0@iφmpG[%,[>]Z<^s2rkcJLZl-rLJ'ܴisGɨQgϞQ?_" ]򡐍b߿ 6)YYY .]R$$I얖:'+:fx ==S!S&ѣG4H.2sK/$k}Hv?B0=[X`xʟ+e|9ya.JfNNZ92qidaT>usBTҾW{uKIr@ vt%dŽgʠ/ W͑vn(o #YSSwON9w-;Xf,^+}fc~e{E~7rު[sL|rt;d 12s'"Mk)v5(E\z<)^xy|#S./͕a'HF7%Ì>ȭ>ȤAυ$|@HB>Xt<--|'H\ Cf: n=y^"dHذY]fyHF9#W)\`jǥ{ʥ2x`]E2W~yτvIM4{Һe3yx&wb?o߾z(܊1#СCeȑү_?ܹ ϟ/.F UZZS#| Dڴn?=kvWuuymtLC‰vw}W^K/T Spyf'((͋T`2s@5p@y Jqǡ4sqgD.Y=}~us^C-k6ebBWh;#r5h =:GeCM}# (d~RfRc.`Ǯg7mrnuMMfnq2Sжw%y#ޙVQCv "؈TŢԺURV_[WZSYT֥E%5,""5B _2&&{sw~w393s&OlMkVqȑ#-rmHO k[.g_`/l4&' A:Td.p{Ftݻ,-u^w-X~aKh"kү}q|ƶ!A8ij6ׄY靺:c^.Wk.ls{Ĥ+p7"l?kX 1іuQ}>jAzK߫͢r௴~mM;9oQcu~Vwy8=TǿMUMc/wg,u=uGvQF9Brsr; BpP_uA<}\aܡX(Ê0L& }roU̺t$-~$H|A`>u̝Puͷm沭wycWt6qd-Ubvֶƞrʚur\j% \Ԗ="Ҝ\Vϙg_;f̘n}^zE '̵fdJ?>v~;'`OpQfP+޻wo2y2~|)pM2To7KU 20|y:Y9!@|6p,).3*KyiVk~0o0 $qcc^|SM~vGF~zg…XdI3+@=Im|U/v΄x&&\ٗ1&N:9sI]JB#.o 7njsR _7w %e0ֶ\2rKlv Ym_'2dӺL6/ҧ%rq7+䓭0ܹ'Z=} 3":c1eÖ2Ϡ-`օq_G#嬭d|%0kԂe•Ђ N0+y9\2a.Tuԩa5!'LR1NϣWT#AV"'=+fᶶ~pKaHF@zXTi(V'Gtg3G"Dܶ$mKD_bH3-Ӵc-;,e.1U24Yg#/)Yg\~] ,TTnחw#iY,ΏW1c b[gпԦM)Eҹ93Kj8*J~sFt;ֹ-[[>[n;[2;Z-2eF͢23gf:]y,ATEO;7o3`bZ}BY3}#%?Z9ux4=Ht cp}ɵZ͗S_D.\?"s[eQt%g,̧!Q2Mu:*wvHZG7 c{,3JhW3˳jh3%3=um%ږ6%%xugn9/++f´Pb_)?tCig٘b⎻ qP<a%$>T'NsA];7*ةāNGDTy՞t| @[ȜA4vs;`r#Ym$3tƞFWyKBe[̭VT'{u$jp/–bf$2<-"2c4;z[u( e]fyumMj;?ס}Xo[nh1TYuKϴ]4[ZtxS]hg}?;]UWe{Kp|nuDANQ mk.]8ksˬ^GO~yН}\[9s0mg1ä(m tLlt9 vоUcoO/P=Ns/}bߞP_lg{,`Y_@Wž=@8Y }{:@}ql%1ifU9/r뭬$Lڶ}fL+߯(N^\ N~(YԴ< YH~S9lJ3_KةmswԺo5:CG8߮%m{l m_VRr~kھ`Yi_ rz_,0th&LA>;'m ־iQ_t;ǯr[V16Co.?;lةrʫLtk9UWb ZNc5߲P ;U5-[}qtgtUK}5H=0q/;6vD`:RrXti-#ynۖR/RRm̍3]Sӊy-VKISkK[ 4Kӑ [>@Pԙzq>xdl}Xy1mxwFΥ'XN~*"igjL}=R/ױ{3a/͈#iR s2LXCb7OW'IbbqzX3>`)y:%ж_ON{#1Փ`D],ҫ4voɷX_fa7>ݫ0zn7; qlU'}˨˫>U~kuhWywk9^_wzμ|{J*QSTY/zbg>m3;*t|)c|{YIwT_^c02zfΞo,_o}qX*"̷ھsWXsR$׬T^Sߞc?|jPS[ *Nb͑N}g~sAHsj}JGcN.~,3@ߓ?-';Q[>;*)kK4e0~2f='ԩ>RNItαGYn|.^p a>zbv5/_s /坺Y:8\eMW_xZ4C{w!.<.*6{a:˸-gb_U;k,l_xzk~9BΟ?݈:(/:Z=2kop(ݶ^PSUfFUE?Xx?X]Sk}Uj/h;u/eW}ʺf[zqpo zcpj=Z-%}@v5ni9l[ǧ$|iv"w s '̦?:(ռe=Rw_acD/~{3|c4 e !WA. u)ҥ"#etKeu`0=16뒖Xڲw7_Kf>݄˿>Eer7[n2շ-y(;߲iݮG{~nMnIwUjei=rƪc)@q3ͷO7K03VOK(ݖ-zk;f |$ii_i~k,y3$ȞBY2IEڬPD8>Ϣyu},tܲ#مdͶ)ʼr[bٞͶ[IĆ$C`& )\р9NGZy뇄8L82 ;?tv\Cm jw_~ .-7m)?ێ/߃ .|wKV]/YK}x(k5v+HuK,M{[a擊vFF؂gmá&t =iZOlOls u_yuڬ ؞G=7; ljlZ<;tI7' ݂//9%wn@f%__rҿBLǗ[;ԞjԶSNoqFK9}I0zR7~e-rӞi־Q3>[:K`ocs6BOҪiǝ'SžVc穳0XjiYIP/v-vj-2~^?|{ؾ~Kmo@H/猵"t3c3z+Y=xǧRAc[Efպ%W]EǙ2 -'7njF5֢\,>P/mi[m$~jKb"v>-'_܎Iw.y5,^WqبYAmNF'ɓ06T\r=j2ZsYf|,=\c"ʞhgyʤ@՗m<|~T_]hJ(W?-ѴR~J9 :"Ӵ%Ounշׯy^ql1Lˑ8L{OwG.=OLՆe|UǙ:ZVvJ^-+橾F5 e%7X2uؑmy^UUU/tsHq=1u׼#^J\]]Z#A iKwrΫ21iYK؇G,V|Ě`у@za|[lRo[ǰu[b_δir}Bbʶ{ތ9TDu \jQvJCuk,jni7Iu]}s^d?\1R18%peTlh5!^ǻj*q(|̺CYh^p3ooٲX=_0_߭*Ӿh,reW;cRI%C)CHwNSNF,l}z=t{X'ANn>Jک2KG+7Z!:ce`×9-L<G ^_IVZ_ J= ZN@sjδ{]52q f\k>1qH͍yގi0vUGi8Pҹ9-GC鏖miӓsQ^^ &xZ[;[_A t/V?'.܍ѾJ5SAJQ=jݫ|r=k,`턣^0 W[cga| ;y-%Ù[ Χe(3=f.ˆs|3.M;cECߩO#rMK,*Ӵ[,"ؙgM$:Xuf-F1cF@$@C<{hŭl޼wgk  \bh@L2Ҏ !-"`0HHz7:>^g^_+~],:$L< eS^]V kDVt5^s n~~ٲ(~Avy\+BF2m)Y+#-6[yϯī{UaUbK+I3JʕwHH"x]ta^`-&˼U,p [i7 z*õ j*% Lt|=f8q*fTJ_ ȕ"IJߋ%-Yu |zɗp1R& ǜw^nURexS,j,ۏ߼ͧ'>pS}8<Ô<,^ܼD=ǒS}F$@$@$S"_/ fFVZ]ī:ɩ9V߂]. mHaֽvO [SsӖVO2>mxuX7d:޽m-`{/7]hڱviGn{ + w>x4O|XrVyr^n I˂$@$@$F)Q9 dS ;RṒނ?=\mA@IDATΜ mx~NzV2ۛsS~bU3vn14Y{KMB~+_ctQ'G'Uk8n0yh5=u1w(>v%,f풆6[3Xc/ &ݞӓ3̅Ž/b^<8[LiN?38=+ͥYw(L;<`l|c8ijM.{;̋,}8d?9eVŤK2p_7oވ;=l1 @pNlk©dL<i#0{nV+Vǧgib+s_\lRu 6?Xz,dY7d=> i QOZpfb<٣|ĘE;0K͸7qԔj fyx+pOK1  v $@$@$sx/~?Ztqs?;T9Nۼ<6Wm•~3 Nc72ԙ&'qCC=m.Hťw\JLNns1Sqi*Hc폧.Mu2c061 {LrvX b__~deeQGM$  '8sr<8/[iLBqB/&TiݖnΗ6_$m  `tLU@򣌵&ˢL(=ϻ:M D:>yu0W۫WicݫƓ ,p|lҳY{HU[## 6mb~uu:t| ;Y Dێ;F7jeffftc }Ӵ̐7rt%C[ Cba.9E8$@$@C "-)Dޗ !0 ;ce:D~_82He  &@uPk܈uCz&$@$@$@W18䅘})8zTSW6`s ]HHH ]]~"҈=SyǏi64 $@$@$@N3>#T{[rC7p @xOFYT-0I$@$@n; ̓cJs?(#  kt|܆87rJr(#  xN$@$@$1t|:Ƌ$@$@$@@D\ZfMX ѦM¢d!N t 0tOFFFtaLw3%J2 m04~R 0t[B7-DSTTLJ#  @D8>pԖc<U1I#0}HKvrHHH1P^:p~ZgpHHHW1LcOל;GX۱>߅t|spRWS,mm,    &@s|/:)Caޅ%-wgf 3^&TWgCiiVVyd @:0U~I2 G1(4sWr-N! @X:,H.Ʈ-Ӣ0hʙsQ_"dHHH:TXxrQUj7V !uaWZB   p'@uP+1FTYSE劋B  %xTjS$@$@$@oge t@DY󄺰MZd"ǝ 0t[~H蜅p|222:G Keff"== -F)9PahNdH݂~>n!">> @0" D:ؓq3~f. @xĸ 2?o4qut|`F  G+;=88IHH | iolJb Dr.Sc  }xY!^Z!K݊x*2HHH:PJ|V ̿vLoEQO1&ε$@$@$@A*$". kX6g6rHHH]].CV[RVaͫo4mzmb&IHHz:>.t}aWkCQQR"   p&@etꪁN#0"lVchZ j]JQD$@$@$PC]x*C-80M$@$@?\u؟ A @ԀcX7vO%   p$qT&   n!@ǧ[( @8K]k֬ 6m vFaGd:-p?$ tBD8>Ӆ233ޅ#ϔ(0q'Ii2$ nk? xOQQQFvHHH Nm v9GFt`K2 6t|ck z#n2wbK(D1 @X='vb Xb^m~&e(&  ot|<Ƨb?rrrC`ə}5mnq0A1 @h@PUZjfosr'  ;\1$U{K`ڌmX~8u v @C]YK"-/h}Te%ESP!1&  &0h Q"-##-BU_$ bG IHH  :lո`ذadAcaOU,bce[xpO/(K1 L0@IIլ[NOrr2#]t|3   IMME~{/z)͝`yWWQHHH5kքmڴ),ћAaB C'9 dddtNDzzzZEEE}|=   `D wlƶGR1|DL7]R   &@kx +3f ':>+}NOL\s5<y,9)k3bXx1Hv)D @ ](_9^xa Q\\V_ ] RD$@$@$ yŰܡ(ʭe$@$@$@aN5czZ v܉=ѓ0gO9'nE)#  _t|\&q$|Wmi4TŞbU$ @o"@era)lَ<\49ɥE$@$@$@N¤K’ず=x2 m6nbė0” +12_KaE(*GLBaW_ou(H1 @94u8.D& .& 5eSęO\   ^@OAjFM$@$@qb+IHH@DY PnbӦMp d@d:-p?$ tBD8>Ӆ233ޅ#ϔ(0q'Ii2$ nk? xOQQQFvHHH ©S[=\m#.i F:KrHHH \ 9kCwWdnq6؋pGQH$@$@$xya~>Y57[(E1 @81: ơh؎mA\\g^a@$@$@$ЫB)r]%. $@$@$@``~ 4(=*Ӕ䙁HHH` 7_Cqw+@1f !:>J9~ Ƚ]@lavK1HHH < ig\n]gjQɞvh1HHŸ1>rgOCܡZOMf t|Ʃ e3 8QN$@$@ogĩq kK"Kr   ^B@5!/)C9 FGm&  :>B$@$@$@@D\ZƼc+ ¦M  d!N t 0tOFFFtaLw3%J2 m04~R 0t[B7-DSTd=%G$@$@$@D8> x7`Oܨ$`i8z0Ouf 7:>^S?= l7fYӳ&ۤL cv_x5wb$-oF1 7:>7Vrh?Ds˄1. @#K]C6ksG/;&`B> ए8IHH pcpFÙ:S kGǃ$@$@$xg/܋UUX8w_sǹǃ$@$@$Pm.x .Yf-ndv   FoSJ ޯvl<;w+>!ֵ$@$@$@M'~y>/o~X{ k?1׾Mijt+I @ ew^rm<2*3$7܎'7HHH pm|*qH\u *@Iu=bb0dX" 6HHH bQFG r]` @/ @' V>IHHzV tYfM ĦMB70 0t[~H蜅p|222:G Keff"== -F)9PahNdH݂~>n!">> @0" NCy>6oنrs;{u0:)M2   ^@ Ù٭r?~'f\ZɹA$@$@$;.q*k~g|rtlY̗$@$@$@aN5c%O C9LK%>-b   mXcT--yjKӚPVXDّK)HHHJ3S,y1TcDO(.rF =:>.CP3^) bؖ$S$@$@$@.ƪmQdL5 닁GR|]:HHH C{'n~ kJcΣ$$@$@$@JTMŌPS fiNY|4աԵ$@$@$@L#9G1nz1 @/"@ub8ըAa*GǕ$@$@$;q"&61шAAzfWZ @o!@sP_Wk>   e^6`l. @ DČϚ5k:O Knڴ E)2 }ɐ C'd:Y'##stTff&ӻb䙒%6d?)Md:-xYYY")** C "`@44T`~>rb189Ǘh(7`oy=b`HݡM   0#@G!il)KRv)Sgb1˖aq6)$@$@$@Hwu46j0Ο N$E>'qxhr\p)]U+DaL$@$@$$Lō߁K.<2  ??\}',,䜏bL$@$@JsdݑD'uddC}g'Jn @p?ˇ[+á=@55џސ1&  7t|~z/}9iXS$@$@$@aIsXիG3 p>'G uZ6 @p֯GP`Λ>c4=u#p"gqV 8dtg_dL$@$@$ntz#uSW(ռl5 =< \+*k>Ǫ4ӌMG|- $@$@$$M]+VDY\G.]ne _/,t^M3&  t|tK3T<Cilb*av5Qb   p!@ǧ#PW M^"$@$@$@=A{:$  1f͚tӦMN;H ;E ]tPDN.P:("G㓑9:]X*33]h1Lɏ Cw2 &C2 @ìЍq V>{$@$@$qbj=x?{BgkA IHHz@D!il))H;vypt0:6u&IHH‹g|t<QQ5sD%D#`tc   #P?A7i.c   #@Gy_'7ux:IHHŽ/u9$:$8rHHH q6$@$@$@$%t F!   8G)Z=#&ΙFǣ($@$@$@=G@=ׂp 65@զ Q&A`tl$  /t|t<+|f!Ѐܝ(7=$@$@$@aGI})vit}&c  Ot|t\ƇOBCU'ț4ϊ~"4`O.S0xu`@$@$@$5" ã (ڻW7o{$NY["  W|sdAR_+TK)j:=N&  0$Y> ݓM†cUNiL\s1PO6u IS+?ŸP%JxCL$@$@=E|"Gܦռ¹Ya9=H[huwIHHŠêQ=ҘVԠfBDŽ9VJ$@$@A㣨ꪰ9;mmdaq|͓}ZB#$d HHH \ |#4 w'3zf׀ܝ(ie&?V>w~eL :>:$ؙcJ|ς   =L   ~t|1k   qk͚5a{ӦMaю2 }Ȑ C'd:Y'##stTff&ӻb䙒%6d?)Md:-xYYY")** C "`@44T`~>rb1v{my>vCAQ4g$R!&HHH|ǧXǜc[;>^ܬ|W% @C؈;6U9͉\kΝFFLnX|B:>ʑ1 1:>:8 Sq'*{g'$ A eؓ[;Լ+ Qj1 @㣃c^;yrsmAGUj-mb*SN0^"Sm$@$@$@FsD{c9|9| ' lDAIHH@TSN&F`h rGmI5'3-ΚIZux'n1zJJJ"AGTTTO?rf'ѺJ_'mvbٲe8czA}{Y-M.boMثdH0Xx1RRRЯ_?m[oѬqOUU t|̧Kru[u^qB4O56"KWbIprSO3>#Fpt;6ߏyY;B!&&$)9enۢg1ْr[̗[Z8c45(`\QY(Y'XA8gȑ2e B֫FsiY__O8ow"qvْ;2pp]:UZX{\j !!Uo9_Uiqni)R!9%!1c 9ΐ,{ 8IZX_$|o׾륪S__8_AbGLuKkQrpb엑U_o}eKQ{~_eO(2*!Iˉ/bK]^ym-q~GGj{&DZAǧBܐdݦ3w܉۷㬳j999>}z<7AGmۆɓ'})=^{ SNG4=n/5+0·oN&,Yn@9.UKt4Ov \8pc ?>{JQi5,MϮ:9նWGjZ.ڥ:vvӑG`V4_#D{uUI9)?ګఴm̙dA8a\sη;Z4è,jk|qU)!:t>hU;/>cYˑGi[3ӗ>Z/6: _ڷOYL¹UqH6Gí{g΁}{p]柝{~|#ʊ[pp ++y=%1ɾ2#}.Ax]4x0Ή#i\wuLL44Ad~+}2n2UvPB+]Ua#ՂO&L-g*"izm3$뒏:։DO4ݽωoe7ߌכ'@>oS>=G5KBZ wgM\rqygB{~MDmlqb3ɸ xg=w \4y+_EeY9ʶLc$6>y{3VɋpFmsa|mE.X%Y#AeֆR}mٖ}"kјe&\~+nG1F3@w FPf`/YXfoS ))'q|v芓 K/C_IV=2,N2%Nd|'l=ɓ2{<)&wlٲ%(}<Ď-Ly˥ÇGHҗir '3TRX m=I;CLR]1IQ;f| 'Lm95yȁE-P=%?qn$5b֭yj˳a:*H@=}K$#uK{N:6´ SpxeCH3s%4Cթcc m,B~}*b+8Tm.ը7Q۝i/\j!iPF7"\먮_~>ڌiGF0ry֬Y]cVz5vH#Dr  9PR5"nzM=JZhŖDNnwq 9vI,}|lK$^gh,}F_cƌG{jWuӷ/ؒr]l\Ƥ3!aÆYtqsڽ+W^u"ch8_-ZZ}+eSL,o%-9kxwwBL)7l8_ZD+'-qC.AYذa}1L23|an!klLGt[ٲt ΅[>{ё8ܒyထGb^t,bMA@0  ˉ83e 'd<бGb(s @Iꉑ#ol w#aԁe|g*ؐ;픟=Ko9FclOr5o/t ȸڻ &GqZ T%1Y'NB%n~ ! ǧ;)ӣ=XSUۀ|4fb“w8`Ϸ_f*jW+#ۃ47wCgqEY'{Κ2ѵajm>A9qج2̥~? ﾿Zxϱ1-KHpYB/q}eRDٜZYcosy˚Ic11ڪa'?ԎGfz|d5!8Tqqc0ݵd? 7/gbẶoM5=Gc٭'~%1PZN #vl썽!b[/ň qHNe:k1}v[2EN"ԗY+Yo9='I7,U'd?*(z;iM8k31?׼6ZNO1tz )8YU&cAqKc~]nsI̤_J˂[Zѳ7M-/bLtv}9˶nOK鈮eYLI@RGa|% JkOp %$$"u`;5n]9q- uyA/jPGYl/k(wY=f O pvX -2,d%r猏nWJ~?̿w(O1ʇMg/ڰR|wLO9,^|9SOY3AZ#1d-{-OFf8O);%WXDaLƃV#=+]rnObHXF-=3c.G}?D{wG~˗LMks׮bCl@jJ5'fɽ @cVu|d1ljIfĪ0ׯ4miy̐=s%2yikiZ}`hnv͢{ȸK K$@$II!kZ& Ow孼=)y`MUlɩyˬٽ{C1^n-4m6mZFlkCy0޲UE8p矻׬5o;ܛ[2W!Ƽdtf;s0߬DXt;x2Z hY D&V'@}(Zl/wZ[]n9"δ8' 0~mFz˵U_⮫x>Y^?lڏ '&tE1H' [aI H"?X0Jz_c_kC"PDyH:Pw]G\ 6tDnU'k+oӦOO`ҥӦ2͛7g?)J0gqx /|,_e?ygz ܹ=< ֮}pe>t5s3s,<&u%;ffK/ a{g|[xD-_"++ YE-!N^jh)b1恀p2y"s4<`&8!aŽ +o(8m@KrJq_]?s?t/V_2dp?_ O,g\}!"o @0q?771[ȎqAy_8!✨s#iYʌ8v-/6r鈽lt(ii]wߍK]o*k%ypͷo|7\#!/|c?yѣq[|kּan߾.~rUXbn97/[ڴQ^qF1U吧0;,`kM(yrqɴӾ]mH|wfcW/}_!zEoӿ'|5> 29OД:L|PZu,d\]or D ;Os}mgYR m@ Zd0ZcK(:Q&L3 '2dJ̔@Qbu(Ec -Z{^}쇚rww~v}~[o<>uD"!*IDATƞ}iguV9KW>sGzvm-ݐ/|3)u_>h~+ӟ)g?wudwKu}j>T<r<\.(./9rOiͧ=X~rsO)巯_Ǟ[~g(;^oWxa2@Gzuo.~a`HgO_qIG}'5=Сt`|_>醀.>b9 vHow;orKUW_]=_|F?~z=ElޫGϹW;T7~|}w\ܶH>BN(_/⯗K[~rwjyQ9'9_XyS(_ʡJ^}z ||4}Dۃ nϻG7|3~>T^WΞ@f;}u87_ wgyf?Glo?f{衇ʡC'z't/|ȮOK}t3_V>?Gʕ>X~ʗw;/5tOO^us9k>S;yUo+\t q TR~|Qa ; h]wv?xr]_Rf˽ˬ@g!s> =c ֳ_]R\pl|d.}KְF ~ۃ_]́>y~WW^2Nm*wt_?xQ{?}d?6Cя~Mo*__<?V$u'N9<Ïv?e7-]a[ʡ?O]ºI aʀ?[sw߻tS]J~BCuG%ˮ˯/!p|ia˖׺{?}Vߩ3oڐmZcq܃!=SWz)B^<zlA}n-{Y^O*>c{vӺ!hgtC}O@a`Uv-0?{WK_ rkn]g22XAq|⎃b>k+Ou 0@5~`#c-[V lΩcǯ{ysj|g~>C7"bw͠Xmk!?{h xL6:-/Lpɬ2&@a@ Y#`Sm AɞX;&[BEHlgzWnp"C7lOt C|5wR=q#Ykj@a ]cko :WΪ13[9kuYu&8L]ߺ)di탼? 0 ᫷?L<"a 0̀!54gclլS7'X'g[kNUɍ-Qb`h|7M]ؘ75h]+Oޙ=N=q 1ّ0@SS>kzx}ފ0jt׮Kq>0 ߩlsfƧ`#[8֛}11 &[B["E@a l;kSMhcKrbiCkum >ڤ5/[Ը|a *u\1 > Z5!_ynˏ0@€s }8VL^O'ZC+ nŦb5,[A@|ڦ1iV̱֛%9Z\Ȗf a 00!sK=<&1evr.qgW9u{6*ֶnV"e덫ma !pj\9Փ+_1.QL"Ïa 00??V1|閭qɖ(.AO)rV?t'tR꫞nm0!a 8k_zl#[dɎ0@ʀ%Ԁqf`&ۭ\U =e>TqԔÞ<s|Ko [89j+| B&_Zc0@h130?:e'RSrK3oujpl2h#~l:OFS=)ZojzX^e+Gu _څ>a 0D3Pqn 0Ѫ}%Yr;>ŧR3\nէA0ϓݺ&i- c^go-L1صVC]X0@ЙV wqmaخ&GXt#E9!=~%0kW[Ӧ9VM?Z5h.xahlT7!~[6Zo%ۇl]x1CrWmϝ}a 00?廍G _9>(/XlI̞.cܕ࣍2#O=&["Ko9WL#[jMZ$h}0@ؐ?}FpAK+g9԰t}V{LϲRycV~Ygi6!V F+LZ?qn{p y^} 8ue{61W 1b0@XIs)6Cpl[0pz(Nܵ9{Gǖ%„ hm +ԁ+Ɠ1K6 w[ȧ̵Ha M$Ac:ay'^O8ܳo:actnʇ5!>H,ˡ<|zK Ï&i\[1nϢy a 0k8Z6g!9j Vu|[X<Zk,<>l1dkQNKKeAbXO0|׃z6Z9|i]s-;@a`SS/k[>/`CZyqljs-nrբeW6ϐв9rt`؊ %6dKt!o=>Mg "ƚhZ7q0@SЙ-so&ڇ![qーc\ipav#{6o1#aN0N _y؊7웁6aﶪ0qTdG+E0:ϗxKϢWr@a`;Ҏa|t[EVb$nxma%G[[o{imDfh`Fd`!GcHZ|J!Hrӊ!0@X;ӊy# d㣽1%d{xɓy;xdv1T%CDq0V9.[q®u ْZøa 00=>kl]>`+m|d[,ۇ\/{{i C٨rkz 5$#Lyr;h1)w٥|rd#$w@a`8G;&=R|xV MSc'^sYf_Yok𙲰nTF1];4(h0Hչ{|ɖaHa ]3yҎn]_ oa>`|I̵ljۉ.mes#&9A^/8~f!gȧ?: 0Zc-_:W%юaSC=85C:]!]jࣂ%{pj\nm$`"!\ZQ$bQ7Fv}uL<(?@a`[ YGOSl^'֜Qyl0ZgkGjfu|m0`Brv'\B-]/Aϼ#}0@0s]ks裸j2bnqV1LYP7€6ܨ|#-Rj ә![ jOm%"a 0v̀2Iw{(ϓ@C֊S3dZan{VVn}0P?A\pϡc2~:„x/c0@c`p&VZs\6|顡Gy 9_ʫ1p^XƩ#o{dWO|֡867|l ‰K0֣|]B%Cz9&?: 0Z!u`-- \0t oaz(qlizdoNƶ5hsCǸ r97N}Wna|#560@dGkl0il`hb-Pqli 7BӡƏkEmhhcOzngh]*E Z9İ}V3$a 0g#86d+W1M=w=T9=o.q>"\j M?ՒSp u3o,Oq`E.qr\bhr@a lGkCe﹎SL-O<鵾/9+Tl04=𥹦ƨ|!\5Yq|Ȧy`a 01XvL/߯._#Ҏv_-TdO|Xõbp[9˖(bQ#-rjSK9^88ya 0֙3%ukற#kk$Z1ۺf>}݉c1~t]nӇ9ZZ꥘-0@`Z{\|ZҎɖP?fG`$7 C]Z97OF yFQ=kaG0@ؔ,R:F9h;&k,cć嬅WcV6BZuޔ85a 0p1ltl|ǗvOx|(O >]}ޗFbV\qx~0@bZ,M_hXӋ&{a[|pd,j |[c8}_0@=7~m!kYmV]=Q>X|(s^y,cyc1; 0.,X41oa{r;|B7źs=ePSml0 0 lLS>˭n끇' ?}k象b,,|[7/xa M(Fu_׍kУu\'A{h豘җi97>: 0Fz(e,>[pC;#5z25^fݺO0@u{~vNzAe|_+va ㍁IIyGk~H% +䲝1~ck%@a`eM,JɹG{g m:lZ֣@a  LVnfce^I= /Kk`~-va 0+ !kVcm>=]Iو@cy7ϞMoo-;CR`a 0p3afM/Îq9 `o-@a !u+Ouse0r6b0@e{WGzf9֧#s0@8z e3l^j@a ㊁Wf@a 0|6 /a 0@n@a y) a 0/2_Wv@a l@ Kia 0p|1z0@a` IENDB`XlsxWriter-1.1.2/docs/_static/logo.png0000644000076500000240000001522213123763617017726 0ustar Johnstaff00000000000000PNG  IHDR>aiCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs  JIDATx=]]U3s涎}؉8%HN4QѪB O  /Di!P@%8%BIخk;7N8_=5gfٳ{9={Z֬Ykzݥ_UUhDIJR]1T%#7 Y'S{ɶ6kyyÇcǎsw;w`0( l4l(z͋oHʼʼzk Oy иNw|^]x [[[ G~Ze`[d z$#%pD =wDUX؝@—bx)?W%4$>(BKKKH{)$v'{_vP/{.-A1B:*(~zE+*nQ>2B7_] ?湹;BCGg:tJ!0Eܰ;o QI`Լ7sS#W\ POv !jƷ!>20WC4C HF*M8F(,'C S>~ÿ q }ChPz}'*Ոmk0S,_ м'n=׋ʰ r1 ZݽЫeX|4| =4q HHx5$h-/2|!z~ğ&(]zx 14Әj|h`y(ʐJI~?Ƣ7 @ 4 cjKyE0r0 Q3oGZWNh-Lchx L[(M'~{WW^yɕ )ΧV%$z_0Y^ v <"m0*EmZw>c鯐j]O2zw1dmmJ 5MO),.}ø0R'@V6<|?Lls;h/8I* ^%(y(a)Ө&`/7ɡc_3'_"o]}%cIx|a"~mFE^h:8?>nn;^OP[ܵ|?93۷^%_װC/gO65 p2K 9r]Tծʝ&>Ɗ'^΍WOLMf,^[}=rۥkZ d8#eoa`Gw/e/Z,0#8|@WKjb uq?{EzQ5 ?aϏAj':G,=H|l75SH]=;ѹ \~ey\aw\9uit};e/x_K(` P:xŨPE,E@a> <O6V?y `]iT$oBW~: +:ٓ#+j|//%YٽCnVe}dKK\z{iFOڥ L/T` @ Mx}x*?M=b%OzsKT#ƦnaAE.}ZǛzP>)*;b]3G嗗0; +WBy;;ϓ]G;wWdX >x\YwCI#'_?$a1(k l~WeLFEDԷ29sb?~OVW/#?XGAoڷi*EɌN ['w@t?{KO>ounJ?10iA)*QH NIَIg޻6u#+7_Sۓr)j^FB2gLr"IdoÖ}s: L\5+7WwӠC?6ۗtJOO@Rkoc W{Bh]R b(ZB Qwѣ;ٰT<4b1j>c/V<;T?WG=7& 'Ѣ0ޥKb7<|3El`lPƧy&"ob?>3mǶi*:9+@y `!Ø d1H5UԼ>ucN}jGbWiOiCtXVٳ8ɱǖq-1B.ab#KD"%|\ w%f)EFBW]F{)L0Ƕ@Z#XɅ]_ܱS"ʻ;Te=\,GTWT:M=;ŸEj/%-k&6C_JvXJVګIN${="t…^z -N[)s@j4& 0"ݻ@A8u+ڱ0Eu}[ +bYSO|?b3F|83$*u>kY`ku:LHSw!U5˷9>JXL}&0l" `V#>@Ei>Oj`%c:/v=( Й:d9ٿP3B̼;0d.W/|m n]7!U>,~n0!YTu;BF!B/oXcwVgrϙ4%\F/Sf̜HTkX5t)R\@J\k' Z'~.bY0>\VJoz8omm1]8Vr߆СcM 𲯘ɃgIL{^|}ߋ!J̨@"Ǭ֒F0EiM[CLak6f7WX>u4>v3Vɴ[ x銠tsqAKֶYo1{\7c SQ2-Zj`ε1C,7Nc. 1%k}zI d-ϲ5 ,a0]YOH1r e1SI:tDHQZ>7>OfM}#2͙[Qiti`Ukٶ찵נݼ=Wg#ydul`3.6It} uWEߚ%<]|o\\@H10(|@6[Bwh= & qgF,$¾aݥί47Sg>V7ͥ廗F {÷_|sRs Ι a#1M4&s]b ÛszO[7&!=5$ * `,BŎqm@rs(>@i@\2:1ϔq$7|$m.3(Sh%ܾ}bU*}K[{#ƴ}N5$i#7ĐSʻ`;PX P3 /7䋯EKHk <\b99PB-%z@ɬQ^ ڮkL S4t ls6fX*zEPFp/LNeWUR:\0͡o5| z vzct^~*gnf.և򠊮 8looז@!=Da "vz]k`ХG ۾>7c&#*Vkb%ï]Rn:  1F e/@. `L=h'{i-=7ã} &݊痗O*kx, #b(5y&Fb㪕#`4pK3 _g <Ļlh=t? 8^;1R60=ѐT,F;œY!$&~(`$3T(GA 3^`dh88(-DCQQC=R0EDU{ Z ='R38yv1*0D/*4IgLkP*u㨔kO~$'j{ J&@ZÅRhqT2P~E|TK& T0qTX3:+}%". _HkA@ .]RPB(`Qwpե9$>h]/x Wr`/Q(=?; z!Qx{jWpW%@>a*:UԞHIENDB`XlsxWriter-1.1.2/docs/_static/pygments.css0000644000076500000240000000733113123763617020642 0ustar Johnstaff00000000000000.highlight .hll { border-left: 2px solid #ff0000; } .highlight .c { color: #65B154 } /* Comment */ .highlight .err { border: 1px solid #FF0000 } /* Error */ .highlight .k { color: #007020; font-weight: bold } /* Keyword */ .highlight .o { color: #666666 } /* Operator */ .highlight .cm { color: #65B154 } /* Comment.Multiline */ .highlight .cp { color: #007020 } /* Comment.Preproc */ .highlight .c1 { color: #65B154 } /* Comment.Single */ .highlight .cs { color: #65B154; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #A00000 } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #FF0000 } /* Generic.Error */ .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ .highlight .gi { color: #00A000 } /* Generic.Inserted */ .highlight .go { color: #333333 } /* Generic.Output */ .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ .highlight .gt { color: #0044DD } /* Generic.Traceback */ .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #007020 } /* Keyword.Pseudo */ .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #902000 } /* Keyword.Type */ .highlight .m { color: #208050 } /* Literal.Number */ .highlight .s { color: #4070a0 } /* Literal.String */ .highlight .na { color: #4070a0 } /* Name.Attribute */ .highlight .nb { color: #007020 } /* Name.Builtin */ .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ .highlight .no { color: #60add5 } /* Name.Constant */ .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ .highlight .ne { color: #007020 } /* Name.Exception */ .highlight .nf { color: #06287e } /* Name.Function */ .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #bb60d5 } /* Name.Variable */ .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mf { color: #208050 } /* Literal.Number.Float */ .highlight .mh { color: #208050 } /* Literal.Number.Hex */ .highlight .mi { color: #208050 } /* Literal.Number.Integer */ .highlight .mo { color: #208050 } /* Literal.Number.Oct */ .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ .highlight .sc { color: #4070a0 } /* Literal.String.Char */ .highlight .sd { color: #4070a0 } /* Literal.String.Doc */ .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ .highlight .si { color: #70a0d0 } /* Literal.String.Interpol */ .highlight .sx { color: #c65d09 } /* Literal.String.Other */ .highlight .sr { color: #235388 } /* Literal.String.Regex */ .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ .highlight .ss { color: #517918 } /* Literal.String.Symbol */ .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */XlsxWriter-1.1.2/docs/readme.html0000644000076500000240000001444713263123126016753 0ustar Johnstaff00000000000000 Getting Started with XlsxWriter — XlsxWriter Readme

Getting Started with XlsxWriter

Here are some easy instructions to get you up and running with the XlsxWriter module.

Installing XlsxWriter

The first step is to install the XlsxWriter module. There are several ways to do this.

Using PIP

The pip installer is the preferred method for installing Python modules from PyPI, the Python Package Index:

$ sudo pip install XlsxWriter

Note

Windows users can omit sudo at the start of the command.

Using Easy_Install

If pip doesn’t work you can try easy_install:

$ sudo easy_install install XlsxWriter

Installing from a tarball

If you download a tarball of the latest version of XlsxWriter you can install it as follows (change the version number to suit):

$ tar -zxvf XlsxWriter-1.2.3.tar.gz

$ cd XlsxWriter-1.2.3
$ sudo python setup.py install

A tarball of the latest code can be downloaded from GitHub as follows:

$ curl -O -L http://github.com/jmcnamara/XlsxWriter/archive/master.tar.gz

$ tar zxvf master.tar.gz
$ cd XlsxWriter-master/
$ sudo python setup.py install

Cloning from GitHub

The XlsxWriter source code and bug tracker is in the XlsxWriter repository on GitHub. You can clone the repository and install from it as follows:

$ git clone https://github.com/jmcnamara/XlsxWriter.git

$ cd XlsxWriter
$ sudo python setup.py install

Running a sample program

If the installation went correctly you can create a small sample program like the following to verify that the module works correctly:

import xlsxwriter

workbook = xlsxwriter.Workbook('hello.xlsx')
worksheet = workbook.add_worksheet()

worksheet.write('A1', 'Hello world')

workbook.close()

Save this to a file called hello.py and run it as follows:

$ python hello.py

This will output a file called hello.xlsx which should look something like the following:

_static/hello01.png

If you downloaded a tarball or cloned the repo, as shown above, you should also have a directory called examples with some sample applications that demonstrate different features of XlsxWriter.

Documentation

The full version of XlsxWriter documentation is hosted on Read The Docs. It is also available as a PDF.

XlsxWriter-1.1.2/examples/0000755000076500000240000000000013362632140015505 5ustar Johnstaff00000000000000XlsxWriter-1.1.2/examples/array_formula.py0000644000076500000240000000177013263123126020726 0ustar Johnstaff00000000000000####################################################################### # # Example of how to use Python and the XlsxWriter module to write # simple array formulas. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter # Create a new workbook and add a worksheet workbook = xlsxwriter.Workbook('array_formula.xlsx') worksheet = workbook.add_worksheet() # Write some test data. worksheet.write('B1', 500) worksheet.write('B2', 10) worksheet.write('B5', 1) worksheet.write('B6', 2) worksheet.write('B7', 3) worksheet.write('C1', 300) worksheet.write('C2', 15) worksheet.write('C5', 20234) worksheet.write('C6', 21003) worksheet.write('C7', 10000) # Write an array formula that returns a single value worksheet.write_formula('A1', '{=SUM(B1:C1*B2:C2)}') # Same as above but more verbose. worksheet.write_array_formula('A2:A2', '{=SUM(B1:C1*B2:C2)}') # Write an array formula that returns a range of values worksheet.write_array_formula('A5:A7', '{=TREND(C5:C7,B5:B7)}') workbook.close() XlsxWriter-1.1.2/examples/autofilter.py0000644000076500000240000001410513263123126020235 0ustar Johnstaff00000000000000############################################################################### # # An example of how to create autofilters with XlsxWriter. # # An autofilter is a way of adding drop down lists to the headers of a 2D # range of worksheet data. This allows users to filter the data based on # simple criteria so that some data is shown and some is hidden. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('autofilter.xlsx') # Add a worksheet for each autofilter example. worksheet1 = workbook.add_worksheet() worksheet2 = workbook.add_worksheet() worksheet3 = workbook.add_worksheet() worksheet4 = workbook.add_worksheet() worksheet5 = workbook.add_worksheet() worksheet6 = workbook.add_worksheet() # Add a bold format for the headers. bold = workbook.add_format({'bold': 1}) # Open a text file with autofilter example data. textfile = open('autofilter_data.txt') # Read the headers from the first line of the input file. headers = textfile.readline().strip("\n").split() # Read the text file and store the field data. data = [] for line in textfile: # Split the input data based on whitespace. row_data = line.strip("\n").split() # Convert the number data from the text file. for i, item in enumerate(row_data): try: row_data[i] = float(item) except ValueError: pass data.append(row_data) # Set up several sheets with the same data. for worksheet in (workbook.worksheets()): # Make the columns wider. worksheet.set_column('A:D', 12) # Make the header row larger. worksheet.set_row(0, 20, bold) # Make the headers bold. worksheet.write_row('A1', headers) ############################################################################### # # Example 1. Autofilter without conditions. # # Set the autofilter. worksheet1.autofilter('A1:D51') row = 1 for row_data in (data): worksheet1.write_row(row, 0, row_data) # Move on to the next worksheet row. row += 1 ############################################################################### # # # Example 2. Autofilter with a filter condition in the first column. # # Autofilter range using Row-Column notation. worksheet2.autofilter(0, 0, 50, 3) # Add filter criteria. The placeholder "Region" in the filter is # ignored and can be any string that adds clarity to the expression. worksheet2.filter_column(0, 'Region == East') # Hide the rows that don't match the filter criteria. row = 1 for row_data in (data): region = row_data[0] # Check for rows that match the filter. if region == 'East': # Row matches the filter, no further action required. pass else: # We need to hide rows that don't match the filter. worksheet2.set_row(row, options={'hidden': True}) worksheet2.write_row(row, 0, row_data) # Move on to the next worksheet row. row += 1 ############################################################################### # # # Example 3. Autofilter with a dual filter condition in one of the columns. # # Set the autofilter. worksheet3.autofilter('A1:D51') # Add filter criteria. worksheet3.filter_column('A', 'x == East or x == South') # Hide the rows that don't match the filter criteria. row = 1 for row_data in (data): region = row_data[0] # Check for rows that match the filter. if region == 'East' or region == 'South': # Row matches the filter, no further action required. pass else: # We need to hide rows that don't match the filter. worksheet3.set_row(row, options={'hidden': True}) worksheet3.write_row(row, 0, row_data) # Move on to the next worksheet row. row += 1 ############################################################################### # # # Example 4. Autofilter with filter conditions in two columns. # # Set the autofilter. worksheet4.autofilter('A1:D51') # Add filter criteria. worksheet4.filter_column('A', 'x == East') worksheet4.filter_column('C', 'x > 3000 and x < 8000') # Hide the rows that don't match the filter criteria. row = 1 for row_data in (data): region = row_data[0] volume = int(row_data[2]) # Check for rows that match the filter. if region == 'East' and volume > 3000 and volume < 8000: # Row matches the filter, no further action required. pass else: # We need to hide rows that don't match the filter. worksheet4.set_row(row, options={'hidden': True}) worksheet4.write_row(row, 0, row_data) # Move on to the next worksheet row. row += 1 ############################################################################### # # # Example 5. Autofilter with filter for blanks. # # Create a blank cell in our test data. # Set the autofilter. worksheet5.autofilter('A1:D51') # Add filter criteria. worksheet5.filter_column('A', 'x == Blanks') # Simulate a blank cell in the data. data[5][0] = '' # Hide the rows that don't match the filter criteria. row = 1 for row_data in (data): region = row_data[0] # Check for rows that match the filter. if region == '': # Row matches the filter, no further action required. pass else: # We need to hide rows that don't match the filter. worksheet5.set_row(row, options={'hidden': True}) worksheet5.write_row(row, 0, row_data) # Move on to the next worksheet row. row += 1 ############################################################################### # # # Example 6. Autofilter with filter for non-blanks. # # Set the autofilter. worksheet6.autofilter('A1:D51') # Add filter criteria. worksheet6.filter_column('A', 'x == NonBlanks') # Hide the rows that don't match the filter criteria. row = 1 for row_data in (data): region = row_data[0] # Check for rows that match the filter. if region != '': # Row matches the filter, no further action required. pass else: # We need to hide rows that don't match the filter. worksheet6.set_row(row, options={'hidden': True}) worksheet6.write_row(row, 0, row_data) # Move on to the next worksheet row. row += 1 workbook.close() XlsxWriter-1.1.2/examples/autofilter_data.txt0000644000076500000240000000356312124652616021431 0ustar Johnstaff00000000000000Region Item Volume Month East Apple 9000 July East Apple 5000 July South Orange 9000 September North Apple 2000 November West Apple 9000 November South Pear 7000 October North Pear 9000 August West Orange 1000 December West Grape 1000 November South Pear 10000 April West Grape 6000 January South Orange 3000 May North Apple 3000 December South Apple 7000 February West Grape 1000 December East Grape 8000 February South Grape 10000 June West Pear 7000 December South Apple 2000 October East Grape 7000 December North Grape 6000 April East Pear 8000 February North Apple 7000 August North Orange 7000 July North Apple 6000 June South Grape 8000 September West Apple 3000 October South Orange 10000 November West Grape 4000 July North Orange 5000 August East Orange 1000 November East Orange 4000 October North Grape 5000 August East Apple 1000 December South Apple 10000 March East Grape 7000 October West Grape 1000 September East Grape 10000 October South Orange 8000 March North Apple 4000 July South Orange 5000 July West Apple 4000 June East Apple 5000 April North Pear 3000 August East Grape 9000 November North Orange 8000 October East Apple 10000 June South Pear 1000 December North Grape 10000 July East Grape 6000 February XlsxWriter-1.1.2/examples/cell_indentation.py0000644000076500000240000000117213263123126021372 0ustar Johnstaff00000000000000############################################################################## # # A simple formatting example using XlsxWriter. # # This program demonstrates the indentation cell format. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('cell_indentation.xlsx') worksheet = workbook.add_worksheet() indent1 = workbook.add_format({'indent': 1}) indent2 = workbook.add_format({'indent': 2}) worksheet.set_column('A:A', 40) worksheet.write('A1', "This text is indented 1 level", indent1) worksheet.write('A2', "This text is indented 2 levels", indent2) workbook.close() XlsxWriter-1.1.2/examples/chart.py0000644000076500000240000000164613276050061017167 0ustar Johnstaff00000000000000####################################################################### # # An example of a simple Excel chart with Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart.xlsx') worksheet = workbook.add_worksheet() # Create a new Chart object. chart = workbook.add_chart({'type': 'column'}) # Write some data to add to plot on the chart. data = [ [1, 2, 3, 4, 5], [2, 4, 6, 8, 10], [3, 6, 9, 12, 15], ] worksheet.write_column('A1', data[0]) worksheet.write_column('B1', data[1]) worksheet.write_column('C1', data[2]) # Configure the charts. In simplest case we just add some data series. chart.add_series({'values': '=Sheet1!$A$1:$A$5'}) chart.add_series({'values': '=Sheet1!$B$1:$B$5'}) chart.add_series({'values': '=Sheet1!$C$1:$C$5'}) # Insert the chart into the worksheet. worksheet.insert_chart('A7', chart) workbook.close() XlsxWriter-1.1.2/examples/chart_area.py0000644000076500000240000000651113263123126020152 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Area charts with Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_area.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Number', 'Batch 1', 'Batch 2'] data = [ [2, 3, 4, 5, 6, 7], [40, 40, 50, 30, 25, 50], [30, 25, 30, 10, 5, 10], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) worksheet.write_column('C2', data[2]) ####################################################################### # # Create an area chart. # chart1 = workbook.add_chart({'type': 'area'}) # Configure the first series. chart1.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure a second series. Note use of alternative syntax to define ranges. chart1.add_series({ 'name': ['Sheet1', 0, 2], 'categories': ['Sheet1', 1, 0, 6, 0], 'values': ['Sheet1', 1, 2, 6, 2], }) # Add a chart title and some axis labels. chart1.set_title ({'name': 'Results of sample analysis'}) chart1.set_x_axis({'name': 'Test number'}) chart1.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart1.set_style(11) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D2', chart1, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a stacked area chart sub-type. # chart2 = workbook.add_chart({'type': 'area', 'subtype': 'stacked'}) # Configure the first series. chart2.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. chart2.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title and some axis labels. chart2.set_title ({'name': 'Stacked Chart'}) chart2.set_x_axis({'name': 'Test number'}) chart2.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart2.set_style(12) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D18', chart2, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a percent stacked area chart sub-type. # chart3 = workbook.add_chart({'type': 'area', 'subtype': 'percent_stacked'}) # Configure the first series. chart3.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. chart3.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title and some axis labels. chart3.set_title ({'name': 'Percent Stacked Chart'}) chart3.set_x_axis({'name': 'Test number'}) chart3.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart3.set_style(13) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D34', chart3, {'x_offset': 25, 'y_offset': 10}) workbook.close() XlsxWriter-1.1.2/examples/chart_bar.py0000644000076500000240000000650013263123126020004 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Bar charts with Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_bar.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Number', 'Batch 1', 'Batch 2'] data = [ [2, 3, 4, 5, 6, 7], [10, 40, 50, 20, 10, 50], [30, 60, 70, 50, 40, 30], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) worksheet.write_column('C2', data[2]) ####################################################################### # # Create a new bar chart. # chart1 = workbook.add_chart({'type': 'bar'}) # Configure the first series. chart1.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure a second series. Note use of alternative syntax to define ranges. chart1.add_series({ 'name': ['Sheet1', 0, 2], 'categories': ['Sheet1', 1, 0, 6, 0], 'values': ['Sheet1', 1, 2, 6, 2], }) # Add a chart title and some axis labels. chart1.set_title ({'name': 'Results of sample analysis'}) chart1.set_x_axis({'name': 'Test number'}) chart1.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart1.set_style(11) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D2', chart1, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a stacked chart sub-type. # chart2 = workbook.add_chart({'type': 'bar', 'subtype': 'stacked'}) # Configure the first series. chart2.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. chart2.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title and some axis labels. chart2.set_title ({'name': 'Stacked Chart'}) chart2.set_x_axis({'name': 'Test number'}) chart2.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart2.set_style(12) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D18', chart2, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a percentage stacked chart sub-type. # chart3 = workbook.add_chart({'type': 'bar', 'subtype': 'percent_stacked'}) # Configure the first series. chart3.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. chart3.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title and some axis labels. chart3.set_title ({'name': 'Percent Stacked Chart'}) chart3.set_x_axis({'name': 'Test number'}) chart3.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart3.set_style(13) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D34', chart3, {'x_offset': 25, 'y_offset': 10}) workbook.close() XlsxWriter-1.1.2/examples/chart_clustered.py0000644000076500000240000000333713263123126021237 0ustar Johnstaff00000000000000####################################################################### # # A demo of a clustered category chart in XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # from xlsxwriter.workbook import Workbook workbook = Workbook('chart_clustered.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Types', 'Sub Type', 'Value 1', 'Value 2', 'Value 3'] data = [ ['Type 1', 'Sub Type A', 5000, 8000, 6000], ['', 'Sub Type B', 2000, 3000, 4000], ['', 'Sub Type C', 250, 1000, 2000], ['Type 2', 'Sub Type D', 6000, 6000, 6500], ['', 'Sub Type E', 500, 300, 200], ] worksheet.write_row('A1', headings, bold) for row_num, row_data in enumerate(data): worksheet.write_row(row_num + 1, 0, row_data) # Create a new chart object. In this case an embedded chart. chart = workbook.add_chart({'type': 'column'}) # Configure the series. Note, that the categories are 2D ranges (from column A # to column B). This creates the clusters. The series are shown as formula # strings for clarity but you can also use the list syntax. See the docs. chart.add_series({ 'categories': '=Sheet1!$A$2:$B$6', 'values': '=Sheet1!$C$2:$C$6', }) chart.add_series({ 'categories': '=Sheet1!$A$2:$B$6', 'values': '=Sheet1!$D$2:$D$6', }) chart.add_series({ 'categories': '=Sheet1!$A$2:$B$6', 'values': '=Sheet1!$E$2:$E$6', }) # Set the Excel chart style. chart.set_style(37) # Turn off the legend. chart.set_legend({'position': 'none'}) # Insert the chart into the worksheet. worksheet.insert_chart('G3', chart) workbook.close() XlsxWriter-1.1.2/examples/chart_column.py0000644000076500000240000000652213263123126020541 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Column charts with Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_column.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Number', 'Batch 1', 'Batch 2'] data = [ [2, 3, 4, 5, 6, 7], [10, 40, 50, 20, 10, 50], [30, 60, 70, 50, 40, 30], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) worksheet.write_column('C2', data[2]) ####################################################################### # # Create a new column chart. # chart1 = workbook.add_chart({'type': 'column'}) # Configure the first series. chart1.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure a second series. Note use of alternative syntax to define ranges. chart1.add_series({ 'name': ['Sheet1', 0, 2], 'categories': ['Sheet1', 1, 0, 6, 0], 'values': ['Sheet1', 1, 2, 6, 2], }) # Add a chart title and some axis labels. chart1.set_title ({'name': 'Results of sample analysis'}) chart1.set_x_axis({'name': 'Test number'}) chart1.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart1.set_style(11) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D2', chart1, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a stacked chart sub-type. # chart2 = workbook.add_chart({'type': 'column', 'subtype': 'stacked'}) # Configure the first series. chart2.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. chart2.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title and some axis labels. chart2.set_title ({'name': 'Stacked Chart'}) chart2.set_x_axis({'name': 'Test number'}) chart2.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart2.set_style(12) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D18', chart2, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a percentage stacked chart sub-type. # chart3 = workbook.add_chart({'type': 'column', 'subtype': 'percent_stacked'}) # Configure the first series. chart3.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. chart3.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title and some axis labels. chart3.set_title ({'name': 'Percent Stacked Chart'}) chart3.set_x_axis({'name': 'Test number'}) chart3.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart3.set_style(13) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D34', chart3, {'x_offset': 25, 'y_offset': 10}) workbook.close() XlsxWriter-1.1.2/examples/chart_combined.py0000644000076500000240000000641613123762525021035 0ustar Johnstaff00000000000000####################################################################### # # An example of a Combined chart in XlsxWriter. # # Copyright 2013, John McNamara, jmcnamara@cpan.org # from xlsxwriter.workbook import Workbook workbook = Workbook('chart_combined.xlsx') worksheet = workbook.add_worksheet() # Add a format for the headings. bold = workbook.add_format({'bold': True}) # Add the worksheet data that the charts will refer to. headings = ['Number', 'Batch 1', 'Batch 2'] data = [ [2, 3, 4, 5, 6, 7], [10, 40, 50, 20, 10, 50], [30, 60, 70, 50, 40, 30], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) worksheet.write_column('C2', data[2]) # # In the first example we will create a combined column and line chart. # They will share the same X and Y axes. # # Create a new column chart. This will use this as the primary chart. column_chart1 = workbook.add_chart({'type': 'column'}) # Configure the data series for the primary chart. column_chart1.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Create a new column chart. This will use this as the secondary chart. line_chart1 = workbook.add_chart({'type': 'line'}) # Configure the data series for the secondary chart. line_chart1.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Combine the charts. column_chart1.combine(line_chart1) # Add a chart title and some axis labels. Note, this is done via the # primary chart. column_chart1.set_title({ 'name': 'Combined chart - same Y axis'}) column_chart1.set_x_axis({'name': 'Test number'}) column_chart1.set_y_axis({'name': 'Sample length (mm)'}) # Insert the chart into the worksheet worksheet.insert_chart('E2', column_chart1) # # In the second example we will create a similar combined column and line # chart except that the secondary chart will have a secondary Y axis. # # Create a new column chart. This will use this as the primary chart. column_chart2 = workbook.add_chart({'type': 'column'}) # Configure the data series for the primary chart. column_chart2.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Create a new column chart. This will use this as the secondary chart. line_chart2 = workbook.add_chart({'type': 'line'}) # Configure the data series for the secondary chart. We also set a # secondary Y axis via (y2_axis). This is the only difference between # this and the first example, apart from the axis label below. line_chart2.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', 'y2_axis': True, }) # Combine the charts. column_chart2.combine(line_chart2) # Add a chart title and some axis labels. column_chart2.set_title({ 'name': 'Combine chart - secondary Y axis'}) column_chart2.set_x_axis({ 'name': 'Test number'}) column_chart2.set_y_axis({ 'name': 'Sample length (mm)'}) # Note: the y2 properties are on the secondary chart. line_chart2.set_y2_axis({'name': 'Target length (mm)'}) # Insert the chart into the worksheet worksheet.insert_chart('E18', column_chart2) workbook.close() XlsxWriter-1.1.2/examples/chart_data_table.py0000644000076500000240000000525113351302542021321 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Column charts with data tables using # Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_data_table.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Number', 'Batch 1', 'Batch 2'] data = [ [2, 3, 4, 5, 6, 7], [10, 40, 50, 20, 10, 50], [30, 60, 70, 50, 40, 30], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) worksheet.write_column('C2', data[2]) ####################################################################### # # Create a column chart with a data table. # chart1 = workbook.add_chart({'type': 'column'}) # Configure the first series. chart1.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. Note use of alternative syntax to define ranges. chart1.add_series({ 'name': ['Sheet1', 0, 2], 'categories': ['Sheet1', 1, 0, 6, 0], 'values': ['Sheet1', 1, 2, 6, 2], }) # Add a chart title and some axis labels. chart1.set_title({'name': 'Chart with Data Table'}) chart1.set_x_axis({'name': 'Test number'}) chart1.set_y_axis({'name': 'Sample length (mm)'}) # Set a default data table on the X-Axis. chart1.set_table() # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D2', chart1, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a column chart with a data table and legend keys. # chart2 = workbook.add_chart({'type': 'column'}) # Configure the first series. chart2.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. chart2.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title and some axis labels. chart2.set_title({'name': 'Data Table with legend keys'}) chart2.set_x_axis({'name': 'Test number'}) chart2.set_y_axis({'name': 'Sample length (mm)'}) # Set a data table on the X-Axis with the legend keys shown. chart2.set_table({'show_keys': True}) # Hide the chart legend since the keys are shown on the data table. chart2.set_legend({'position': 'none'}) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D18', chart2, {'x_offset': 25, 'y_offset': 10}) workbook.close() XlsxWriter-1.1.2/examples/chart_data_tools.py0000644000076500000240000001202413263123126021367 0ustar Johnstaff00000000000000####################################################################### # # A demo of an various Excel chart data tools that are available via # an XlsxWriter chart. # # These include, Trendlines, Data Labels, Error Bars, Drop Lines, # High-Low Lines and Up-Down Bars. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_data_tools.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Number', 'Data 1', 'Data 2'] data = [ [2, 3, 4, 5, 6, 7], [10, 40, 50, 20, 10, 50], [30, 60, 70, 50, 40, 30], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) worksheet.write_column('C2', data[2]) ####################################################################### # # Trendline example. # # Create a Line chart. chart1 = workbook.add_chart({'type': 'line'}) # Configure the first series with a polynomial trendline. chart1.add_series({ 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', 'trendline': { 'type': 'polynomial', 'order': 3, }, }) # Configure the second series with a moving average trendline. chart1.add_series({ 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', 'trendline': {'type': 'linear'}, }) # Add a chart title. chart1.set_title({'name': 'Chart with Trendlines'}) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D2', chart1, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Data Labels and Markers example. # # Create a Line chart. chart2 = workbook.add_chart({'type': 'line'}) # Configure the first series. chart2.add_series({ 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', 'data_labels': {'value': 1}, 'marker': {'type': 'automatic'}, }) # Configure the second series. chart2.add_series({ 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title. chart2.set_title({'name': 'Chart with Data Labels and Markers'}) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D18', chart2, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Error Bars example. # # Create a Line chart. chart3 = workbook.add_chart({'type': 'line'}) # Configure the first series. chart3.add_series({ 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', 'y_error_bars': {'type': 'standard_error'}, }) # Configure the second series. chart3.add_series({ 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title. chart3.set_title({'name': 'Chart with Error Bars'}) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D34', chart3, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Up-Down Bars example. # # Create a Line chart. chart4 = workbook.add_chart({'type': 'line'}) # Add the Up-Down Bars. chart4.set_up_down_bars() # Configure the first series. chart4.add_series({ 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure the second series. chart4.add_series({ 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title. chart4.set_title({'name': 'Chart with Up-Down Bars'}) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D50', chart4, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # High-Low Lines example. # # Create a Line chart. chart5 = workbook.add_chart({'type': 'line'}) # Add the High-Low lines. chart5.set_high_low_lines() # Configure the first series. chart5.add_series({ 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure the second series. chart5.add_series({ 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title. chart5.set_title({'name': 'Chart with High-Low Lines'}) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D66', chart5, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Drop Lines example. # # Create a Line chart. chart6 = workbook.add_chart({'type': 'line'}) # Add Drop Lines. chart6.set_drop_lines() # Configure the first series. chart6.add_series({ 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure the second series. chart6.add_series({ 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title. chart6.set_title({'name': 'Chart with Drop Lines'}) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D82', chart6, {'x_offset': 25, 'y_offset': 10}) workbook.close() XlsxWriter-1.1.2/examples/chart_date_axis.py0000644000076500000240000000272613263123126021207 0ustar Johnstaff00000000000000####################################################################### # # An example of creating an Excel charts with a date axis using # Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # from datetime import date import xlsxwriter workbook = xlsxwriter.Workbook('chart_date_axis.xlsx') worksheet = workbook.add_worksheet() chart = workbook.add_chart({'type': 'line'}) date_format = workbook.add_format({'num_format': 'dd/mm/yyyy'}) # Widen the first column to display the dates. worksheet.set_column('A:A', 12) # Some data to be plotted in the worksheet. dates = [date(2013, 1, 1), date(2013, 1, 2), date(2013, 1, 3), date(2013, 1, 4), date(2013, 1, 5), date(2013, 1, 6), date(2013, 1, 7), date(2013, 1, 8), date(2013, 1, 9), date(2013, 1, 10)] values = [10, 30, 20, 40, 20, 60, 50, 40, 30, 30] # Write the date to the worksheet. worksheet.write_column('A1', dates, date_format) worksheet.write_column('B1', values) # Add a series to the chart. chart.add_series({ 'categories': '=Sheet1!$A$1:$A$10', 'values': '=Sheet1!$B$1:$B$10', }) # Configure the X axis as a Date axis and set the max and min limits. chart.set_x_axis({ 'date_axis': True, 'min': date(2013, 1, 2), 'max': date(2013, 1, 9), }) # Turn off the legend. chart.set_legend({'none': True}) # Insert the chart into the worksheet. worksheet.insert_chart('D2', chart) workbook.close() XlsxWriter-1.1.2/examples/chart_doughnut.py0000644000076500000240000000771413263123126021105 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Doughnut charts with Python and XlsxWriter. # # The demo also shows how to set segment colors. It is possible to # define chart colors for most types of XlsxWriter charts # via the add_series() method. However, Pie/Doughnut charts are a special # case since each segment is represented as a point so it is necessary to # assign formatting to each point in the series. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_doughnut.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Category', 'Values'] data = [ ['Glazed', 'Chocolate', 'Cream'], [50, 35, 15], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) ####################################################################### # # Create a new chart object. # chart1 = workbook.add_chart({'type': 'doughnut'}) # Configure the series. Note the use of the list syntax to define ranges: chart1.add_series({ 'name': 'Doughnut sales data', 'categories': ['Sheet1', 1, 0, 3, 0], 'values': ['Sheet1', 1, 1, 3, 1], }) # Add a title. chart1.set_title({'name': 'Popular Doughnut Types'}) # Set an Excel chart style. Colors with white outline and shadow. chart1.set_style(10) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('C2', chart1, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a Doughnut chart with user defined segment colors. # # Create an example Doughnut chart like above. chart2 = workbook.add_chart({'type': 'doughnut'}) # Configure the series and add user defined segment colors. chart2.add_series({ 'name': 'Doughnut sales data', 'categories': '=Sheet1!$A$2:$A$4', 'values': '=Sheet1!$B$2:$B$4', 'points': [ {'fill': {'color': '#FA58D0'}}, {'fill': {'color': '#61210B'}}, {'fill': {'color': '#F5F6CE'}}, ], }) # Add a title. chart2.set_title({'name': 'Doughnut Chart with user defined colors'}) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('C18', chart2, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a Doughnut chart with rotation of the segments. # # Create an example Doughnut chart like above. chart3 = workbook.add_chart({'type': 'doughnut'}) # Configure the series. chart3.add_series({ 'name': 'Doughnut sales data', 'categories': '=Sheet1!$A$2:$A$4', 'values': '=Sheet1!$B$2:$B$4', }) # Add a title. chart3.set_title({'name': 'Doughnut Chart with segment rotation'}) # Change the angle/rotation of the first segment. chart3.set_rotation(90) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('C34', chart3, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a Doughnut chart with user defined hole size and other options. # # Create an example Doughnut chart like above. chart4 = workbook.add_chart({'type': 'doughnut'}) # Configure the series. chart4.add_series({ 'name': 'Doughnut sales data', 'categories': '=Sheet1!$A$2:$A$4', 'values': '=Sheet1!$B$2:$B$4', 'points': [ {'fill': {'color': '#FA58D0'}}, {'fill': {'color': '#61210B'}}, {'fill': {'color': '#F5F6CE'}}, ], }) # Set a 3D style. chart4.set_style(26) # Add a title. chart4.set_title({'name': 'Doughnut Chart with options applied'}) # Change the angle/rotation of the first segment. chart4.set_rotation(28) # Change the hole size. chart4.set_hole_size(33) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('C50', chart4, {'x_offset': 25, 'y_offset': 10}) workbook.close() XlsxWriter-1.1.2/examples/chart_gradient.py0000644000076500000240000000322513263123126021036 0ustar Johnstaff00000000000000####################################################################### # # An example of creating an Excel charts with gradient fills using # Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_gradient.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Number', 'Batch 1', 'Batch 2'] data = [ [2, 3, 4, 5, 6, 7], [10, 40, 50, 20, 10, 50], [30, 60, 70, 50, 40, 30], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) worksheet.write_column('C2', data[2]) # Create a new column chart. chart = workbook.add_chart({'type': 'column'}) # Configure the first series, including a gradient. chart.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', 'gradient': {'colors': ['#963735', '#F1DCDB']} }) # Configure the second series, including a gradient. chart.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', 'gradient': {'colors': ['#E36C0A', '#FCEADA']} }) # Set a gradient for the plotarea. chart.set_plotarea({ 'gradient': {'colors': ['#FFEFD1', '#F0EBD5', '#B69F66']} }) # Add some axis labels. chart.set_x_axis({'name': 'Test number'}) chart.set_y_axis({'name': 'Sample length (mm)'}) # Turn off the chart legend. chart.set_legend({'none': True}) # Insert the chart into the worksheet. worksheet.insert_chart('E2', chart) workbook.close() XlsxWriter-1.1.2/examples/chart_line.py0000644000076500000240000000313413263123126020167 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Line charts with Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_line.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Number', 'Batch 1', 'Batch 2'] data = [ [2, 3, 4, 5, 6, 7], [10, 40, 50, 20, 10, 50], [30, 60, 70, 50, 40, 30], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) worksheet.write_column('C2', data[2]) # Create a new chart object. In this case an embedded chart. chart1 = workbook.add_chart({'type': 'line'}) # Configure the first series. chart1.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. Note use of alternative syntax to define ranges. chart1.add_series({ 'name': ['Sheet1', 0, 2], 'categories': ['Sheet1', 1, 0, 6, 0], 'values': ['Sheet1', 1, 2, 6, 2], }) # Add a chart title and some axis labels. chart1.set_title ({'name': 'Results of sample analysis'}) chart1.set_x_axis({'name': 'Test number'}) chart1.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. Colors with white outline and shadow. chart1.set_style(10) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D2', chart1, {'x_offset': 25, 'y_offset': 10}) workbook.close() XlsxWriter-1.1.2/examples/chart_pareto.py0000644000076500000240000000412013263123126020526 0ustar Johnstaff00000000000000####################################################################### # # An example of creating of a Pareto chart with Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_pareto.xlsx') worksheet = workbook.add_worksheet() # Formats used in the workbook. bold = workbook.add_format({'bold': True}) percent_format = workbook.add_format({'num_format': '0.0%'}) # Widen the columns for visibility. worksheet.set_column('A:A', 15) worksheet.set_column('B:C', 10) # Add the worksheet data that the charts will refer to. headings = ['Reason', 'Number', 'Percentage'] reasons = [ 'Traffic', 'Child care', 'Public Transport', 'Weather', 'Overslept', 'Emergency', ] numbers = [60, 40, 20, 15, 10, 5] percents = [0.44, 0.667, 0.8, 0.9, 0.967, 1] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', reasons) worksheet.write_column('B2', numbers) worksheet.write_column('C2', percents, percent_format) # Create a new column chart. This will be the primary chart. column_chart = workbook.add_chart({'type': 'column'}) # Add a series. column_chart.add_series({ 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Add a chart title. column_chart.set_title({'name': 'Reasons for lateness'}) # Turn off the chart legend. column_chart.set_legend({'position': 'none'}) # Set the title and scale of the Y axes. Note, the secondary axis is set from # the primary chart. column_chart.set_y_axis({ 'name': 'Respondents (number)', 'min': 0, 'max': 120 }) column_chart.set_y2_axis({'max': 1}) # Create a new line chart. This will be the secondary chart. line_chart = workbook.add_chart({'type': 'line'}) # Add a series, on the secondary axis. line_chart.add_series({ 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', 'marker': {'type': 'automatic'}, 'y2_axis': 1, }) # Combine the charts. column_chart.combine(line_chart) # Insert the chart into the worksheet. worksheet.insert_chart('F2', column_chart) workbook.close() XlsxWriter-1.1.2/examples/chart_pattern.py0000644000076500000240000000306513263123126020720 0ustar Johnstaff00000000000000####################################################################### # # An example of an Excel chart with patterns using Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_pattern.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Shingle', 'Brick'] data = [ [105, 150, 130, 90 ], [50, 120, 100, 110], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) # Create a new Chart object. chart = workbook.add_chart({'type': 'column'}) # Configure the charts. Add two series with patterns. The gap is used to make # the patterns more visible. chart.add_series({ 'name': '=Sheet1!$A$1', 'values': '=Sheet1!$A$2:$A$5', 'pattern': { 'pattern': 'shingle', 'fg_color': '#804000', 'bg_color': '#c68c53' }, 'border': {'color': '#804000'}, 'gap': 70, }) chart.add_series({ 'name': '=Sheet1!$B$1', 'values': '=Sheet1!$B$2:$B$5', 'pattern': { 'pattern': 'horizontal_brick', 'fg_color': '#b30000', 'bg_color': '#ff6666' }, 'border': {'color': '#b30000'}, }) # Add a chart title and some axis labels. chart.set_title ({'name': 'Cladding types'}) chart.set_x_axis({'name': 'Region'}) chart.set_y_axis({'name': 'Number of houses'}) # Insert the chart into the worksheet. worksheet.insert_chart('D2', chart) workbook.close() XlsxWriter-1.1.2/examples/chart_pie.py0000644000076500000240000000573313263123126020024 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Pie charts with Python and XlsxWriter. # # The demo also shows how to set segment colors. It is possible to # define chart colors for most types of XlsxWriter charts # via the add_series() method. However, Pie/Doughnut charts are a special # case since each segment is represented as a point so it is necessary to # assign formatting to each point in the series. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_pie.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Category', 'Values'] data = [ ['Apple', 'Cherry', 'Pecan'], [60, 30, 10], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) ####################################################################### # # Create a new chart object. # chart1 = workbook.add_chart({'type': 'pie'}) # Configure the series. Note the use of the list syntax to define ranges: chart1.add_series({ 'name': 'Pie sales data', 'categories': ['Sheet1', 1, 0, 3, 0], 'values': ['Sheet1', 1, 1, 3, 1], }) # Add a title. chart1.set_title({'name': 'Popular Pie Types'}) # Set an Excel chart style. Colors with white outline and shadow. chart1.set_style(10) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('C2', chart1, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a Pie chart with user defined segment colors. # # Create an example Pie chart like above. chart2 = workbook.add_chart({'type': 'pie'}) # Configure the series and add user defined segment colors. chart2.add_series({ 'name': 'Pie sales data', 'categories': '=Sheet1!$A$2:$A$4', 'values': '=Sheet1!$B$2:$B$4', 'points': [ {'fill': {'color': '#5ABA10'}}, {'fill': {'color': '#FE110E'}}, {'fill': {'color': '#CA5C05'}}, ], }) # Add a title. chart2.set_title({'name': 'Pie Chart with user defined colors'}) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('C18', chart2, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a Pie chart with rotation of the segments. # # Create an example Pie chart like above. chart3 = workbook.add_chart({'type': 'pie'}) # Configure the series. chart3.add_series({ 'name': 'Pie sales data', 'categories': '=Sheet1!$A$2:$A$4', 'values': '=Sheet1!$B$2:$B$4', }) # Add a title. chart3.set_title({'name': 'Pie Chart with segment rotation'}) # Change the angle/rotation of the first segment. chart3.set_rotation(90) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('C34', chart3, {'x_offset': 25, 'y_offset': 10}) workbook.close() XlsxWriter-1.1.2/examples/chart_radar.py0000644000076500000240000000653113263123126020335 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Radar charts with Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_radar.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Number', 'Batch 1', 'Batch 2'] data = [ [2, 3, 4, 5, 6, 7], [30, 60, 70, 50, 40, 30], [25, 40, 50, 30, 50, 40], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) worksheet.write_column('C2', data[2]) ####################################################################### # # Create a new radar chart. # chart1 = workbook.add_chart({'type': 'radar'}) # Configure the first series. chart1.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. Note use of alternative syntax to define ranges. chart1.add_series({ 'name': ['Sheet1', 0, 2], 'categories': ['Sheet1', 1, 0, 6, 0], 'values': ['Sheet1', 1, 2, 6, 2], }) # Add a chart title and some axis labels. chart1.set_title ({'name': 'Results of sample analysis'}) chart1.set_x_axis({'name': 'Test number'}) chart1.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart1.set_style(11) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D2', chart1, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a radar chart with markers chart sub-type. # chart2 = workbook.add_chart({'type': 'radar', 'subtype': 'with_markers'}) # Configure the first series. chart2.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. chart2.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title and some axis labels. chart2.set_title ({'name': 'Radar Chart With Markers'}) chart2.set_x_axis({'name': 'Test number'}) chart2.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart2.set_style(12) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D18', chart2, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a filled radar chart sub-type. # chart3 = workbook.add_chart({'type': 'radar', 'subtype': 'filled'}) # Configure the first series. chart3.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. chart3.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title and some axis labels. chart3.set_title ({'name': 'Filled Radar Chart'}) chart3.set_x_axis({'name': 'Test number'}) chart3.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart3.set_style(13) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D34', chart3, {'x_offset': 25, 'y_offset': 10}) workbook.close() XlsxWriter-1.1.2/examples/chart_scatter.py0000644000076500000240000001245013263123126020706 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Scatter charts with Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_scatter.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Number', 'Batch 1', 'Batch 2'] data = [ [2, 3, 4, 5, 6, 7], [10, 40, 50, 20, 10, 50], [30, 60, 70, 50, 40, 30], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) worksheet.write_column('C2', data[2]) ####################################################################### # # Create a new scatter chart. # chart1 = workbook.add_chart({'type': 'scatter'}) # Configure the first series. chart1.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. Note use of alternative syntax to define ranges. chart1.add_series({ 'name': ['Sheet1', 0, 2], 'categories': ['Sheet1', 1, 0, 6, 0], 'values': ['Sheet1', 1, 2, 6, 2], }) # Add a chart title and some axis labels. chart1.set_title ({'name': 'Results of sample analysis'}) chart1.set_x_axis({'name': 'Test number'}) chart1.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart1.set_style(11) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D2', chart1, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a scatter chart sub-type with straight lines and markers. # chart2 = workbook.add_chart({'type': 'scatter', 'subtype': 'straight_with_markers'}) # Configure the first series. chart2.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. chart2.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title and some axis labels. chart2.set_title ({'name': 'Straight line with markers'}) chart2.set_x_axis({'name': 'Test number'}) chart2.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart2.set_style(12) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D18', chart2, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a scatter chart sub-type with straight lines and no markers. # chart3 = workbook.add_chart({'type': 'scatter', 'subtype': 'straight'}) # Configure the first series. chart3.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. chart3.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title and some axis labels. chart3.set_title ({'name': 'Straight line'}) chart3.set_x_axis({'name': 'Test number'}) chart3.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart3.set_style(13) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D34', chart3, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a scatter chart sub-type with smooth lines and markers. # chart4 = workbook.add_chart({'type': 'scatter', 'subtype': 'smooth_with_markers'}) # Configure the first series. chart4.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. chart4.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title and some axis labels. chart4.set_title ({'name': 'Smooth line with markers'}) chart4.set_x_axis({'name': 'Test number'}) chart4.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart4.set_style(14) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D50', chart4, {'x_offset': 25, 'y_offset': 10}) ####################################################################### # # Create a scatter chart sub-type with smooth lines and no markers. # chart5 = workbook.add_chart({'type': 'scatter', 'subtype': 'smooth'}) # Configure the first series. chart5.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure second series. chart5.add_series({ 'name': '=Sheet1!$C$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$C$2:$C$7', }) # Add a chart title and some axis labels. chart5.set_title ({'name': 'Smooth line'}) chart5.set_x_axis({'name': 'Test number'}) chart5.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart5.set_style(15) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D66', chart5, {'x_offset': 25, 'y_offset': 10}) workbook.close() XlsxWriter-1.1.2/examples/chart_secondary_axis.py0000644000076500000240000000263513263123126022260 0ustar Johnstaff00000000000000####################################################################### # # An example of creating an Excel Line chart with a secondary axis # using Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_secondary_axis.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Aliens', 'Humans'] data = [ [2, 3, 4, 5, 6, 7], [10, 40, 50, 20, 10, 50], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) # Create a new chart object. In this case an embedded chart. chart = workbook.add_chart({'type': 'line'}) # Configure a series with a secondary axis chart.add_series({ 'name': '=Sheet1!$A$1', 'values': '=Sheet1!$A$2:$A$7', 'y2_axis': 1, }) chart.add_series({ 'name': '=Sheet1!$B$1', 'values': '=Sheet1!$B$2:$B$7', }) chart.set_legend({'position': 'right'}) # Add a chart title and some axis labels. chart.set_title({'name': 'Survey results'}) chart.set_x_axis({'name': 'Days', }) chart.set_y_axis({'name': 'Population', 'major_gridlines': {'visible': 0}}) chart.set_y2_axis({'name': 'Laser wounds'}) # Insert the chart into the worksheet (with an offset). worksheet.insert_chart('D2', chart, {'x_offset': 25, 'y_offset': 10}) workbook.close() XlsxWriter-1.1.2/examples/chart_stock.py0000644000076500000240000000326613263123126020371 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Stock charts with Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # from datetime import datetime import xlsxwriter workbook = xlsxwriter.Workbook('chart_stock.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': 1}) date_format = workbook.add_format({'num_format': 'dd/mm/yyyy'}) chart = workbook.add_chart({'type': 'stock'}) # Add the worksheet data that the charts will refer to. headings = ['Date', 'High', 'Low', 'Close'] data = [ ['2007-01-01', '2007-01-02', '2007-01-03', '2007-01-04', '2007-01-05'], [27.2, 25.03, 19.05, 20.34, 18.5], [23.49, 19.55, 15.12, 17.84, 16.34], [25.45, 23.05, 17.32, 20.45, 17.34], ] worksheet.write_row('A1', headings, bold) for row in range(5): date = datetime.strptime(data[0][row], "%Y-%m-%d") worksheet.write(row + 1, 0, date, date_format) worksheet.write(row + 1, 1, data[1][row]) worksheet.write(row + 1, 2, data[2][row]) worksheet.write(row + 1, 3, data[3][row]) worksheet.set_column('A:D', 11) # Add a series for each of the High-Low-Close columns. chart.add_series({ 'categories': '=Sheet1!$A$2:$A$6', 'values': '=Sheet1!$B$2:$B$6', }) chart.add_series({ 'categories': '=Sheet1!$A$2:$A$6', 'values': '=Sheet1!$C$2:$C$6', }) chart.add_series({ 'categories': '=Sheet1!$A$2:$A$6', 'values': '=Sheet1!$D$2:$D$6', }) # Add a chart title and some axis labels. chart.set_title ({'name': 'High-Low-Close'}) chart.set_x_axis({'name': 'Date'}) chart.set_y_axis({'name': 'Share price'}) worksheet.insert_chart('E9', chart) workbook.close() XlsxWriter-1.1.2/examples/chart_styles.py0000644000076500000240000000253513263123126020567 0ustar Johnstaff00000000000000####################################################################### # # An example showing all 48 default chart styles available in Excel 2007 # using Python and XlsxWriter. Note, these styles are not the same as # the styles available in Excel 2013. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chart_styles.xlsx') # Show the styles for all of these chart types. chart_types = ['column', 'area', 'line', 'pie'] for chart_type in chart_types: # Add a worksheet for each chart type. worksheet = workbook.add_worksheet(chart_type.title()) worksheet.set_zoom(30) style_number = 1 # Create 48 charts, each with a different style. for row_num in range(0, 90, 15): for col_num in range(0, 64, 8): chart = workbook.add_chart({'type': chart_type}) chart.add_series({'values': '=Data!$A$1:$A$6'}) chart.set_title ({'name': 'Style %d' % style_number}) chart.set_legend({'none': True}) chart.set_style(style_number) worksheet.insert_chart(row_num, col_num , chart) style_number += 1 # Create a worksheet with data for the charts. data_worksheet = workbook.add_worksheet('Data') data = [10, 40, 50, 20, 10, 50] data_worksheet.write_column('A1', data) data_worksheet.hide() workbook.close() XlsxWriter-1.1.2/examples/chartsheet.py0000644000076500000240000000335613345201546020223 0ustar Johnstaff00000000000000####################################################################### # # An example of creating an Excel chart in a chartsheet with Python # and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('chartsheet.xlsx') # Add a worksheet to hold the data. worksheet = workbook.add_worksheet() # Add a chartsheet. A worksheet that only holds a chart. chartsheet = workbook.add_chartsheet() # Add a format for the headings. bold = workbook.add_format({'bold': 1}) # Add the worksheet data that the charts will refer to. headings = ['Number', 'Batch 1', 'Batch 2'] data = [ [2, 3, 4, 5, 6, 7], [10, 40, 50, 20, 10, 50], [30, 60, 70, 50, 40, 30], ] worksheet.write_row('A1', headings, bold) worksheet.write_column('A2', data[0]) worksheet.write_column('B2', data[1]) worksheet.write_column('C2', data[2]) # Create a new bar chart. chart1 = workbook.add_chart({'type': 'bar'}) # Configure the first series. chart1.add_series({ 'name': '=Sheet1!$B$1', 'categories': '=Sheet1!$A$2:$A$7', 'values': '=Sheet1!$B$2:$B$7', }) # Configure a second series. Note use of alternative syntax to define ranges. chart1.add_series({ 'name': ['Sheet1', 0, 2], 'categories': ['Sheet1', 1, 0, 6, 0], 'values': ['Sheet1', 1, 2, 6, 2], }) # Add a chart title and some axis labels. chart1.set_title ({'name': 'Results of sample analysis'}) chart1.set_x_axis({'name': 'Test number'}) chart1.set_y_axis({'name': 'Sample length (mm)'}) # Set an Excel chart style. chart1.set_style(11) # Add the chart to the chartsheet. chartsheet.set_chart(chart1) # Display the chartsheet as the active sheet when the workbook is opened. chartsheet.activate(); workbook.close() XlsxWriter-1.1.2/examples/comments1.py0000644000076500000240000000075113263123126017767 0ustar Johnstaff00000000000000############################################################################### # # An example of writing cell comments to a worksheet using Python and # XlsxWriter. # # For more advanced comment options see comments2.py. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('comments1.xlsx') worksheet = workbook.add_worksheet() worksheet.write('A1', 'Hello') worksheet.write_comment('A1', 'This is a comment') workbook.close() XlsxWriter-1.1.2/examples/comments2.py0000644000076500000240000001663113263123126017774 0ustar Johnstaff00000000000000############################################################################### # # An example of writing cell comments to a worksheet using Python and # XlsxWriter. # # Each of the worksheets demonstrates different features of cell comments. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('comments2.xlsx') worksheet1 = workbook.add_worksheet() worksheet2 = workbook.add_worksheet() worksheet3 = workbook.add_worksheet() worksheet4 = workbook.add_worksheet() worksheet5 = workbook.add_worksheet() worksheet6 = workbook.add_worksheet() worksheet7 = workbook.add_worksheet() worksheet8 = workbook.add_worksheet() text_wrap = workbook.add_format({'text_wrap': 1, 'valign': 'top'}) ############################################################################### # # Example 1. Demonstrates a simple cell comments without formatting. # comments. # # Set up some formatting. worksheet1.set_column('C:C', 25) worksheet1.set_row(2, 50) worksheet1.set_row(5, 50) # Simple ASCII string. cell_text = 'Hold the mouse over this cell to see the comment.' comment = 'This is a comment.' worksheet1.write('C3', cell_text, text_wrap) worksheet1.write_comment('C3', comment) ############################################################################### # # Example 2. Demonstrates visible and hidden comments. # # Set up some formatting. worksheet2.set_column('C:C', 25) worksheet2.set_row(2, 50) worksheet2.set_row(5, 50) cell_text = 'This cell comment is visible.' comment = 'Hello.' worksheet2.write('C3', cell_text, text_wrap) worksheet2.write_comment('C3', comment, {'visible': True}) cell_text = "This cell comment isn't visible (the default)." worksheet2.write('C6', cell_text, text_wrap) worksheet2.write_comment('C6', comment) ############################################################################### # # Example 3. Demonstrates visible and hidden comments set at the worksheet # level. # # Set up some formatting. worksheet3.set_column('C:C', 25) worksheet3.set_row(2, 50) worksheet3.set_row(5, 50) worksheet3.set_row(8, 50) # Make all comments on the worksheet visible. worksheet3.show_comments() cell_text = 'This cell comment is visible, explicitly.' comment = 'Hello.' worksheet3.write('C3', cell_text, text_wrap) worksheet3.write_comment('C3', comment, {'visible': 1}) cell_text = 'This cell comment is also visible because of show_comments().' worksheet3.write('C6', cell_text, text_wrap) worksheet3.write_comment('C6', comment) cell_text = 'However, we can still override it locally.' worksheet3.write('C9', cell_text, text_wrap) worksheet3.write_comment('C9', comment, {'visible': False}) ############################################################################### # # Example 4. Demonstrates changes to the comment box dimensions. # # Set up some formatting. worksheet4.set_column('C:C', 25) worksheet4.set_row(2, 50) worksheet4.set_row(5, 50) worksheet4.set_row(8, 50) worksheet4.set_row(15, 50) worksheet4.show_comments() cell_text = 'This cell comment is default size.' comment = 'Hello.' worksheet4.write('C3', cell_text, text_wrap) worksheet4.write_comment('C3', comment) cell_text = 'This cell comment is twice as wide.' worksheet4.write('C6', cell_text, text_wrap) worksheet4.write_comment('C6', comment, {'x_scale': 2}) cell_text = 'This cell comment is twice as high.' worksheet4.write('C9', cell_text, text_wrap) worksheet4.write_comment('C9', comment, {'y_scale': 2}) cell_text = 'This cell comment is scaled in both directions.' worksheet4.write('C16', cell_text, text_wrap) worksheet4.write_comment('C16', comment, {'x_scale': 1.2, 'y_scale': 0.8}) cell_text = 'This cell comment has width and height specified in pixels.' worksheet4.write('C19', cell_text, text_wrap) worksheet4.write_comment('C19', comment, {'width': 200, 'height': 20}) ############################################################################### # # Example 5. Demonstrates changes to the cell comment position. # worksheet5.set_column('C:C', 25) worksheet5.set_row(2, 50) worksheet5.set_row(5, 50) worksheet5.set_row(8, 50) worksheet5.set_row(11, 50) worksheet5.show_comments() cell_text = 'This cell comment is in the default position.' comment = 'Hello.' worksheet5.write('C3', cell_text, text_wrap) worksheet5.write_comment('C3', comment) cell_text = 'This cell comment has been moved to another cell.' worksheet5.write('C6', cell_text, text_wrap) worksheet5.write_comment('C6', comment, {'start_cell': 'E4'}) cell_text = 'This cell comment has been moved to another cell.' worksheet5.write('C9', cell_text, text_wrap) worksheet5.write_comment('C9', comment, {'start_row': 8, 'start_col': 4}) cell_text = 'This cell comment has been shifted within its default cell.' worksheet5.write('C12', cell_text, text_wrap) worksheet5.write_comment('C12', comment, {'x_offset': 30, 'y_offset': 12}) ############################################################################### # # Example 6. Demonstrates changes to the comment background color. # worksheet6.set_column('C:C', 25) worksheet6.set_row(2, 50) worksheet6.set_row(5, 50) worksheet6.set_row(8, 50) worksheet6.show_comments() cell_text = 'This cell comment has a different color.' comment = 'Hello.' worksheet6.write('C3', cell_text, text_wrap) worksheet6.write_comment('C3', comment, {'color': 'green'}) cell_text = 'This cell comment has the default color.' worksheet6.write('C6', cell_text, text_wrap) worksheet6.write_comment('C6', comment) cell_text = 'This cell comment has a different color.' worksheet6.write('C9', cell_text, text_wrap) worksheet6.write_comment('C9', comment, {'color': '#CCFFCC'}) ############################################################################### # # Example 7. Demonstrates how to set the cell comment author. # worksheet7.set_column('C:C', 30) worksheet7.set_row(2, 50) worksheet7.set_row(5, 50) worksheet7.set_row(8, 50) author = '' cell = 'C3' cell_text = ("Move the mouse over this cell and you will see 'Cell commented " "by (blank)' in the status bar at the bottom") comment = 'Hello.' worksheet7.write(cell, cell_text, text_wrap) worksheet7.write_comment(cell, comment) author = 'Python' cell = 'C6' cell_text = ("Move the mouse over this cell and you will see 'Cell commented " "by Python' in the status bar at the bottom") worksheet7.write(cell, cell_text, text_wrap) worksheet7.write_comment(cell, comment, {'author': author}) ############################################################################### # # Example 8. Demonstrates the need to explicitly set the row height. # # Set up some formatting. worksheet8.set_column('C:C', 25) worksheet8.set_row(2, 80) worksheet8.show_comments() cell_text = ('The height of this row has been adjusted explicitly using ' 'set_row(). The size of the comment box is adjusted ' 'accordingly by XlsxWriter.') comment = 'Hello.' worksheet8.write('C3', cell_text, text_wrap) worksheet8.write_comment('C3', comment) cell_text = ('The height of this row has been adjusted by Excel due to the ' 'text wrap property being set. Unfortunately this means that ' 'the height of the row is unknown to XlsxWriter at run time ' "and thus the comment box is stretched as well.\n\n" 'Use set_row() to specify the row height explicitly to avoid ' 'this problem.') worksheet8.write('C6', cell_text, text_wrap) worksheet8.write_comment('C6', comment) workbook.close() XlsxWriter-1.1.2/examples/conditional_format.py0000644000076500000240000002524413263123126021740 0ustar Johnstaff00000000000000############################################################################### # # Example of how to add conditional formatting to an XlsxWriter file. # # Conditional formatting allows you to apply a format to a cell or a # range of cells based on certain criteria. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('conditional_format.xlsx') worksheet1 = workbook.add_worksheet() worksheet2 = workbook.add_worksheet() worksheet3 = workbook.add_worksheet() worksheet4 = workbook.add_worksheet() worksheet5 = workbook.add_worksheet() worksheet6 = workbook.add_worksheet() worksheet7 = workbook.add_worksheet() worksheet8 = workbook.add_worksheet() worksheet9 = workbook.add_worksheet() # Add a format. Light red fill with dark red text. format1 = workbook.add_format({'bg_color': '#FFC7CE', 'font_color': '#9C0006'}) # Add a format. Green fill with dark green text. format2 = workbook.add_format({'bg_color': '#C6EFCE', 'font_color': '#006100'}) # Some sample data to run the conditional formatting against. data = [ [34, 72, 38, 30, 75, 48, 75, 66, 84, 86], [6, 24, 1, 84, 54, 62, 60, 3, 26, 59], [28, 79, 97, 13, 85, 93, 93, 22, 5, 14], [27, 71, 40, 17, 18, 79, 90, 93, 29, 47], [88, 25, 33, 23, 67, 1, 59, 79, 47, 36], [24, 100, 20, 88, 29, 33, 38, 54, 54, 88], [6, 57, 88, 28, 10, 26, 37, 7, 41, 48], [52, 78, 1, 96, 26, 45, 47, 33, 96, 36], [60, 54, 81, 66, 81, 90, 80, 93, 12, 55], [70, 5, 46, 14, 71, 19, 66, 36, 41, 21], ] ############################################################################### # # Example 1. # caption = ('Cells with values >= 50 are in light red. ' 'Values < 50 are in light green.') # Write the data. worksheet1.write('A1', caption) for row, row_data in enumerate(data): worksheet1.write_row(row + 2, 1, row_data) # Write a conditional format over a range. worksheet1.conditional_format('B3:K12', {'type': 'cell', 'criteria': '>=', 'value': 50, 'format': format1}) # Write another conditional format over the same range. worksheet1.conditional_format('B3:K12', {'type': 'cell', 'criteria': '<', 'value': 50, 'format': format2}) ############################################################################### # # Example 2. # caption = ('Values between 30 and 70 are in light red. ' 'Values outside that range are in light green.') worksheet2.write('A1', caption) for row, row_data in enumerate(data): worksheet2.write_row(row + 2, 1, row_data) worksheet2.conditional_format('B3:K12', {'type': 'cell', 'criteria': 'between', 'minimum': 30, 'maximum': 70, 'format': format1}) worksheet2.conditional_format('B3:K12', {'type': 'cell', 'criteria': 'not between', 'minimum': 30, 'maximum': 70, 'format': format2}) ############################################################################### # # Example 3. # caption = ('Duplicate values are in light red. ' 'Unique values are in light green.') worksheet3.write('A1', caption) for row, row_data in enumerate(data): worksheet3.write_row(row + 2, 1, row_data) worksheet3.conditional_format('B3:K12', {'type': 'duplicate', 'format': format1}) worksheet3.conditional_format('B3:K12', {'type': 'unique', 'format': format2}) ############################################################################### # # Example 4. # caption = ('Above average values are in light red. ' 'Below average values are in light green.') worksheet4.write('A1', caption) for row, row_data in enumerate(data): worksheet4.write_row(row + 2, 1, row_data) worksheet4.conditional_format('B3:K12', {'type': 'average', 'criteria': 'above', 'format': format1}) worksheet4.conditional_format('B3:K12', {'type': 'average', 'criteria': 'below', 'format': format2}) ############################################################################### # # Example 5. # caption = ('Top 10 values are in light red. ' 'Bottom 10 values are in light green.') worksheet5.write('A1', caption) for row, row_data in enumerate(data): worksheet5.write_row(row + 2, 1, row_data) worksheet5.conditional_format('B3:K12', {'type': 'top', 'value': '10', 'format': format1}) worksheet5.conditional_format('B3:K12', {'type': 'bottom', 'value': '10', 'format': format2}) ############################################################################### # # Example 6. # caption = ('Cells with values >= 50 are in light red. ' 'Values < 50 are in light green. Non-contiguous ranges.') # Write the data. worksheet6.write('A1', caption) for row, row_data in enumerate(data): worksheet6.write_row(row + 2, 1, row_data) # Write a conditional format over a range. worksheet6.conditional_format('B3:K6', {'type': 'cell', 'criteria': '>=', 'value': 50, 'format': format1, 'multi_range': 'B3:K6 B9:K12'}) # Write another conditional format over the same range. worksheet6.conditional_format('B3:K6', {'type': 'cell', 'criteria': '<', 'value': 50, 'format': format2, 'multi_range': 'B3:K6 B9:K12'}) ############################################################################### # # Example 7. # caption = 'Examples of color scales with default and user colors.' data = range(1, 13) worksheet7.write('A1', caption) worksheet7.write('B2', "2 Color Scale") worksheet7.write('D2', "2 Color Scale + user colors") worksheet7.write('G2', "3 Color Scale") worksheet7.write('I2', "3 Color Scale + user colors") for row, row_data in enumerate(data): worksheet7.write(row + 2, 1, row_data) worksheet7.write(row + 2, 3, row_data) worksheet7.write(row + 2, 6, row_data) worksheet7.write(row + 2, 8, row_data) worksheet7.conditional_format('B3:B14', {'type': '2_color_scale'}) worksheet7.conditional_format('D3:D14', {'type': '2_color_scale', 'min_color': "#FF0000", 'max_color': "#00FF00"}) worksheet7.conditional_format('G3:G14', {'type': '3_color_scale'}) worksheet7.conditional_format('I3:I14', {'type': '3_color_scale', 'min_color': "#C5D9F1", 'mid_color': "#8DB4E3", 'max_color': "#538ED5"}) ############################################################################### # # Example 8. # caption = 'Examples of data bars.' worksheet8.write('A1', caption) worksheet8.write('B2', "Default data bars") worksheet8.write('D2', "Bars only") worksheet8.write('F2', "With user color") worksheet8.write('H2', "Solid bars") worksheet8.write('J2', "Right to left") worksheet8.write('L2', "Excel 2010 style") worksheet8.write('N2', "Negative same as positive") data = range(1, 13) for row, row_data in enumerate(data): worksheet8.write(row + 2, 1, row_data) worksheet8.write(row + 2, 3, row_data) worksheet8.write(row + 2, 5, row_data) worksheet8.write(row + 2, 7, row_data) worksheet8.write(row + 2, 9, row_data) data = [-1, -2, -3, -2, -1, 0, 1, 2, 3, 2, 1, 0] for row, row_data in enumerate(data): worksheet8.write(row + 2, 11, row_data) worksheet8.write(row + 2, 13, row_data) worksheet8.conditional_format('B3:B14', {'type': 'data_bar'}) worksheet8.conditional_format('D3:D14', {'type': 'data_bar', 'bar_only': True}) worksheet8.conditional_format('F3:F14', {'type': 'data_bar', 'bar_color': '#63C384'}) worksheet8.conditional_format('H3:H14', {'type': 'data_bar', 'bar_solid': True}) worksheet8.conditional_format('J3:J14', {'type': 'data_bar', 'bar_direction': 'right'}) worksheet8.conditional_format('L3:L14', {'type': 'data_bar', 'data_bar_2010': True}) worksheet8.conditional_format('M3:N14', {'type': 'data_bar', 'bar_negative_color_same': True, 'bar_negative_border_color_same': True}) ############################################################################### # # Example 9. # caption = 'Examples of conditional formats with icon sets.' data = [ [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], ] worksheet9.write('A1', caption) for row, row_data in enumerate(data): worksheet9.write_row(row + 2, 1, row_data) worksheet9.conditional_format('B3:D3', {'type': 'icon_set', 'icon_style': '3_traffic_lights'}) worksheet9.conditional_format('B4:D4', {'type': 'icon_set', 'icon_style': '3_traffic_lights', 'reverse_icons': True}) worksheet9.conditional_format('B5:D5', {'type': 'icon_set', 'icon_style': '3_traffic_lights', 'icons_only': True}) worksheet9.conditional_format('B6:D6', {'type': 'icon_set', 'icon_style': '3_arrows'}) worksheet9.conditional_format('B7:E8', {'type': 'icon_set', 'icon_style': '4_arrows'}) worksheet9.conditional_format('B8:F8', {'type': 'icon_set', 'icon_style': '5_arrows'}) worksheet9.conditional_format('B9:F9', {'type': 'icon_set', 'icon_style': '5_ratings'}) workbook.close() XlsxWriter-1.1.2/examples/context_manager.py0000644000076500000240000000066013263123126021236 0ustar Johnstaff00000000000000############################################################################## # # A simple example using the XlsxWriter Python module and the "with" context # manager. This doesn't require an explicit close(). # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter with xlsxwriter.Workbook('hello_world.xlsx') as workbook: worksheet = workbook.add_worksheet() worksheet.write('A1', 'Hello world') XlsxWriter-1.1.2/examples/data_validate.py0000644000076500000240000001562613263123126020652 0ustar Johnstaff00000000000000############################################################################### # # Example of how to add data validation and dropdown lists to an # XlsxWriter file. # # Data validation is a feature of Excel which allows you to restrict # the data that a user enters in a cell and to display help and # warning messages. It also allows you to restrict input to values in # a drop down list. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # from datetime import date, time import xlsxwriter workbook = xlsxwriter.Workbook('data_validate.xlsx') worksheet = workbook.add_worksheet() # Add a format for the header cells. header_format = workbook.add_format({ 'border': 1, 'bg_color': '#C6EFCE', 'bold': True, 'text_wrap': True, 'valign': 'vcenter', 'indent': 1, }) # Set up layout of the worksheet. worksheet.set_column('A:A', 68) worksheet.set_column('B:B', 15) worksheet.set_column('D:D', 15) worksheet.set_row(0, 36) # Write the header cells and some data that will be used in the examples. heading1 = 'Some examples of data validation in XlsxWriter' heading2 = 'Enter values in this column' heading3 = 'Sample Data' worksheet.write('A1', heading1, header_format) worksheet.write('B1', heading2, header_format) worksheet.write('D1', heading3, header_format) worksheet.write_row('D3', ['Integers', 1, 10]) worksheet.write_row('D4', ['List data', 'open', 'high', 'close']) worksheet.write_row('D5', ['Formula', '=AND(F5=50,G5=60)', 50, 60]) # Example 1. Limiting input to an integer in a fixed range. # txt = 'Enter an integer between 1 and 10' worksheet.write('A3', txt) worksheet.data_validation('B3', {'validate': 'integer', 'criteria': 'between', 'minimum': 1, 'maximum': 10}) # Example 2. Limiting input to an integer outside a fixed range. # txt = 'Enter an integer that is not between 1 and 10 (using cell references)' worksheet.write('A5', txt) worksheet.data_validation('B5', {'validate': 'integer', 'criteria': 'not between', 'minimum': '=E3', 'maximum': '=F3'}) # Example 3. Limiting input to an integer greater than a fixed value. # txt = 'Enter an integer greater than 0' worksheet.write('A7', txt) worksheet.data_validation('B7', {'validate': 'integer', 'criteria': '>', 'value': 0}) # Example 4. Limiting input to an integer less than a fixed value. # txt = 'Enter an integer less than 10' worksheet.write('A9', txt) worksheet.data_validation('B9', {'validate': 'integer', 'criteria': '<', 'value': 10}) # Example 5. Limiting input to a decimal in a fixed range. # txt = 'Enter a decimal between 0.1 and 0.5' worksheet.write('A11', txt) worksheet.data_validation('B11', {'validate': 'decimal', 'criteria': 'between', 'minimum': 0.1, 'maximum': 0.5}) # Example 6. Limiting input to a value in a dropdown list. # txt = 'Select a value from a drop down list' worksheet.write('A13', txt) worksheet.data_validation('B13', {'validate': 'list', 'source': ['open', 'high', 'close']}) # Example 7. Limiting input to a value in a dropdown list. # txt = 'Select a value from a drop down list (using a cell range)' worksheet.write('A15', txt) worksheet.data_validation('B15', {'validate': 'list', 'source': '=$E$4:$G$4'}) # Example 8. Limiting input to a date in a fixed range. # txt = 'Enter a date between 1/1/2008 and 12/12/2008' worksheet.write('A17', txt) worksheet.data_validation('B17', {'validate': 'date', 'criteria': 'between', 'minimum': date(2013, 1, 1), 'maximum': date(2013, 12, 12)}) # Example 9. Limiting input to a time in a fixed range. # txt = 'Enter a time between 6:00 and 12:00' worksheet.write('A19', txt) worksheet.data_validation('B19', {'validate': 'time', 'criteria': 'between', 'minimum': time(6, 0), 'maximum': time(12, 0)}) # Example 10. Limiting input to a string greater than a fixed length. # txt = 'Enter a string longer than 3 characters' worksheet.write('A21', txt) worksheet.data_validation('B21', {'validate': 'length', 'criteria': '>', 'value': 3}) # Example 11. Limiting input based on a formula. # txt = 'Enter a value if the following is true "=AND(F5=50,G5=60)"' worksheet.write('A23', txt) worksheet.data_validation('B23', {'validate': 'custom', 'value': '=AND(F5=50,G5=60)'}) # Example 12. Displaying and modifying data validation messages. # txt = 'Displays a message when you select the cell' worksheet.write('A25', txt) worksheet.data_validation('B25', {'validate': 'integer', 'criteria': 'between', 'minimum': 1, 'maximum': 100, 'input_title': 'Enter an integer:', 'input_message': 'between 1 and 100'}) # Example 13. Displaying and modifying data validation messages. # txt = "Display a custom error message when integer isn't between 1 and 100" worksheet.write('A27', txt) worksheet.data_validation('B27', {'validate': 'integer', 'criteria': 'between', 'minimum': 1, 'maximum': 100, 'input_title': 'Enter an integer:', 'input_message': 'between 1 and 100', 'error_title': 'Input value is not valid!', 'error_message': 'It should be an integer between 1 and 100'}) # Example 14. Displaying and modifying data validation messages. # txt = "Display a custom info message when integer isn't between 1 and 100" worksheet.write('A29', txt) worksheet.data_validation('B29', {'validate': 'integer', 'criteria': 'between', 'minimum': 1, 'maximum': 100, 'input_title': 'Enter an integer:', 'input_message': 'between 1 and 100', 'error_title': 'Input value is not valid!', 'error_message': 'It should be an integer between 1 and 100', 'error_type': 'information'}) workbook.close() XlsxWriter-1.1.2/examples/datetimes.py0000644000076500000240000000341713263123126020042 0ustar Johnstaff00000000000000############################################################################## # # A simple program to write some dates and times to an Excel file # using the XlsxWriter Python module. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # from datetime import datetime import xlsxwriter # Create a workbook and add a worksheet. workbook = xlsxwriter.Workbook('datetimes.xlsx') worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': True}) # Expand the first columns so that the dates are visible. worksheet.set_column('A:B', 30) # Write the column headers. worksheet.write('A1', 'Formatted date', bold) worksheet.write('B1', 'Format', bold) # Create a datetime object to use in the examples. date_time = datetime.strptime('2013-01-23 12:30:05.123', '%Y-%m-%d %H:%M:%S.%f') # Examples date and time formats. In the output file compare how changing # the format codes change the appearance of the date. date_formats = ( 'dd/mm/yy', 'mm/dd/yy', 'dd m yy', 'd mm yy', 'd mmm yy', 'd mmmm yy', 'd mmmm yyy', 'd mmmm yyyy', 'dd/mm/yy hh:mm', 'dd/mm/yy hh:mm:ss', 'dd/mm/yy hh:mm:ss.000', 'hh:mm', 'hh:mm:ss', 'hh:mm:ss.000', ) # Start from first row after headers. row = 1 # Write the same date and time using each of the above formats. for date_format_str in date_formats: # Create a format for the date or time. date_format = workbook.add_format({'num_format': date_format_str, 'align': 'left'}) # Write the same date using different formats. worksheet.write_datetime(row, 0, date_time, date_format) # Also write the format string for comparison. worksheet.write_string(row, 1, date_format_str) row += 1 workbook.close() XlsxWriter-1.1.2/examples/defined_name.py0000644000076500000240000000222113263123126020451 0ustar Johnstaff00000000000000############################################################################## # # Example of how to create defined names with the XlsxWriter Python module. # # This method is used to define a user friendly name to represent a value, # a single cell or a range of cells in a workbook. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('defined_name.xlsx') worksheet1 = workbook.add_worksheet() worksheet2 = workbook.add_worksheet() # Define some global/workbook names. workbook.define_name('Exchange_rate', '=0.96') workbook.define_name('Sales', '=Sheet1!$G$1:$H$10') # Define a local/worksheet name. Over-rides the "Sales" name above. workbook.define_name('Sheet2!Sales', '=Sheet2!$G$1:$G$10') # Write some text in the file and one of the defined names in a formula. for worksheet in workbook.worksheets(): worksheet.set_column('A:A', 45) worksheet.write('A1', 'This worksheet contains some defined names.') worksheet.write('A2', 'See Formulas -> Name Manager above.') worksheet.write('A3', 'Example formula in cell B3 ->') worksheet.write('B3', '=Exchange_rate') workbook.close() XlsxWriter-1.1.2/examples/demo.py0000644000076500000240000000152013263123126017000 0ustar Johnstaff00000000000000############################################################################## # # A simple example of some of the features of the XlsxWriter Python module. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter # Create an new Excel file and add a worksheet. workbook = xlsxwriter.Workbook('demo.xlsx') worksheet = workbook.add_worksheet() # Widen the first column to make the text clearer. worksheet.set_column('A:A', 20) # Add a bold format to use to highlight cells. bold = workbook.add_format({'bold': True}) # Write some simple text. worksheet.write('A1', 'Hello') # Text with formatting. worksheet.write('A2', 'World', bold) # Write some numbers, with row/column notation. worksheet.write(2, 0, 123) worksheet.write(3, 0, 123.456) # Insert an image. worksheet.insert_image('B5', 'logo.png') workbook.close() XlsxWriter-1.1.2/examples/diagonal_border.py0000644000076500000240000000140513263123126021171 0ustar Johnstaff00000000000000############################################################################## # # A simple formatting example that demonstrates how to add diagonal cell # borders with XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('diag_border.xlsx') worksheet = workbook.add_worksheet() format1 = workbook.add_format({'diag_type': 1}) format2 = workbook.add_format({'diag_type': 2}) format3 = workbook.add_format({'diag_type': 3}) format4 = workbook.add_format({ 'diag_type': 3, 'diag_border': 7, 'diag_color': 'red', }) worksheet.write('B3', 'Text', format1) worksheet.write('B6', 'Text', format2) worksheet.write('B9', 'Text', format3) worksheet.write('B12', 'Text', format4) workbook.close() XlsxWriter-1.1.2/examples/django_simple.py0000644000076500000240000000337313340606703020702 0ustar Johnstaff00000000000000############################################################################## # # A simple Django view class to write an Excel file using the XlsxWriter # module. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import io from django.http import StreamingHttpResponse from django.views.generic import View import xlsxwriter def get_simple_table_data(): # Simulate a more complex table read. return [[1, 2, 3], [4, 5, 6], [7, 8, 9]] class MyView(View): def get(self, request): # Create an in-memory output file for the new workbook. output = io.BytesIO() # Even though the final file will be in memory the module uses temp # files during assembly for efficiency. To avoid this on servers that # don't allow temp files, for example the Google APP Engine, set the # 'in_memory' Workbook() constructor option as shown in the docs. workbook = xlsxwriter.Workbook(output) worksheet = workbook.add_worksheet() # Get some data to write to the spreadsheet. data = get_simple_table_data() # Write some test data. for row_num, columns in enumerate(data): for col_num, cell_data in enumerate(columns): worksheet.write(row_num, col_num, cell_data) # Close the workbook before sending the data. workbook.close() # Rewind the buffer. output.seek(0) # Set up the Http response. filename = 'django_simple.xlsx' response = HttpResponse( output, content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ) response['Content-Disposition'] = 'attachment; filename=%s' % filename return response XlsxWriter-1.1.2/examples/doc_properties.py0000644000076500000240000000150213263123126021075 0ustar Johnstaff00000000000000############################################################################## # # An example of adding document properties to a XlsxWriter file. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('doc_properties.xlsx') worksheet = workbook.add_worksheet() workbook.set_properties({ 'title': 'This is an example spreadsheet', 'subject': 'With document properties', 'author': 'John McNamara', 'manager': 'Dr. Heinz Doofenshmirtz', 'company': 'of Wolves', 'category': 'Example spreadsheets', 'keywords': 'Sample, Example, Properties', 'comments': 'Created with Python and XlsxWriter', 'status': 'Quo', }) worksheet.set_column('A:A', 70) worksheet.write('A1', "Select 'Workbook Properties' to see properties.") workbook.close() XlsxWriter-1.1.2/examples/headers_footers.py0000644000076500000240000000771713263123126021246 0ustar Johnstaff00000000000000###################################################################### # # This program shows several examples of how to set up headers and # footers with XlsxWriter. # # The control characters used in the header/footer strings are: # # Control Category Description # ======= ======== =========== # &L Justification Left # &C Center # &R Right # # &P Information Page number # &N Total number of pages # &D Date # &T Time # &F File name # &A Worksheet name # # &fontsize Font Font size # &"font,style" Font name and style # &U Single underline # &E Double underline # &S Strikethrough # &X Superscript # &Y Subscript # # &[Picture] Images Image placeholder # &G Same as &[Picture] # # && Miscellaneous Literal ampersand & # # See the main XlsxWriter documentation for more information. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('headers_footers.xlsx') preview = 'Select Print Preview to see the header and footer' ###################################################################### # # A simple example to start # worksheet1 = workbook.add_worksheet('Simple') header1 = '&CHere is some centered text.' footer1 = '&LHere is some left aligned text.' worksheet1.set_header(header1) worksheet1.set_footer(footer1) worksheet1.set_column('A:A', 50) worksheet1.write('A1', preview) ###################################################################### # # Insert a header image. # worksheet2 = workbook.add_worksheet('Image') header2 = '&L&G' # Adjust the page top margin to allow space for the header image. worksheet2.set_margins(top=1.3) worksheet2.set_header(header2, {'image_left': 'python-200x80.png'}) worksheet2.set_column('A:A', 50) worksheet2.write('A1', preview) ###################################################################### # # This is an example of some of the header/footer variables. # worksheet3 = workbook.add_worksheet('Variables') header3 = '&LPage &P of &N' + '&CFilename: &F' + '&RSheetname: &A' footer3 = '&LCurrent date: &D' + '&RCurrent time: &T' worksheet3.set_header(header3) worksheet3.set_footer(footer3) worksheet3.set_column('A:A', 50) worksheet3.write('A1', preview) worksheet3.write('A21', 'Next sheet') worksheet3.set_h_pagebreaks([20]) ###################################################################### # # This example shows how to use more than one font # worksheet4 = workbook.add_worksheet('Mixed fonts') header4 = '&C&"Courier New,Bold"Hello &"Arial,Italic"World' footer4 = '&C&"Symbol"e&"Arial" = mc&X2' worksheet4.set_header(header4) worksheet4.set_footer(footer4) worksheet4.set_column('A:A', 50) worksheet4.write('A1', preview) ###################################################################### # # Example of line wrapping # worksheet5 = workbook.add_worksheet('Word wrap') header5 = "&CHeading 1\nHeading 2" worksheet5.set_header(header5) worksheet5.set_column('A:A', 50) worksheet5.write('A1', preview) ###################################################################### # # Example of inserting a literal ampersand & # worksheet6 = workbook.add_worksheet('Ampersand') header6 = '&CCuriouser && Curiouser - Attorneys at Law' worksheet6.set_header(header6) worksheet6.set_column('A:A', 50) worksheet6.write('A1', preview) workbook.close() XlsxWriter-1.1.2/examples/hello_world.py0000644000076500000240000000056113263123126020372 0ustar Johnstaff00000000000000############################################################################## # # A hello world spreadsheet using the XlsxWriter Python module. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('hello_world.xlsx') worksheet = workbook.add_worksheet() worksheet.write('A1', 'Hello world') workbook.close() XlsxWriter-1.1.2/examples/hide_row_col.py0000644000076500000240000000167213263123126020521 0ustar Johnstaff00000000000000############################################################################### # # Example of how to hide rows and columns in XlsxWriter. In order to # hide rows without setting each one, (of approximately 1 million rows), # Excel uses an optimizations to hide all rows that don't have data. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('hide_row_col.xlsx') worksheet = workbook.add_worksheet() # Write some data. worksheet.write('D1', 'Some hidden columns.') worksheet.write('A8', 'Some hidden rows.') # Hide all rows without data. worksheet.set_default_row(hide_unused_rows=True) # Set the height of empty rows that we do want to display even if it is # the default height. for row in range(1, 7): worksheet.set_row(row, 15) # Columns can be hidden explicitly. This doesn't increase the file size.. worksheet.set_column('G:XFD', None, None, {'hidden': True}) workbook.close() XlsxWriter-1.1.2/examples/hide_sheet.py0000644000076500000240000000131613263123126020160 0ustar Johnstaff00000000000000####################################################################### # # Example of how to hide a worksheet with XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('hide_sheet.xlsx') worksheet1 = workbook.add_worksheet() worksheet2 = workbook.add_worksheet() worksheet3 = workbook.add_worksheet() worksheet1.set_column('A:A', 30) worksheet2.set_column('A:A', 30) worksheet3.set_column('A:A', 30) # Hide Sheet2. It won't be visible until it is unhidden in Excel. worksheet2.hide() worksheet1.write('A1', 'Sheet2 is hidden') worksheet2.write('A1', "Now it's my turn to find you!") worksheet3.write('A1', 'Sheet2 is hidden') workbook.close() XlsxWriter-1.1.2/examples/http_server_py2.py0000644000076500000240000000332213263123126021215 0ustar Johnstaff00000000000000############################################################################## # # Example of using Python and XlsxWriter to create an Excel XLSX file in an in # memory string suitable for serving via SimpleHTTPServer or Django or with # the Google App Engine. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # # Note: This is a Python 2 example. For Python 3 see http_server_py3.py. import SimpleHTTPServer import SocketServer import io import xlsxwriter class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_GET(self): # Create an in-memory output file for the new workbook. output = io.BytesIO() # Even though the final file will be in memory the module uses temp # files during assembly for efficiency. To avoid this on servers that # don't allow temp files, for example the Google APP Engine, set the # 'in_memory' constructor option to True: workbook = xlsxwriter.Workbook(output, {'in_memory': True}) worksheet = workbook.add_worksheet() # Write some test data. worksheet.write(0, 0, 'Hello, world!') # Close the workbook before streaming the data. workbook.close() # Rewind the buffer. output.seek(0) # Construct a server response. self.send_response(200) self.send_header('Content-Disposition', 'attachment; filename=test.xlsx') self.send_header('Content-type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') self.end_headers() self.wfile.write(output.read()) return print('Server listening on port 8000...') httpd = SocketServer.TCPServer(('', 8000), Handler) httpd.serve_forever() XlsxWriter-1.1.2/examples/http_server_py3.py0000644000076500000240000000332013263123126021214 0ustar Johnstaff00000000000000############################################################################## # # Example of using Python and XlsxWriter to create an Excel XLSX file in an in # memory string suitable for serving via SimpleHTTPRequestHandler or Django or # with the Google App Engine. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # # Note: This is a Python 3 example. For Python 2 see http_server_py2.py. import http.server import socketserver import io import xlsxwriter class Handler(http.server.SimpleHTTPRequestHandler): def do_GET(self): # Create an in-memory output file for the new workbook. output = io.BytesIO() # Even though the final file will be in memory the module uses temp # files during assembly for efficiency. To avoid this on servers that # don't allow temp files, for example the Google APP Engine, set the # 'in_memory' constructor option to True: workbook = xlsxwriter.Workbook(output, {'in_memory': True}) worksheet = workbook.add_worksheet() # Write some test data. worksheet.write(0, 0, 'Hello, world!') # Close the workbook before streaming the data. workbook.close() # Rewind the buffer. output.seek(0) # Construct a server response. self.send_response(200) self.send_header('Content-Disposition', 'attachment; filename=test.xlsx') self.send_header('Content-type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') self.end_headers() self.wfile.write(output.read()) return print('Server listening on port 8000...') httpd = socketserver.TCPServer(('', 8000), Handler) httpd.serve_forever() XlsxWriter-1.1.2/examples/hyperlink.py0000644000076500000240000000207713263123126020071 0ustar Johnstaff00000000000000############################################################################### # # Example of how to use the XlsxWriter module to write hyperlinks # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter # Create a new workbook and add a worksheet workbook = xlsxwriter.Workbook('hyperlink.xlsx') worksheet = workbook.add_worksheet('Hyperlinks') # Format the first column worksheet.set_column('A:A', 30) # Add a sample alternative link format. red_format = workbook.add_format({ 'font_color': 'red', 'bold': 1, 'underline': 1, 'font_size': 12, }) # Write some hyperlinks worksheet.write_url('A1', 'http://www.python.org/') # Implicit format. worksheet.write_url('A3', 'http://www.python.org/', string='Python Home') worksheet.write_url('A5', 'http://www.python.org/', tip='Click here') worksheet.write_url('A7', 'http://www.python.org/', red_format) worksheet.write_url('A9', 'mailto:jmcnamara@cpan.org', string='Mail me') # Write a URL that isn't a hyperlink worksheet.write_string('A11', 'http://www.python.org/') workbook.close() XlsxWriter-1.1.2/examples/images.py0000644000076500000240000000164513263123126017331 0ustar Johnstaff00000000000000############################################################################## # # An example of inserting images into a worksheet using the XlsxWriter # Python module. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter # Create an new Excel file and add a worksheet. workbook = xlsxwriter.Workbook('images.xlsx') worksheet = workbook.add_worksheet() # Widen the first column to make the text clearer. worksheet.set_column('A:A', 30) # Insert an image. worksheet.write('A2', 'Insert an image in a cell:') worksheet.insert_image('B2', 'python.png') # Insert an image offset in the cell. worksheet.write('A12', 'Insert an image with an offset:') worksheet.insert_image('B12', 'python.png', {'x_offset': 15, 'y_offset': 10}) # Insert an image with scaling. worksheet.write('A23', 'Insert a scaled image:') worksheet.insert_image('B23', 'python.png', {'x_scale': 0.5, 'y_scale': 0.5}) workbook.close() XlsxWriter-1.1.2/examples/images_bytesio.py0000644000076500000240000000260413263123126021063 0ustar Johnstaff00000000000000############################################################################## # # An example of inserting images from a Python BytesIO byte stream into a # worksheet using the XlsxWriter module. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # # Import the byte stream handler. from io import BytesIO # Import urlopen() for either Python 2 or 3. try: from urllib.request import urlopen except ImportError: from urllib2 import urlopen import xlsxwriter # Create the workbook and add a worksheet. workbook = xlsxwriter.Workbook('images_bytesio.xlsx') worksheet = workbook.add_worksheet() # Read an image from a remote url. url = 'https://raw.githubusercontent.com/jmcnamara/XlsxWriter/' + \ 'master/examples/logo.png' image_data = BytesIO(urlopen(url).read()) # Write the byte stream image to a cell. Note, the filename must be # specified. In this case it will be read from url string. worksheet.insert_image('B2', url, {'image_data': image_data}) # Read a local image file into a a byte stream. Note, the insert_image() # method can do this directly. This is for illustration purposes only. filename = 'python.png' image_file = open(filename, 'rb') image_data = BytesIO(image_file.read()) image_file.close() # Write the byte stream image to a cell. The filename must be specified. worksheet.insert_image('B8', filename, {'image_data': image_data}) workbook.close() XlsxWriter-1.1.2/examples/inheritance1.py0000644000076500000240000000340613300041207020422 0ustar Johnstaff00000000000000############################################################################## # # Example of how to subclass the Workbook and Worksheet objects. We also # override the default worksheet.write() method to show how that is done. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter from xlsxwriter.workbook import Workbook from xlsxwriter.worksheet import Worksheet from xlsxwriter.worksheet import convert_cell_args from xlsxwriter.compatibility import str_types class MyWorksheet(Worksheet): """ Subclass of the XlsxWriter Worksheet class to override the default write() method. """ @convert_cell_args def write(self, row, col, *args): data = args[0] # Reverse strings to demonstrate the overridden method. if isinstance(data, str_types): data = data[::-1] return self.write_string(row, col, data) else: # Call the parent version of write() as usual for other data. return super(MyWorksheet, self).write(row, col, *args) class MyWorkbook(Workbook): """ Subclass of the XlsxWriter Workbook class to override the default Worksheet class with our custom class. """ def add_worksheet(self, name=None): # Overwrite add_worksheet() to create a MyWorksheet object. worksheet = super(MyWorkbook, self).add_worksheet(name, MyWorksheet) return worksheet # Create a new MyWorkbook object. workbook = MyWorkbook('inheritance1.xlsx') # The code from now on will be the same as a normal "Workbook" program. worksheet = workbook.add_worksheet() # Write some data to test the subclassing. worksheet.write('A1', 'Hello') worksheet.write('A2', 'World') worksheet.write('A3', 123) worksheet.write('A4', 345) workbook.close() XlsxWriter-1.1.2/examples/inheritance2.py0000644000076500000240000000752213300041227020430 0ustar Johnstaff00000000000000############################################################################## # # Example of how to subclass the Workbook and Worksheet objects. See also the # simpler inheritance1.py example. # # In this example we see an approach to implementing a simulated autofit in a # user application. This works by overriding the write_string() method to # track the maximum width string in each column and then set the column # widths. # # Note: THIS ISN'T A FULLY FUNCTIONAL AUTOFIT EXAMPLE. It is only a proof or # concept or a framework to try out solutions. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter from xlsxwriter.workbook import Workbook from xlsxwriter.worksheet import Worksheet from xlsxwriter.worksheet import convert_cell_args def excel_string_width(str): """ Calculate the length of the string in Excel character units. This is only an example and won't give accurate results. It will need to be replaced by something more rigorous. """ string_width = len(str) if string_width == 0: return 0 else: return string_width * 1.1 class MyWorksheet(Worksheet): """ Subclass of the XlsxWriter Worksheet class to override the default write_string() method. """ @convert_cell_args def write_string(self, row, col, string, cell_format=None): # Overridden write_string() method to store the maximum string width # seen in each column. # Check that row and col are valid and store max and min values. if self._check_dimensions(row, col): return -1 # Set the min width for the cell. In some cases this might be the # default width of 8.43. In this case we use 0 and adjust for all # string widths. min_width = 0 # Check if it the string is the largest we have seen for this column. string_width = excel_string_width(string) if string_width > min_width: max_width = self.max_column_widths.get(col, min_width) if string_width > max_width: self.max_column_widths[col] = string_width # Now call the parent version of write_string() as usual. return super(MyWorksheet, self).write_string(row, col, string, cell_format) class MyWorkbook(Workbook): """ Subclass of the XlsxWriter Workbook class to override the default Worksheet class with our custom class. """ def add_worksheet(self, name=None): # Overwrite add_worksheet() to create a MyWorksheet object. # Also add an Worksheet attribute to store the column widths. worksheet = super(MyWorkbook, self).add_worksheet(name, MyWorksheet) worksheet.max_column_widths = {} return worksheet def close(self): # We apply the stored column widths for each worksheet when we close # the workbook. This will override any other set_column() values that # may have been applied. This could be handled in the application code # below, instead. for worksheet in self.worksheets(): for column, width in worksheet.max_column_widths.items(): worksheet.set_column(column, column, width) return super(MyWorkbook, self).close() # Create a new MyWorkbook object. workbook = MyWorkbook('inheritance2.xlsx') # The code from now on will be the same as a normal "Workbook" program. worksheet = workbook.add_worksheet() # Write some data to test column fitting. worksheet.write('A1', 'F') worksheet.write('B3', 'Foo') worksheet.write('C1', 'F') worksheet.write('C2', 'Fo') worksheet.write('C3', 'Foo') worksheet.write('C4', 'Food') worksheet.write('D1', 'This is a longer string') # Write a string in row-col notation. worksheet.write(0, 4, 'Hello World') # Write a number. worksheet.write(0, 5, 123456) workbook.close() XlsxWriter-1.1.2/examples/macros.py0000644000076500000240000000202213263123126017336 0ustar Johnstaff00000000000000####################################################################### # # An example of adding macros to an XlsxWriter file using a VBA project # file extracted from an existing Excel xlsm file. # # The vba_extract.py utility supplied with XlsxWriter can be used to extract # the vbaProject.bin file. # # An embedded macro is connected to a form button on the worksheet. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter # Note the file extension should be .xlsm. workbook = xlsxwriter.Workbook('macros.xlsm') worksheet = workbook.add_worksheet() worksheet.set_column('A:A', 30) # Add the VBA project binary. workbook.add_vba_project('./vbaProject.bin') # Show text for the end user. worksheet.write('A3', 'Press the button to say hello.') # Add a button tied to a macro in the VBA project. worksheet.insert_button('B3', {'macro': 'say_hello', 'caption': 'Press Me', 'width': 80, 'height': 30}) workbook.close() XlsxWriter-1.1.2/examples/merge1.py0000644000076500000240000000164213263123126017241 0ustar Johnstaff00000000000000############################################################################## # # A simple example of merging cells with the XlsxWriter Python module. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter # Create an new Excel file and add a worksheet. workbook = xlsxwriter.Workbook('merge1.xlsx') worksheet = workbook.add_worksheet() # Increase the cell size of the merged cells to highlight the formatting. worksheet.set_column('B:D', 12) worksheet.set_row(3, 30) worksheet.set_row(6, 30) worksheet.set_row(7, 30) # Create a format to use in the merged range. merge_format = workbook.add_format({ 'bold': 1, 'border': 1, 'align': 'center', 'valign': 'vcenter', 'fg_color': 'yellow'}) # Merge 3 cells. worksheet.merge_range('B4:D4', 'Merged Range', merge_format) # Merge 3 cells over two rows. worksheet.merge_range('B7:D8', 'Merged Range', merge_format) workbook.close() XlsxWriter-1.1.2/examples/merge_rich_string.py0000644000076500000240000000230513263123126021550 0ustar Johnstaff00000000000000############################################################################## # # An example of merging cells which contain a rich string using the # XlsxWriter Python module. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter # Create an new Excel file and add a worksheet. workbook = xlsxwriter.Workbook('merge_rich_string.xlsx') worksheet = workbook.add_worksheet() # Set up some formats to use. red = workbook.add_format({'color': 'red'}) blue = workbook.add_format({'color': 'blue'}) cell_format = workbook.add_format({'align': 'center', 'valign': 'vcenter', 'border': 1}) # We can only write simple types to merged ranges so we write a blank string. worksheet.merge_range('B2:E5', "", cell_format) # We then overwrite the first merged cell with a rich string. Note that we # must also pass the cell format used in the merged cells format at the end. worksheet.write_rich_string('B2', 'This is ', red, 'red', ' and this is ', blue, 'blue', cell_format) workbook.close() XlsxWriter-1.1.2/examples/outline.py0000644000076500000240000001512413263123126017540 0ustar Johnstaff00000000000000############################################################################### # # Example of how use Python and XlsxWriter to generate Excel outlines and # grouping. # # Excel allows you to group rows or columns so that they can be hidden or # displayed with a single mouse click. This feature is referred to as outlines. # # Outlines can reduce complex data down to a few salient sub-totals or # summaries. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter # Create a new workbook and add some worksheets workbook = xlsxwriter.Workbook('outline.xlsx') worksheet1 = workbook.add_worksheet('Outlined Rows') worksheet2 = workbook.add_worksheet('Collapsed Rows') worksheet3 = workbook.add_worksheet('Outline Columns') worksheet4 = workbook.add_worksheet('Outline levels') # Add a general format bold = workbook.add_format({'bold': 1}) ############################################################################### # # Example 1: A worksheet with outlined rows. It also includes SUBTOTAL() # functions so that it looks like the type of automatic outlines that are # generated when you use the Excel Data->SubTotals menu item. # # For outlines the important parameters are 'level' and 'hidden'. Rows with # the same 'level' are grouped together. The group will be collapsed if # 'hidden' is enabled. The parameters 'height' and 'cell_format' are assigned # default values if they are None. # worksheet1.set_row(1, None, None, {'level': 2}) worksheet1.set_row(2, None, None, {'level': 2}) worksheet1.set_row(3, None, None, {'level': 2}) worksheet1.set_row(4, None, None, {'level': 2}) worksheet1.set_row(5, None, None, {'level': 1}) worksheet1.set_row(6, None, None, {'level': 2}) worksheet1.set_row(7, None, None, {'level': 2}) worksheet1.set_row(8, None, None, {'level': 2}) worksheet1.set_row(9, None, None, {'level': 2}) worksheet1.set_row(10, None, None, {'level': 1}) # Adjust the column width for clarity worksheet1.set_column('A:A', 20) # Add the data, labels and formulas worksheet1.write('A1', 'Region', bold) worksheet1.write('A2', 'North') worksheet1.write('A3', 'North') worksheet1.write('A4', 'North') worksheet1.write('A5', 'North') worksheet1.write('A6', 'North Total', bold) worksheet1.write('B1', 'Sales', bold) worksheet1.write('B2', 1000) worksheet1.write('B3', 1200) worksheet1.write('B4', 900) worksheet1.write('B5', 1200) worksheet1.write('B6', '=SUBTOTAL(9,B2:B5)', bold) worksheet1.write('A7', 'South') worksheet1.write('A8', 'South') worksheet1.write('A9', 'South') worksheet1.write('A10', 'South') worksheet1.write('A11', 'South Total', bold) worksheet1.write('B7', 400) worksheet1.write('B8', 600) worksheet1.write('B9', 500) worksheet1.write('B10', 600) worksheet1.write('B11', '=SUBTOTAL(9,B7:B10)', bold) worksheet1.write('A12', 'Grand Total', bold) worksheet1.write('B12', '=SUBTOTAL(9,B2:B10)', bold) ############################################################################### # # Example 2: A worksheet with outlined rows. This is the same as the # previous example except that the rows are collapsed. # Note: We need to indicate the rows that contains the collapsed symbol '+' # with the optional parameter, 'collapsed'. The group will be then be # collapsed if 'hidden' is True. # worksheet2.set_row(1, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(2, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(3, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(4, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(5, None, None, {'level': 1, 'hidden': True}) worksheet2.set_row(6, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(7, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(8, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(9, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(10, None, None, {'level': 1, 'hidden': True}) worksheet2.set_row(11, None, None, {'collapsed': True}) # Adjust the column width for clarity worksheet2.set_column('A:A', 20) # Add the data, labels and formulas worksheet2.write('A1', 'Region', bold) worksheet2.write('A2', 'North') worksheet2.write('A3', 'North') worksheet2.write('A4', 'North') worksheet2.write('A5', 'North') worksheet2.write('A6', 'North Total', bold) worksheet2.write('B1', 'Sales', bold) worksheet2.write('B2', 1000) worksheet2.write('B3', 1200) worksheet2.write('B4', 900) worksheet2.write('B5', 1200) worksheet2.write('B6', '=SUBTOTAL(9,B2:B5)', bold) worksheet2.write('A7', 'South') worksheet2.write('A8', 'South') worksheet2.write('A9', 'South') worksheet2.write('A10', 'South') worksheet2.write('A11', 'South Total', bold) worksheet2.write('B7', 400) worksheet2.write('B8', 600) worksheet2.write('B9', 500) worksheet2.write('B10', 600) worksheet2.write('B11', '=SUBTOTAL(9,B7:B10)', bold) worksheet2.write('A12', 'Grand Total', bold) worksheet2.write('B12', '=SUBTOTAL(9,B2:B10)', bold) ############################################################################### # # Example 3: Create a worksheet with outlined columns. # data = [ ['Month', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Total'], ['North', 50, 20, 15, 25, 65, 80, '=SUM(B2:G2)'], ['South', 10, 20, 30, 50, 50, 50, '=SUM(B3:G3)'], ['East', 45, 75, 50, 15, 75, 100, '=SUM(B4:G4)'], ['West', 15, 15, 55, 35, 20, 50, '=SUM(B5:G5)']] # Add bold format to the first row. worksheet3.set_row(0, None, bold) # Set column formatting and the outline level. worksheet3.set_column('A:A', 10, bold) worksheet3.set_column('B:G', 5, None, {'level': 1}) worksheet3.set_column('H:H', 10) # Write the data and a formula for row, data_row in enumerate(data): worksheet3.write_row(row, 0, data_row) worksheet3.write('H6', '=SUM(H2:H5)', bold) ############################################################################### # # Example 4: Show all possible outline levels. # levels = [ 'Level 1', 'Level 2', 'Level 3', 'Level 4', 'Level 5', 'Level 6', 'Level 7', 'Level 6', 'Level 5', 'Level 4', 'Level 3', 'Level 2', 'Level 1'] worksheet4.write_column('A1', levels) worksheet4.set_row(0, None, None, {'level': 1}) worksheet4.set_row(1, None, None, {'level': 2}) worksheet4.set_row(2, None, None, {'level': 3}) worksheet4.set_row(3, None, None, {'level': 4}) worksheet4.set_row(4, None, None, {'level': 5}) worksheet4.set_row(5, None, None, {'level': 6}) worksheet4.set_row(6, None, None, {'level': 7}) worksheet4.set_row(7, None, None, {'level': 6}) worksheet4.set_row(8, None, None, {'level': 5}) worksheet4.set_row(9, None, None, {'level': 4}) worksheet4.set_row(10, None, None, {'level': 3}) worksheet4.set_row(11, None, None, {'level': 2}) worksheet4.set_row(12, None, None, {'level': 1}) workbook.close() XlsxWriter-1.1.2/examples/outline_collapsed.py0000644000076500000240000001705213263123126021570 0ustar Johnstaff00000000000000############################################################################### # # Example of how to use Python and XlsxWriter to generate Excel outlines and # grouping. # # These examples focus mainly on collapsed outlines. See also the # outlines.py example program for more general examples. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter # Create a new workbook and add some worksheets workbook = xlsxwriter.Workbook('outline_collapsed.xlsx') worksheet1 = workbook.add_worksheet('Outlined Rows') worksheet2 = workbook.add_worksheet('Collapsed Rows 1') worksheet3 = workbook.add_worksheet('Collapsed Rows 2') worksheet4 = workbook.add_worksheet('Collapsed Rows 3') worksheet5 = workbook.add_worksheet('Outline Columns') worksheet6 = workbook.add_worksheet('Collapsed Columns') # Add a general format bold = workbook.add_format({'bold': 1}) # This function will generate the same data and sub-totals on each worksheet. # Used in the first 4 examples. # def create_sub_totals(worksheet): # Adjust the column width for clarity. worksheet.set_column('A:A', 20) # Add the data, labels and formulas. worksheet.write('A1', 'Region', bold) worksheet.write('A2', 'North') worksheet.write('A3', 'North') worksheet.write('A4', 'North') worksheet.write('A5', 'North') worksheet.write('A6', 'North Total', bold) worksheet.write('B1', 'Sales', bold) worksheet.write('B2', 1000) worksheet.write('B3', 1200) worksheet.write('B4', 900) worksheet.write('B5', 1200) worksheet.write('B6', '=SUBTOTAL(9,B2:B5)', bold) worksheet.write('A7', 'South') worksheet.write('A8', 'South') worksheet.write('A9', 'South') worksheet.write('A10', 'South') worksheet.write('A11', 'South Total', bold) worksheet.write('B7', 400) worksheet.write('B8', 600) worksheet.write('B9', 500) worksheet.write('B10', 600) worksheet.write('B11', '=SUBTOTAL(9,B7:B10)', bold) worksheet.write('A12', 'Grand Total', bold) worksheet.write('B12', '=SUBTOTAL(9,B2:B10)', bold) ############################################################################### # # Example 1: A worksheet with outlined rows. It also includes SUBTOTAL() # functions so that it looks like the type of automatic outlines that are # generated when you use the Excel Data->SubTotals menu item. # worksheet1.set_row(1, None, None, {'level': 2}) worksheet1.set_row(2, None, None, {'level': 2}) worksheet1.set_row(3, None, None, {'level': 2}) worksheet1.set_row(4, None, None, {'level': 2}) worksheet1.set_row(5, None, None, {'level': 1}) worksheet1.set_row(6, None, None, {'level': 2}) worksheet1.set_row(7, None, None, {'level': 2}) worksheet1.set_row(8, None, None, {'level': 2}) worksheet1.set_row(9, None, None, {'level': 2}) worksheet1.set_row(10, None, None, {'level': 1}) # Write the sub-total data that is common to the row examples. create_sub_totals(worksheet1) ############################################################################### # # Example 2: Create a worksheet with collapsed outlined rows. # This is the same as the example 1 except that the all rows are collapsed. # Note: We need to indicate the rows that contains the collapsed symbol '+' # with the optional parameter, 'collapsed'. # worksheet2.set_row(1, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(2, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(3, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(4, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(5, None, None, {'level': 1, 'hidden': True}) worksheet2.set_row(6, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(7, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(8, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(9, None, None, {'level': 2, 'hidden': True}) worksheet2.set_row(10, None, None, {'level': 1, 'hidden': True}) worksheet2.set_row(11, None, None, {'collapsed': True}) # Write the sub-total data that is common to the row examples. create_sub_totals(worksheet2) ############################################################################### # # Example 3: Create a worksheet with collapsed outlined rows. # Same as the example 1 except that the two sub-totals are collapsed. # worksheet3.set_row(1, None, None, {'level': 2, 'hidden': True}) worksheet3.set_row(2, None, None, {'level': 2, 'hidden': True}) worksheet3.set_row(3, None, None, {'level': 2, 'hidden': True}) worksheet3.set_row(4, None, None, {'level': 2, 'hidden': True}) worksheet3.set_row(5, None, None, {'level': 1, 'collapsed': True}) worksheet3.set_row(6, None, None, {'level': 2, 'hidden': True}) worksheet3.set_row(7, None, None, {'level': 2, 'hidden': True}) worksheet3.set_row(8, None, None, {'level': 2, 'hidden': True}) worksheet3.set_row(9, None, None, {'level': 2, 'hidden': True}) worksheet3.set_row(10, None, None, {'level': 1, 'collapsed': True}) # Write the sub-total data that is common to the row examples. create_sub_totals(worksheet3) ############################################################################### # # Example 4: Create a worksheet with outlined rows. # Same as the example 1 except that the two sub-totals are collapsed. # worksheet4.set_row(1, None, None, {'level': 2, 'hidden': True}) worksheet4.set_row(2, None, None, {'level': 2, 'hidden': True}) worksheet4.set_row(3, None, None, {'level': 2, 'hidden': True}) worksheet4.set_row(4, None, None, {'level': 2, 'hidden': True}) worksheet4.set_row(5, None, None, {'level': 1, 'hidden': True, 'collapsed': True}) worksheet4.set_row(6, None, None, {'level': 2, 'hidden': True}) worksheet4.set_row(7, None, None, {'level': 2, 'hidden': True}) worksheet4.set_row(8, None, None, {'level': 2, 'hidden': True}) worksheet4.set_row(9, None, None, {'level': 2, 'hidden': True}) worksheet4.set_row(10, None, None, {'level': 1, 'hidden': True, 'collapsed': True}) worksheet4.set_row(11, None, None, {'collapsed': True}) # Write the sub-total data that is common to the row examples. create_sub_totals(worksheet4) ############################################################################### # # Example 5: Create a worksheet with outlined columns. # data = [ ['Month', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Total'], ['North', 50, 20, 15, 25, 65, 80, '=SUM(B2:G2)'], ['South', 10, 20, 30, 50, 50, 50, '=SUM(B3:G3)'], ['East', 45, 75, 50, 15, 75, 100, '=SUM(B4:G4)'], ['West', 15, 15, 55, 35, 20, 50, '=SUM(B5:G5)']] # Add bold format to the first row. worksheet5.set_row(0, None, bold) # Set column formatting and the outline level. worksheet5.set_column('A:A', 10, bold) worksheet5.set_column('B:G', 5, None, {'level': 1}) worksheet5.set_column('H:H', 10) # Write the data and a formula. for row, data_row in enumerate(data): worksheet5.write_row(row, 0, data_row) worksheet5.write('H6', '=SUM(H2:H5)', bold) ############################################################################### # # Example 6: Create a worksheet with collapsed outlined columns. # This is the same as the previous example except with collapsed columns. # # Reuse the data from the previous example. # Add bold format to the first row. worksheet6.set_row(0, None, bold) # Set column formatting and the outline level. worksheet6.set_column('A:A', 10, bold) worksheet6.set_column('B:G', 5, None, {'level': 1, 'hidden': True}) worksheet6.set_column('H:H', 10, None, {'collapsed': True}) # Write the data and a formula. for row, data_row in enumerate(data): worksheet6.write_row(row, 0, data_row) worksheet6.write('H6', '=SUM(H2:H5)', bold) workbook.close() XlsxWriter-1.1.2/examples/pandas_chart.py0000644000076500000240000000203113263123126020501 0ustar Johnstaff00000000000000############################################################################## # # An example of converting a Pandas dataframe to an xlsx file with a chart # using Pandas and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import pandas as pd # Create a Pandas dataframe from some data. df = pd.DataFrame({'Data': [10, 20, 30, 20, 15, 30, 45]}) # Create a Pandas Excel writer using XlsxWriter as the engine. writer = pd.ExcelWriter('pandas_chart.xlsx', engine='xlsxwriter') # Convert the dataframe to an XlsxWriter Excel object. df.to_excel(writer, sheet_name='Sheet1') # Get the xlsxwriter workbook and worksheet objects. workbook = writer.book worksheet = writer.sheets['Sheet1'] # Create a chart object. chart = workbook.add_chart({'type': 'column'}) # Configure the series of the chart from the dataframe data. chart.add_series({'values': '=Sheet1!$B$2:$B$8'}) # Insert the chart into the worksheet. worksheet.insert_chart('D2', chart) # Close the Pandas Excel writer and output the Excel file. writer.save() XlsxWriter-1.1.2/examples/pandas_chart_columns.py0000644000076500000240000000374713263123126022260 0ustar Johnstaff00000000000000############################################################################## # # An example of converting a Pandas dataframe to an xlsx file with a grouped # column chart using Pandas and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import pandas as pd from vincent.colors import brews # Some sample data to plot. farm_1 = {'Apples': 10, 'Berries': 32, 'Squash': 21, 'Melons': 13, 'Corn': 18} farm_2 = {'Apples': 15, 'Berries': 43, 'Squash': 17, 'Melons': 10, 'Corn': 22} farm_3 = {'Apples': 6, 'Berries': 24, 'Squash': 22, 'Melons': 16, 'Corn': 30} farm_4 = {'Apples': 12, 'Berries': 30, 'Squash': 15, 'Melons': 9, 'Corn': 15} data = [farm_1, farm_2, farm_3, farm_4] index = ['Farm 1', 'Farm 2', 'Farm 3', 'Farm 4'] # Create a Pandas dataframe from the data. df = pd.DataFrame(data, index=index) # Create a Pandas Excel writer using XlsxWriter as the engine. sheet_name = 'Sheet1' writer = pd.ExcelWriter('pandas_chart_columns.xlsx', engine='xlsxwriter') df.to_excel(writer, sheet_name=sheet_name) # Access the XlsxWriter workbook and worksheet objects from the dataframe. workbook = writer.book worksheet = writer.sheets[sheet_name] # Create a chart object. chart = workbook.add_chart({'type': 'column'}) # Some alternative colors for the chart. colors = ['#E41A1C', '#377EB8', '#4DAF4A', '#984EA3', '#FF7F00'] # Configure the series of the chart from the dataframe data. for col_num in range(1, len(farm_1) + 1): chart.add_series({ 'name': ['Sheet1', 0, col_num], 'categories': ['Sheet1', 1, 0, 4, 0], 'values': ['Sheet1', 1, col_num, 4, col_num], 'fill': {'color': colors[col_num - 1]}, 'overlap': -10, }) # Configure the chart axes. chart.set_x_axis({'name': 'Total Produce'}) chart.set_y_axis({'name': 'Farms', 'major_gridlines': {'visible': False}}) # Insert the chart into the worksheet. worksheet.insert_chart('H2', chart) # Close the Pandas Excel writer and output the Excel file. writer.save() XlsxWriter-1.1.2/examples/pandas_chart_line.py0000644000076500000240000000331313263123126021514 0ustar Johnstaff00000000000000############################################################################## # # An example of converting a Pandas dataframe to an xlsx file with a line # chart using Pandas and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import pandas as pd import random # Create some sample data to plot. max_row = 21 categories = ['Node 1', 'Node 2', 'Node 3', 'Node 4'] index_1 = range(0, max_row, 1) multi_iter1 = {'index': index_1} for category in categories: multi_iter1[category] = [random.randint(10, 100) for x in index_1] # Create a Pandas dataframe from the data. index_2 = multi_iter1.pop('index') df = pd.DataFrame(multi_iter1, index=index_2) df = df.reindex(columns=sorted(df.columns)) # Create a Pandas Excel writer using XlsxWriter as the engine. sheet_name = 'Sheet1' writer = pd.ExcelWriter('pandas_chart_line.xlsx', engine='xlsxwriter') df.to_excel(writer, sheet_name=sheet_name) # Access the XlsxWriter workbook and worksheet objects from the dataframe. workbook = writer.book worksheet = writer.sheets[sheet_name] # Create a chart object. chart = workbook.add_chart({'type': 'line'}) # Configure the series of the chart from the dataframe data. for i in range(len(categories)): col = i + 1 chart.add_series({ 'name': ['Sheet1', 0, col], 'categories': ['Sheet1', 1, 0, max_row, 0], 'values': ['Sheet1', 1, col, max_row, col], }) # Configure the chart axes. chart.set_x_axis({'name': 'Index'}) chart.set_y_axis({'name': 'Value', 'major_gridlines': {'visible': False}}) # Insert the chart into the worksheet. worksheet.insert_chart('G2', chart) # Close the Pandas Excel writer and output the Excel file. writer.save() XlsxWriter-1.1.2/examples/pandas_chart_stock.py0000644000076500000240000000361313263123126021713 0ustar Johnstaff00000000000000############################################################################## # # An example of converting a Pandas dataframe with stock data taken from the # web to an xlsx file with a line chart using Pandas and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import pandas as pd import pandas.io.data as web # Create some sample data to plot. all_data = {} for ticker in ['AAPL', 'GOOGL', 'IBM', 'YHOO', 'MSFT']: all_data[ticker] = web.get_data_yahoo(ticker, '5/1/2014', '5/1/2015') # Create a Pandas dataframe from the data. df = pd.DataFrame({tic: data['Adj Close'] for tic, data in all_data.items()}) # Create a Pandas Excel writer using XlsxWriter as the engine. sheet_name = 'Sheet1' writer = pd.ExcelWriter('pandas_chart_stock.xlsx', engine='xlsxwriter') df.to_excel(writer, sheet_name=sheet_name) # Access the XlsxWriter workbook and worksheet objects from the dataframe. workbook = writer.book worksheet = writer.sheets[sheet_name] # Adjust the width of the first column to make the date values clearer. worksheet.set_column('A:A', 20) # Create a chart object. chart = workbook.add_chart({'type': 'line'}) # Configure the series of the chart from the dataframe data. max_row = len(df) + 1 for i in range(len(['AAPL', 'GOOGL'])): col = i + 1 chart.add_series({ 'name': ['Sheet1', 0, col], 'categories': ['Sheet1', 2, 0, max_row, 0], 'values': ['Sheet1', 2, col, max_row, col], 'line': {'width': 1.00}, }) # Configure the chart axes. chart.set_x_axis({'name': 'Date', 'date_axis': True}) chart.set_y_axis({'name': 'Price', 'major_gridlines': {'visible': False}}) # Position the legend at the top of the chart. chart.set_legend({'position': 'top'}) # Insert the chart into the worksheet. worksheet.insert_chart('H2', chart) # Close the Pandas Excel writer and output the Excel file. writer.save() XlsxWriter-1.1.2/examples/pandas_column_formats.py0000644000076500000240000000251113263123126022433 0ustar Johnstaff00000000000000############################################################################## # # An example of converting a Pandas dataframe to an xlsx file # with column formats using Pandas and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import pandas as pd # Create a Pandas dataframe from some data. df = pd.DataFrame({'Numbers': [1010, 2020, 3030, 2020, 1515, 3030, 4545], 'Percentage': [.1, .2, .33, .25, .5, .75, .45 ], }) # Create a Pandas Excel writer using XlsxWriter as the engine. writer = pd.ExcelWriter("pandas_column_formats.xlsx", engine='xlsxwriter') # Convert the dataframe to an XlsxWriter Excel object. df.to_excel(writer, sheet_name='Sheet1') # Get the xlsxwriter workbook and worksheet objects. workbook = writer.book worksheet = writer.sheets['Sheet1'] # Add some cell formats. format1 = workbook.add_format({'num_format': '#,##0.00'}) format2 = workbook.add_format({'num_format': '0%'}) # Note: It isn't possible to format any cells that already have a format such # as the index or headers or any cells that contain dates or datetimes. # Set the column width and format. worksheet.set_column('B:B', 18, format1) # Set the format but not the column width. worksheet.set_column('C:C', None, format2) # Close the Pandas Excel writer and output the Excel file. writer.save() XlsxWriter-1.1.2/examples/pandas_conditional_format.py0000644000076500000240000000163513263123126023264 0ustar Johnstaff00000000000000############################################################################## # # An example of converting a Pandas dataframe to an xlsx file with a # conditional formatting using Pandas and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import pandas as pd # Create a Pandas dataframe from some data. df = pd.DataFrame({'Data': [10, 20, 30, 20, 15, 30, 45]}) # Create a Pandas Excel writer using XlsxWriter as the engine. writer = pd.ExcelWriter('pandas_conditional.xlsx', engine='xlsxwriter') # Convert the dataframe to an XlsxWriter Excel object. df.to_excel(writer, sheet_name='Sheet1') # Get the xlsxwriter workbook and worksheet objects. workbook = writer.book worksheet = writer.sheets['Sheet1'] # Apply a conditional format to the cell range. worksheet.conditional_format('B2:B8', {'type': '3_color_scale'}) # Close the Pandas Excel writer and output the Excel file. writer.save() XlsxWriter-1.1.2/examples/pandas_datetime.py0000644000076500000240000000333613263123126021205 0ustar Johnstaff00000000000000############################################################################## # # An example of converting a Pandas dataframe with datetimes to an xlsx file # with a default datetime and date format using Pandas and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import pandas as pd from datetime import datetime, date # Create a Pandas dataframe from some datetime data. df = pd.DataFrame({'Date and time': [datetime(2015, 1, 1, 11, 30, 55), datetime(2015, 1, 2, 1, 20, 33), datetime(2015, 1, 3, 11, 10 ), datetime(2015, 1, 4, 16, 45, 35), datetime(2015, 1, 5, 12, 10, 15)], 'Dates only': [date(2015, 2, 1), date(2015, 2, 2), date(2015, 2, 3), date(2015, 2, 4), date(2015, 2, 5)], }) # Create a Pandas Excel writer using XlsxWriter as the engine. # Also set the default datetime and date formats. writer = pd.ExcelWriter("pandas_datetime.xlsx", engine='xlsxwriter', datetime_format='mmm d yyyy hh:mm:ss', date_format='mmmm dd yyyy') # Convert the dataframe to an XlsxWriter Excel object. df.to_excel(writer, sheet_name='Sheet1') # Get the xlsxwriter workbook and worksheet objects in order to set the column # widths, to make the dates clearer. workbook = writer.book worksheet = writer.sheets['Sheet1'] worksheet.set_column('B:C', 20) # Close the Pandas Excel writer and output the Excel file. writer.save() XlsxWriter-1.1.2/examples/pandas_header_format.py0000644000076500000240000000246313263123126022211 0ustar Johnstaff00000000000000############################################################################## # # An example of converting a Pandas dataframe to an xlsx file # with a user defined header format. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import pandas as pd # Create a Pandas dataframe from some data. data = [10, 20, 30, 40, 50, 60] df = pd.DataFrame({'Heading': data, 'Longer heading that should be wrapped' : data}) # Create a Pandas Excel writer using XlsxWriter as the engine. writer = pd.ExcelWriter("pandas_header_format.xlsx", engine='xlsxwriter') # Convert the dataframe to an XlsxWriter Excel object. Note that we turn off # the default header and skip one row to allow us to insert a user defined # header. df.to_excel(writer, sheet_name='Sheet1', startrow=1, header=False) # Get the xlsxwriter workbook and worksheet objects. workbook = writer.book worksheet = writer.sheets['Sheet1'] # Add a header format. header_format = workbook.add_format({ 'bold': True, 'text_wrap': True, 'valign': 'top', 'fg_color': '#D7E4BC', 'border': 1}) # Write the column headers with the defined format. for col_num, value in enumerate(df.columns.values): worksheet.write(0, col_num + 1, value, header_format) # Close the Pandas Excel writer and output the Excel file. writer.save() XlsxWriter-1.1.2/examples/pandas_multiple.py0000644000076500000240000000147213263123126021243 0ustar Johnstaff00000000000000############################################################################## # # An example of writing multiple dataframes to worksheets using Pandas and # XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import pandas as pd # Create some Pandas dataframes from some data. df1 = pd.DataFrame({'Data': [11, 12, 13, 14]}) df2 = pd.DataFrame({'Data': [21, 22, 23, 24]}) df3 = pd.DataFrame({'Data': [31, 32, 33, 34]}) # Create a Pandas Excel writer using XlsxWriter as the engine. writer = pd.ExcelWriter('pandas_multiple.xlsx', engine='xlsxwriter') # Write each dataframe to a different worksheet. df1.to_excel(writer, sheet_name='Sheet1') df2.to_excel(writer, sheet_name='Sheet2') df3.to_excel(writer, sheet_name='Sheet3') # Close the Pandas Excel writer and output the Excel file. writer.save() XlsxWriter-1.1.2/examples/pandas_positioning.py0000644000076500000240000000211713263123126021747 0ustar Johnstaff00000000000000############################################################################## # # An example of positioning dataframes in a worksheet using Pandas and # XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import pandas as pd # Create some Pandas dataframes from some data. df1 = pd.DataFrame({'Data': [11, 12, 13, 14]}) df2 = pd.DataFrame({'Data': [21, 22, 23, 24]}) df3 = pd.DataFrame({'Data': [31, 32, 33, 34]}) df4 = pd.DataFrame({'Data': [41, 42, 43, 44]}) # Create a Pandas Excel writer using XlsxWriter as the engine. writer = pd.ExcelWriter('pandas_positioning.xlsx', engine='xlsxwriter') # Position the dataframes in the worksheet. df1.to_excel(writer, sheet_name='Sheet1') # Default position, cell A1. df2.to_excel(writer, sheet_name='Sheet1', startcol=3) df3.to_excel(writer, sheet_name='Sheet1', startrow=6) # It is also possible to write the dataframe without the header and index. df4.to_excel(writer, sheet_name='Sheet1', startrow=7, startcol=4, header=False, index=False) # Close the Pandas Excel writer and output the Excel file. writer.save() XlsxWriter-1.1.2/examples/pandas_simple.py0000644000076500000240000000123413263123126020675 0ustar Johnstaff00000000000000############################################################################## # # A simple example of converting a Pandas dataframe to an xlsx file using # Pandas and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import pandas as pd # Create a Pandas dataframe from some data. df = pd.DataFrame({'Data': [10, 20, 30, 20, 15, 30, 45]}) # Create a Pandas Excel writer using XlsxWriter as the engine. writer = pd.ExcelWriter('pandas_simple.xlsx', engine='xlsxwriter') # Convert the dataframe to an XlsxWriter Excel object. df.to_excel(writer, sheet_name='Sheet1') # Close the Pandas Excel writer and output the Excel file. writer.save() XlsxWriter-1.1.2/examples/panes.py0000644000076500000240000000615213263123126017170 0ustar Johnstaff00000000000000####################################################################### # # Example of using Python and the XlsxWriter module to create # worksheet panes. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('panes.xlsx') worksheet1 = workbook.add_worksheet('Panes 1') worksheet2 = workbook.add_worksheet('Panes 2') worksheet3 = workbook.add_worksheet('Panes 3') worksheet4 = workbook.add_worksheet('Panes 4') ####################################################################### # # Set up some formatting and text to highlight the panes. # header_format = workbook.add_format({'bold': True, 'align': 'center', 'valign': 'vcenter', 'fg_color': '#D7E4BC', 'border': 1}) center_format = workbook.add_format({'align': 'center'}) ####################################################################### # # Example 1. Freeze pane on the top row. # worksheet1.freeze_panes(1, 0) # Other sheet formatting. worksheet1.set_column('A:I', 16) worksheet1.set_row(0, 20) worksheet1.set_selection('C3') # Some text to demonstrate scrolling. for col in range(0, 9): worksheet1.write(0, col, 'Scroll down', header_format) for row in range(1, 100): for col in range(0, 9): worksheet1.write(row, col, row + 1, center_format) ####################################################################### # # Example 2. Freeze pane on the left column. # worksheet2.freeze_panes(0, 1) # Other sheet formatting. worksheet2.set_column('A:A', 16) worksheet2.set_selection('C3') # Some text to demonstrate scrolling. for row in range(0, 50): worksheet2.write(row, 0, 'Scroll right', header_format) for col in range(1, 26): worksheet2.write(row, col, col, center_format) ####################################################################### # # Example 3. Freeze pane on the top row and left column. # worksheet3.freeze_panes(1, 1) # Other sheet formatting. worksheet3.set_column('A:Z', 16) worksheet3.set_row(0, 20) worksheet3.set_selection('C3') worksheet3.write(0, 0, '', header_format) # Some text to demonstrate scrolling. for col in range(1, 26): worksheet3.write(0, col, 'Scroll down', header_format) for row in range(1, 50): worksheet3.write(row, 0, 'Scroll right', header_format) for col in range(1, 26): worksheet3.write(row, col, col, center_format) ####################################################################### # # Example 4. Split pane on the top row and left column. # # The divisions must be specified in terms of row and column dimensions. # The default row height is 15 and the default column width is 8.43 # worksheet4.split_panes(15, 8.43) # Other sheet formatting. worksheet4.set_selection('C3') # Some text to demonstrate scrolling. for col in range(1, 26): worksheet4.write(0, col, 'Scroll', center_format) for row in range(1, 50): worksheet4.write(row, 0, 'Scroll', center_format) for col in range(1, 26): worksheet4.write(row, col, col, center_format) workbook.close() XlsxWriter-1.1.2/examples/rich_strings.py0000644000076500000240000000303613263123126020556 0ustar Johnstaff00000000000000####################################################################### # # An example of using Python and XlsxWriter to write some "rich strings", # i.e., strings with multiple formats. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('rich_strings.xlsx') worksheet = workbook.add_worksheet() worksheet.set_column('A:A', 30) # Set up some formats to use. bold = workbook.add_format({'bold': True}) italic = workbook.add_format({'italic': True}) red = workbook.add_format({'color': 'red'}) blue = workbook.add_format({'color': 'blue'}) center = workbook.add_format({'align': 'center'}) superscript = workbook.add_format({'font_script': 1}) # Write some strings with multiple formats. worksheet.write_rich_string('A1', 'This is ', bold, 'bold', ' and this is ', italic, 'italic') worksheet.write_rich_string('A3', 'This is ', red, 'red', ' and this is ', blue, 'blue') worksheet.write_rich_string('A5', 'Some ', bold, 'bold text', ' centered', center) worksheet.write_rich_string('A7', italic, 'j = k', superscript, '(n-1)', center) workbook.close() XlsxWriter-1.1.2/examples/right_to_left.py0000644000076500000240000000313113263123126020705 0ustar Johnstaff00000000000000####################################################################### # _*_ coding: utf-8 # # Example of how to use Python and the XlsxWriter module to change the default # worksheet and cell text direction from left-to-right to right-to-left as # required by some middle eastern versions of Excel. # # Note, this a Python2 unicode version. Remove the u'' to make this work with # Python3. See also the unicode_python3.py example. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('right_to_left.xlsx') # Add two worksheets. worksheet1 = workbook.add_worksheet() worksheet2 = workbook.add_worksheet() # Add the cell formats. format_left_to_right = workbook.add_format({'reading_order': 1}) format_right_to_left = workbook.add_format({'reading_order': 2}) # Make the columns wider for clarity. worksheet1.set_column('A:A', 25) worksheet2.set_column('A:A', 25) # Change the direction for worksheet2. worksheet2.right_to_left() # Write some data to show the difference. # Standard direction: | A1 | B1 | C1 | ... worksheet1.write('A1', u'نص عربي / English text') # Default direction. worksheet1.write('A2', u'نص عربي / English text', format_left_to_right) worksheet1.write('A3', u'نص عربي / English text', format_right_to_left) # Right to left direction: ... | C1 | B1 | A1 | worksheet2.write('A1', u'نص عربي / English text') # Default direction. worksheet2.write('A2', u'نص عربي / English text', format_left_to_right) worksheet2.write('A3', u'نص عربي / English text', format_right_to_left) workbook.close() XlsxWriter-1.1.2/examples/sparklines1.py0000644000076500000240000000250713263123126020316 0ustar Johnstaff00000000000000############################################################################### # # Example of how to add sparklines to a Python XlsxWriter file. # # Sparklines are small charts that fit in a single cell and are # used to show trends in data. # # See sparklines2.py for examples of more complex sparkline formatting. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('sparklines1.xlsx') worksheet = workbook.add_worksheet() # Some sample data to plot. data = [ [-2, 2, 3, -1, 0], [30, 20, 33, 20, 15], [1, -1, -1, 1, -1], ] # Write the sample data to the worksheet. worksheet.write_row('A1', data[0]) worksheet.write_row('A2', data[1]) worksheet.write_row('A3', data[2]) # Add a line sparkline (the default) with markers. worksheet.add_sparkline('F1', {'range': 'Sheet1!A1:E1', 'markers': True}) # Add a column sparkline with non-default style. worksheet.add_sparkline('F2', {'range': 'Sheet1!A2:E2', 'type': 'column', 'style': 12}) # Add a win/loss sparkline with negative values highlighted. worksheet.add_sparkline('F3', {'range': 'Sheet1!A3:E3', 'type': 'win_loss', 'negative_points': True}) workbook.close() XlsxWriter-1.1.2/examples/sparklines2.py0000644000076500000240000002022113263123126020310 0ustar Johnstaff00000000000000############################################################################### # # Example of how to add sparklines to an XlsxWriter file with Python. # # Sparklines are small charts that fit in a single cell and are # used to show trends in data. This example shows the majority of # options that can be applied to sparklines. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('sparklines2.xlsx') worksheet1 = workbook.add_worksheet() worksheet2 = workbook.add_worksheet() bold = workbook.add_format({'bold': True}) row = 1 # Set the columns widths to make the output clearer. worksheet1.set_column('A:A', 14) worksheet1.set_column('B:B', 50) worksheet1.set_zoom(150) # Headings. worksheet1.write('A1', 'Sparkline', bold) worksheet1.write('B1', 'Description', bold) ############################################################################### # text = 'A default "line" sparkline.' worksheet1.add_sparkline('A2', {'range': 'Sheet2!A1:J1'}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'A default "column" sparkline.' worksheet1.add_sparkline('A3', {'range': 'Sheet2!A2:J2', 'type': 'column'}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'A default "win/loss" sparkline.' worksheet1.add_sparkline('A4', {'range': 'Sheet2!A3:J3', 'type': 'win_loss'}) worksheet1.write(row, 1, text) row += 2 ############################################################################### # text = 'Line with markers.' worksheet1.add_sparkline('A6', {'range': 'Sheet2!A1:J1', 'markers': True}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'Line with high and low points.' worksheet1.add_sparkline('A7', {'range': 'Sheet2!A1:J1', 'high_point': True, 'low_point': True}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'Line with first and last point markers.' worksheet1.add_sparkline('A8', {'range': 'Sheet2!A1:J1', 'first_point': True, 'last_point': True}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'Line with negative point markers.' worksheet1.add_sparkline('A9', {'range': 'Sheet2!A1:J1', 'negative_points': True}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'Line with axis.' worksheet1.add_sparkline('A10', {'range': 'Sheet2!A1:J1', 'axis': True}) worksheet1.write(row, 1, text) row += 2 ############################################################################### # text = 'Column with default style (1).' worksheet1.add_sparkline('A12', {'range': 'Sheet2!A2:J2', 'type': 'column'}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'Column with style 2.' worksheet1.add_sparkline('A13', {'range': 'Sheet2!A2:J2', 'type': 'column', 'style': 2}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'Column with style 3.' worksheet1.add_sparkline('A14', {'range': 'Sheet2!A2:J2', 'type': 'column', 'style': 3}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'Column with style 4.' worksheet1.add_sparkline('A15', {'range': 'Sheet2!A2:J2', 'type': 'column', 'style': 4}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'Column with style 5.' worksheet1.add_sparkline('A16', {'range': 'Sheet2!A2:J2', 'type': 'column', 'style': 5}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'Column with style 6.' worksheet1.add_sparkline('A17', {'range': 'Sheet2!A2:J2', 'type': 'column', 'style': 6}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'Column with a user defined color.' worksheet1.add_sparkline('A18', {'range': 'Sheet2!A2:J2', 'type': 'column', 'series_color': '#E965E0'}) worksheet1.write(row, 1, text) row += 2 ############################################################################### # text = 'A win/loss sparkline.' worksheet1.add_sparkline('A20', {'range': 'Sheet2!A3:J3', 'type': 'win_loss'}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'A win/loss sparkline with negative points highlighted.' worksheet1.add_sparkline('A21', {'range': 'Sheet2!A3:J3', 'type': 'win_loss', 'negative_points': True}) worksheet1.write(row, 1, text) row += 2 ############################################################################### # text = 'A left to right column (the default).' worksheet1.add_sparkline('A23', {'range': 'Sheet2!A4:J4', 'type': 'column', 'style': 20}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'A right to left column.' worksheet1.add_sparkline('A24', {'range': 'Sheet2!A4:J4', 'type': 'column', 'style': 20, 'reverse': True}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # text = 'Sparkline and text in one cell.' worksheet1.add_sparkline('A25', {'range': 'Sheet2!A4:J4', 'type': 'column', 'style': 20}) worksheet1.write(row, 0, 'Growth') worksheet1.write(row, 1, text) row += 2 ############################################################################### # text = 'A grouped sparkline. Changes are applied to all three.' worksheet1.add_sparkline('A27', {'location': ['A27', 'A28', 'A29'], 'range': ['Sheet2!A5:J5', 'Sheet2!A6:J6', 'Sheet2!A7:J7'], 'markers': True}) worksheet1.write(row, 1, text) row += 1 ############################################################################### # # Create a second worksheet with data to plot. # worksheet2.set_column('A:J', 11) data = [ # Simple line data. [-2, 2, 3, -1, 0, -2, 3, 2, 1, 0], # Simple column data. [30, 20, 33, 20, 15, 5, 5, 15, 10, 15], # Simple win/loss data. [1, 1, -1, -1, 1, -1, 1, 1, 1, -1], # Unbalanced histogram. [5, 6, 7, 10, 15, 20, 30, 50, 70, 100], # Data for the grouped sparkline example. [-2, 2, 3, -1, 0, -2, 3, 2, 1, 0], [3, -1, 0, -2, 3, 2, 1, 0, 2, 1], [0, -2, 3, 2, 1, 0, 1, 2, 3, 1], ] # Write the sample data to the worksheet. worksheet2.write_row('A1', data[0]) worksheet2.write_row('A2', data[1]) worksheet2.write_row('A3', data[2]) worksheet2.write_row('A4', data[3]) worksheet2.write_row('A5', data[4]) worksheet2.write_row('A6', data[5]) worksheet2.write_row('A7', data[6]) workbook.close() XlsxWriter-1.1.2/examples/tab_colors.py0000644000076500000240000000123313263123126020204 0ustar Johnstaff00000000000000####################################################################### # # Example of how to set Excel worksheet tab colors using Python # and the XlsxWriter module. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('tab_colors.xlsx') # Set up some worksheets. worksheet1 = workbook.add_worksheet() worksheet2 = workbook.add_worksheet() worksheet3 = workbook.add_worksheet() worksheet4 = workbook.add_worksheet() # Set tab colors worksheet1.set_tab_color('red') worksheet2.set_tab_color('green') worksheet3.set_tab_color('#FF9900') # Orange # worksheet4 will have the default color. workbook.close() XlsxWriter-1.1.2/examples/tables.py0000644000076500000240000002423313335654635017352 0ustar Johnstaff00000000000000############################################################################### # # Example of how to add tables to an XlsxWriter worksheet. # # Tables in Excel are used to group rows and columns of data into a single # structure that can be referenced in a formula or formatted collectively. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('tables.xlsx') worksheet1 = workbook.add_worksheet() worksheet2 = workbook.add_worksheet() worksheet3 = workbook.add_worksheet() worksheet4 = workbook.add_worksheet() worksheet5 = workbook.add_worksheet() worksheet6 = workbook.add_worksheet() worksheet7 = workbook.add_worksheet() worksheet8 = workbook.add_worksheet() worksheet9 = workbook.add_worksheet() worksheet10 = workbook.add_worksheet() worksheet11 = workbook.add_worksheet() worksheet12 = workbook.add_worksheet() currency_format = workbook.add_format({'num_format': '$#,##0'}) # Some sample data for the table. data = [ ['Apples', 10000, 5000, 8000, 6000], ['Pears', 2000, 3000, 4000, 5000], ['Bananas', 6000, 6000, 6500, 6000], ['Oranges', 500, 300, 200, 700], ] ############################################################################### # # Example 1. # caption = 'Default table with no data.' # Set the columns widths. worksheet1.set_column('B:G', 12) # Write the caption. worksheet1.write('B1', caption) # Add a table to the worksheet. worksheet1.add_table('B3:F7') ############################################################################### # # Example 2. # caption = 'Default table with data.' # Set the columns widths. worksheet2.set_column('B:G', 12) # Write the caption. worksheet2.write('B1', caption) # Add a table to the worksheet. worksheet2.add_table('B3:F7', {'data': data}) ############################################################################### # # Example 3. # caption = 'Table without default autofilter.' # Set the columns widths. worksheet3.set_column('B:G', 12) # Write the caption. worksheet3.write('B1', caption) # Add a table to the worksheet. worksheet3.add_table('B3:F7', {'autofilter': 0}) # Table data can also be written separately, as an array or individual cells. worksheet3.write_row('B4', data[0]) worksheet3.write_row('B5', data[1]) worksheet3.write_row('B6', data[2]) worksheet3.write_row('B7', data[3]) ############################################################################### # # Example 4. # caption = 'Table without default header row.' # Set the columns widths. worksheet4.set_column('B:G', 12) # Write the caption. worksheet4.write('B1', caption) # Add a table to the worksheet. worksheet4.add_table('B4:F7', {'header_row': 0}) # Table data can also be written separately, as an array or individual cells. worksheet4.write_row('B4', data[0]) worksheet4.write_row('B5', data[1]) worksheet4.write_row('B6', data[2]) worksheet4.write_row('B7', data[3]) ############################################################################### # # Example 5. # caption = 'Default table with "First Column" and "Last Column" options.' # Set the columns widths. worksheet5.set_column('B:G', 12) # Write the caption. worksheet5.write('B1', caption) # Add a table to the worksheet. worksheet5.add_table('B3:F7', {'first_column': 1, 'last_column': 1}) # Table data can also be written separately, as an array or individual cells. worksheet5.write_row('B4', data[0]) worksheet5.write_row('B5', data[1]) worksheet5.write_row('B6', data[2]) worksheet5.write_row('B7', data[3]) ############################################################################### # # Example 6. # caption = 'Table with banded columns but without default banded rows.' # Set the columns widths. worksheet6.set_column('B:G', 12) # Write the caption. worksheet6.write('B1', caption) # Add a table to the worksheet. worksheet6.add_table('B3:F7', {'banded_rows': 0, 'banded_columns': 1}) # Table data can also be written separately, as an array or individual cells. worksheet6.write_row('B4', data[0]) worksheet6.write_row('B5', data[1]) worksheet6.write_row('B6', data[2]) worksheet6.write_row('B7', data[3]) ############################################################################### # # Example 7. # caption = 'Table with user defined column headers' # Set the columns widths. worksheet7.set_column('B:G', 12) # Write the caption. worksheet7.write('B1', caption) # Add a table to the worksheet. worksheet7.add_table('B3:F7', {'data': data, 'columns': [{'header': 'Product'}, {'header': 'Quarter 1'}, {'header': 'Quarter 2'}, {'header': 'Quarter 3'}, {'header': 'Quarter 4'}, ]}) ############################################################################### # # Example 8. # caption = 'Table with user defined column headers' # Set the columns widths. worksheet8.set_column('B:G', 12) # Write the caption. worksheet8.write('B1', caption) # Formula to use in the table. formula = '=SUM(Table8[@[Quarter 1]:[Quarter 4]])' # Add a table to the worksheet. worksheet8.add_table('B3:G7', {'data': data, 'columns': [{'header': 'Product'}, {'header': 'Quarter 1'}, {'header': 'Quarter 2'}, {'header': 'Quarter 3'}, {'header': 'Quarter 4'}, {'header': 'Year', 'formula': formula}, ]}) ############################################################################### # # Example 9. # caption = 'Table with totals row (but no caption or totals).' # Set the columns widths. worksheet9.set_column('B:G', 12) # Write the caption. worksheet9.write('B1', caption) # Formula to use in the table. formula = '=SUM(Table9[@[Quarter 1]:[Quarter 4]])' # Add a table to the worksheet. worksheet9.add_table('B3:G8', {'data': data, 'total_row': 1, 'columns': [{'header': 'Product'}, {'header': 'Quarter 1'}, {'header': 'Quarter 2'}, {'header': 'Quarter 3'}, {'header': 'Quarter 4'}, {'header': 'Year', 'formula': formula }, ]}) ############################################################################### # # Example 10. # caption = 'Table with totals row with user captions and functions.' # Set the columns widths. worksheet10.set_column('B:G', 12) # Write the caption. worksheet10.write('B1', caption) # Options to use in the table. options = {'data': data, 'total_row': 1, 'columns': [{'header': 'Product', 'total_string': 'Totals'}, {'header': 'Quarter 1', 'total_function': 'sum'}, {'header': 'Quarter 2', 'total_function': 'sum'}, {'header': 'Quarter 3', 'total_function': 'sum'}, {'header': 'Quarter 4', 'total_function': 'sum'}, {'header': 'Year', 'formula': '=SUM(Table10[@[Quarter 1]:[Quarter 4]])', 'total_function': 'sum' }, ]} # Add a table to the worksheet. worksheet10.add_table('B3:G8', options) ############################################################################### # # Example 11. # caption = 'Table with alternative Excel style.' # Set the columns widths. worksheet11.set_column('B:G', 12) # Write the caption. worksheet11.write('B1', caption) # Options to use in the table. options = {'data': data, 'style': 'Table Style Light 11', 'total_row': 1, 'columns': [{'header': 'Product', 'total_string': 'Totals'}, {'header': 'Quarter 1', 'total_function': 'sum'}, {'header': 'Quarter 2', 'total_function': 'sum'}, {'header': 'Quarter 3', 'total_function': 'sum'}, {'header': 'Quarter 4', 'total_function': 'sum'}, {'header': 'Year', 'formula': '=SUM(Table11[@[Quarter 1]:[Quarter 4]])', 'total_function': 'sum' }, ]} # Add a table to the worksheet. worksheet11.add_table('B3:G8', options) ############################################################################### # # Example 12. # caption = 'Table with column formats.' # Set the columns widths. worksheet12.set_column('B:G', 12) # Write the caption. worksheet12.write('B1', caption) # Options to use in the table. options = {'data': data, 'total_row': 1, 'columns': [{'header': 'Product', 'total_string': 'Totals'}, {'header': 'Quarter 1', 'total_function': 'sum', 'format': currency_format, }, {'header': 'Quarter 2', 'total_function': 'sum', 'format': currency_format, }, {'header': 'Quarter 3', 'total_function': 'sum', 'format': currency_format, }, {'header': 'Quarter 4', 'total_function': 'sum', 'format': currency_format, }, {'header': 'Year', 'formula': '=SUM(Table12[@[Quarter 1]:[Quarter 4]])', 'total_function': 'sum', 'format': currency_format, }, ]} # Add a table to the worksheet. worksheet12.add_table('B3:G8', options) workbook.close() XlsxWriter-1.1.2/examples/text_indent.py0000644000076500000240000000116413263123126020405 0ustar Johnstaff00000000000000############################################################################## # # A simple formatting example using XlsxWriter. # # This program demonstrates the indentation cell format. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('text_indent.xlsx') worksheet = workbook.add_worksheet() indent1 = workbook.add_format({'indent': 1}) indent2 = workbook.add_format({'indent': 2}) worksheet.set_column('A:A', 40) worksheet.write('A1', "This text is indented 1 level", indent1) worksheet.write('A2', "This text is indented 2 levels", indent2) workbook.close() XlsxWriter-1.1.2/examples/textbox.py0000644000076500000240000000732013263123126017555 0ustar Johnstaff00000000000000####################################################################### # # An example of inserting textboxes into an Excel worksheet using # Python and XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('textbox.xlsx') worksheet = workbook.add_worksheet() row = 4 col = 1 # The examples below show different textbox options and formatting. In each # example the text describes the formatting. # Example text = 'A simple textbox with some text' worksheet.insert_textbox(row, col, text) row += 10 # Example text = 'A textbox with changed dimensions' options = { 'width': 256, 'height': 100, } worksheet.insert_textbox(row, col, text, options) row += 10 # Example text = 'A textbox with an offset in the cell' options = { 'x_offset': 10, 'y_offset': 10, } worksheet.insert_textbox(row, col, text, options) row += 10 # Example text = 'A textbox with scaling' options = { 'x_scale': 1.5, 'y_scale': 0.8, } worksheet.insert_textbox(row, col, text, options) row += 10 # Example text = 'A textbox with some long text that wraps around onto several lines' worksheet.insert_textbox(row, col, text) row += 10 # Example text = 'A textbox\nwith some\nnewlines\n\nand paragraphs' worksheet.insert_textbox(row, col, text) row += 10 # Example text = 'A textbox with a solid fill background' options = { 'fill': {'color': 'red'}, } worksheet.insert_textbox(row, col, text, options) row += 10 # Example text = 'A textbox with a no fill background' options = { 'fill': {'none': True}, } worksheet.insert_textbox(row, col, text, options) row += 10 # Example text = 'A textbox with a gradient fill background' options = { 'gradient': {'colors': ['#DDEBCF', '#9CB86E', '#156B13']}, } worksheet.insert_textbox(row, col, text, options) row += 10 # Example text = 'A textbox with a user defined border line' options = { 'border': {'color': 'red', 'width': 3, 'dash_type': 'round_dot'}, } worksheet.insert_textbox(row, col, text, options) row += 10 # Example text = 'A textbox with no border line' options = { 'border': {'none': True}, } worksheet.insert_textbox(row, col, text, options) row += 10 # Example text = 'Default alignment: top - left' worksheet.insert_textbox(row, col, text) row += 10 # Example text = 'Alignment: top - center' options = { 'align': {'horizontal': 'center'}, } worksheet.insert_textbox(row, col, text) row += 10 # Example text = 'Alignment: top - center' options = { 'align': {'horizontal': 'center'}, } worksheet.insert_textbox(row, col, text, options) row += 10 # Example text = 'Alignment: middle - center' options = { 'align': {'vertical': 'middle', 'horizontal': 'center'}, } worksheet.insert_textbox(row, col, text, options) row += 10 # Example text = 'Font properties: bold' options = { 'font': {'bold': True}, } worksheet.insert_textbox(row, col, text, options) row += 10 # Example text = 'Font properties: various' options = { 'font': {'bold': True}, } worksheet.insert_textbox(row, col, text, options) row += 10 # Example text = 'Font properties: various' options = { 'font': {'bold': True, 'italic': True, 'underline': True, 'name': 'Arial', 'color': 'red', 'size': 12} } worksheet.insert_textbox(row, col, text, options) row += 10 # Example text = 'Some text in a textbox with formatting' options = { 'font': {'color': 'white'}, 'align': {'vertical': 'middle', 'horizontal': 'center' }, 'gradient': {'colors': ['red', 'blue']}, } worksheet.insert_textbox(row, col, text, options) row += 10 workbook.close() XlsxWriter-1.1.2/examples/tutorial1.py0000644000076500000240000000174013263123126020004 0ustar Johnstaff00000000000000############################################################################## # # A simple program to write some data to an Excel file using the XlsxWriter # Python module. # # This program is shown, with explanations, in Tutorial 1 of the XlsxWriter # documentation. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter # Create a workbook and add a worksheet. workbook = xlsxwriter.Workbook('Expenses01.xlsx') worksheet = workbook.add_worksheet() # Some data we want to write to the worksheet. expenses = ( ['Rent', 1000], ['Gas', 100], ['Food', 300], ['Gym', 50], ) # Start from the first cell. Rows and columns are zero indexed. row = 0 col = 0 # Iterate over the data and write it out row by row. for item, cost in (expenses): worksheet.write(row, col, item) worksheet.write(row, col + 1, cost) row += 1 # Write a total using a formula. worksheet.write(row, 0, 'Total') worksheet.write(row, 1, '=SUM(B1:B4)') workbook.close() XlsxWriter-1.1.2/examples/tutorial2.py0000644000076500000240000000240413263123126020003 0ustar Johnstaff00000000000000############################################################################## # # A simple program to write some data to an Excel file using the XlsxWriter # Python module. # # This program is shown, with explanations, in Tutorial 2 of the XlsxWriter # documentation. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter # Create a workbook and add a worksheet. workbook = xlsxwriter.Workbook('Expenses02.xlsx') worksheet = workbook.add_worksheet() # Add a bold format to use to highlight cells. bold = workbook.add_format({'bold': True}) # Add a number format for cells with money. money = workbook.add_format({'num_format': '$#,##0'}) # Write some data header. worksheet.write('A1', 'Item', bold) worksheet.write('B1', 'Cost', bold) # Some data we want to write to the worksheet. expenses = ( ['Rent', 1000], ['Gas', 100], ['Food', 300], ['Gym', 50], ) # Start from the first cell below the headers. row = 1 col = 0 # Iterate over the data and write it out row by row. for item, cost in (expenses): worksheet.write(row, col, item) worksheet.write(row, col + 1, cost, money) row += 1 # Write a total using a formula. worksheet.write(row, 0, 'Total', bold) worksheet.write(row, 1, '=SUM(B2:B5)', money) workbook.close() XlsxWriter-1.1.2/examples/tutorial3.py0000644000076500000240000000326513263123126020012 0ustar Johnstaff00000000000000############################################################################## # # A simple program to write some data to an Excel file using the XlsxWriter # Python module. # # This program is shown, with explanations, in Tutorial 3 of the XlsxWriter # documentation. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # from datetime import datetime import xlsxwriter # Create a workbook and add a worksheet. workbook = xlsxwriter.Workbook('Expenses03.xlsx') worksheet = workbook.add_worksheet() # Add a bold format to use to highlight cells. bold = workbook.add_format({'bold': 1}) # Add a number format for cells with money. money_format = workbook.add_format({'num_format': '$#,##0'}) # Add an Excel date format. date_format = workbook.add_format({'num_format': 'mmmm d yyyy'}) # Adjust the column width. worksheet.set_column(1, 1, 15) # Write some data headers. worksheet.write('A1', 'Item', bold) worksheet.write('B1', 'Date', bold) worksheet.write('C1', 'Cost', bold) # Some data we want to write to the worksheet. expenses = ( ['Rent', '2013-01-13', 1000], ['Gas', '2013-01-14', 100], ['Food', '2013-01-16', 300], ['Gym', '2013-01-20', 50], ) # Start from the first cell below the headers. row = 1 col = 0 for item, date_str, cost in (expenses): # Convert the date string into a datetime object. date = datetime.strptime(date_str, "%Y-%m-%d") worksheet.write_string(row, col, item) worksheet.write_datetime(row, col + 1, date, date_format) worksheet.write_number(row, col + 2, cost, money_format) row += 1 # Write a total using a formula. worksheet.write(row, 0, 'Total', bold) worksheet.write(row, 2, '=SUM(C2:C5)', money_format) workbook.close() XlsxWriter-1.1.2/examples/unicode_polish_utf8.py0000644000076500000240000000205413263123126022031 0ustar Johnstaff00000000000000############################################################################## # # A simple example of converting some Unicode text to an Excel file using # the XlsxWriter Python module. # # This example generates a spreadsheet with some Polish text from a file # with UTF8 encoded text. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import codecs import xlsxwriter # Open the input file with the correct encoding. textfile = codecs.open('unicode_polish_utf8.txt', 'r', 'utf-8') # Create an new Excel file and convert the text data. workbook = xlsxwriter.Workbook('unicode_polish_utf8.xlsx') worksheet = workbook.add_worksheet() # Widen the first column to make the text clearer. worksheet.set_column('A:A', 50) # Start from the first cell. row = 0 col = 0 # Read the text file and write it to the worksheet. for line in textfile: # Ignore the comments in the text file. if line.startswith('#'): continue # Write any other lines to the worksheet. worksheet.write(row, col, line.rstrip("\n")) row += 1 workbook.close() XlsxWriter-1.1.2/examples/unicode_polish_utf8.txt0000644000076500000240000000144212106312053022212 0ustar Johnstaff00000000000000# # XlsxWriter Unicode examples. # # Sample encoded text borrowed from Sean Burke's Pod::Simple distro. # # The text is Polish encoded as UTF8 # # See the unicode_polish_utf8.py example. # WŚRÓD NOCNEJ CISZY Wśród nocnej ciszy głos się rozchodzi: Wstańcie, pasterze, Bóg się nam rodzi! Czym prędzej się wybierajcie, Do Betlejem pospieszajcie Przywitać Pana. Poszli, znaleźli Dzieciątko w żłobie Z wszystkimi znaki danymi sobie. Jako Bogu cześć Mu dali, A witając zawołali Z wielkiej radości: Ach, witaj Zbawco z dawno żądany, Wiele tysięcy lat wyglądany Na Ciebie króle, prorocy Czekali, a Tyś tej nocy Nam się objawił. I my czekamy na Ciebie, Pana, A skoro przyjdziesz na głos kapłana, Padniemy na twarz przed Tobą, Wierząc, żeś jest pod osłoną Chleba i wina. XlsxWriter-1.1.2/examples/unicode_python2.py0000644000076500000240000000120413263123126021164 0ustar Johnstaff00000000000000############################################################################### # _*_ coding: utf-8 # # A simple Unicode spreadsheet in Python 2 using the XlsxWriter Python module. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # # To write Unicode text in UTF-8 to a xlsxwriter file in Python 2: # # 1. Encode the file as UTF-8. # 2. Include the "coding" directive at the start of the file. # 3. Use u'' to indicate a Unicode string. import xlsxwriter workbook = xlsxwriter.Workbook('unicode_python2.xlsx') worksheet = workbook.add_worksheet() worksheet.write('B3', u'Это фраза на русском!') workbook.close() XlsxWriter-1.1.2/examples/unicode_python3.py0000644000076500000240000000101413263123126021164 0ustar Johnstaff00000000000000############################################################################### # # # A simple Unicode spreadsheet in Python 3 using the XlsxWriter Python module. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # # To write Unicode text in UTF-8 to a xlsxwriter file in Python 3: # # 1. Encode the file as UTF-8. # # import xlsxwriter workbook = xlsxwriter.Workbook('unicode_python3.xlsx') worksheet = workbook.add_worksheet() worksheet.write('B3', 'Это фраза на русском!') workbook.close() XlsxWriter-1.1.2/examples/unicode_shift_jis.py0000644000076500000240000000206313263123126021547 0ustar Johnstaff00000000000000############################################################################## # # A simple example of converting some Unicode text to an Excel file using # the XlsxWriter Python module. # # This example generates a spreadsheet with some Japanese text from a file # with Shift-JIS encoded text. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import codecs import xlsxwriter # Open the input file with the correct encoding. textfile = codecs.open('unicode_shift_jis.txt', 'r', 'shift_jis') # Create an new Excel file and convert the text data. workbook = xlsxwriter.Workbook('unicode_shift_jis.xlsx') worksheet = workbook.add_worksheet() # Widen the first column to make the text clearer. worksheet.set_column('A:A', 50) # Start from the first cell. row = 0 col = 0 # Read the text file and write it to the worksheet. for line in textfile: # Ignore the comments in the text file. if line.startswith('#'): continue # Write any other lines to the worksheet. worksheet.write(row, col, line.rstrip("\n")) row += 1 workbook.close() XlsxWriter-1.1.2/examples/unicode_shift_jis.txt0000644000076500000240000000066512106312053021736 0ustar Johnstaff00000000000000# # XlsxWriter Unicode examples. # # Sample encoded text borrowed from Sean Burke's Pod::Simple distro. # # The text is encoded as Japanese Shift-JIS. # # See the unicode_shift_jis.py example. # Some uninteresting product specs found on the Net ^ S2763 GZ4 _CNCbN~[v 12V 10W~1 @ E295 E365 E76mm 8.0kg ގ @A~AA}Cgd@KX i 76,000~ivEgX݁j XlsxWriter-1.1.2/examples/vba_extract.py0000644000076500000240000000362713263123126020370 0ustar Johnstaff00000000000000#!python ############################################################################## # # vba_extract - A simple utility to extract a vbaProject.bin binary from an # Excel 2007+ xlsm file for insertion into an XlsxWriter file. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import sys import shutil from zipfile import ZipFile from zipfile import BadZipfile # The VBA project file we want to extract. vba_filename = 'vbaProject.bin' # Get the xlsm file name from the commandline. if len(sys.argv) > 1: xlsm_file = sys.argv[1] else: print("\nUtility to extract a vbaProject.bin binary from an Excel 2007+ " "xlsm macro file for insertion into an XlsxWriter file." "\n" "See: https://xlsxwriter.readthedocs.io/working_with_macros.html\n" "\n" "Usage: vba_extract file.xlsm\n") exit() try: # Open the Excel xlsm file as a zip file. xlsm_zip = ZipFile(xlsm_file, 'r') # Read the xl/vbaProject.bin file. vba_data = xlsm_zip.read('xl/' + vba_filename) # Write the vba data to a local file. vba_file = open(vba_filename, "wb") vba_file.write(vba_data) vba_file.close() except IOError: # Use exc_info() for Python 2.5+ compatibility. e = sys.exc_info()[1] print("File error: %s" % str(e)) exit() except KeyError: # Usually when there isn't a xl/vbaProject.bin member in the file. e = sys.exc_info()[1] print("File error: %s" % str(e)) print("File may not be an Excel xlsm macro file: '%s'" % xlsm_file) exit() except BadZipfile: # Usually if the file is an xls file and not an xlsm file. e = sys.exc_info()[1] print("File error: %s: '%s'" % (str(e), xlsm_file)) print("File may not be an Excel xlsm macro file.") exit() except: # Catch any other exceptions. e = sys.exc_info()[1] print("File error: %s" % str(e)) exit() print("Extracted: %s" % vba_filename) XlsxWriter-1.1.2/examples/vbaProject.bin0000644000076500000240000003700012426312001020265 0ustar Johnstaff00000000000000ࡱ>  Root EntryA,VBAAAThisWorkbookSheet1  !"#$%&'()*+,-./0234689:;<=>?@ABCDEFHIJKLMNOPQRSTUVXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'ڝ& #xME (SLSS<N0{00020819-0000-0000-C000-000000000046}@(%H@0QgR xAttribute VB_Name = "ThisWorkbook" Bas0{00020P819-0C$0046} |GlobalSpacFalse dCreatablPredeclaIdTru BExposeTemplateDerivBustomizD2'ڝ:#xME (SLSS<N0{00020819-0000-0000-C000-000000000046}@(%H@0QgR xAttribute VB_Name = "She@et1" Bast0{000@20819- 0C$0046} |GlobaBlSpacFalse dCre atablPr@edeclaI"dTru BExposeTemplateDer iv$BustomizD2Module1 __SRP_21__SRP_3 5gThisWorkbook17ڝxME(6 < <80%  `H88QgR $*\Rffff*0b52671f51*\R0*#14"@8opHHello from Python!A@"c0aAttribute VB_Name = "Module1" Sub say_hello()  MsgBox ("HT from Python!"DEnd n rU~| 1p0+'''<:lN\\ 6\<0$ \<krU $`nڝP#xME (SLSS<N0{00020819-0000-0000-C000-000000000046}8(%HxAttribute VB_Name = "ThisWorkboo k1" Bas0{00020819-0C$0046} |Global!SpacFalse dCreatablPre declaIdTru BExposeTemplateDerivBustom izD2ڝ2#Sheet2G_VBA_PROJECTWH dirj__SRP_0#xME (SLSS<N0{00020820-0000-0000-C000-000000000046}8(%HxAttribute VB_Name = "She@et2" Bast0{00020820- C$0046} |Global!SpacFalse dCreatablPre declaIdTru BExposeTemplateDeriv$Bustom izD2a  *\G{000204EF-0000-0000-C000-000000000046}#4.0#9#C:\Program Files\Common Files\Microsoft Shared\VBA\VBA6\VBE6.DLL#Visual Basic For Applications*\G{00020813-0000-0000-C000-000000000046}#1.6#0#C:\program files\microsoft office 2007\Office12\EXCEL.EXE#Microsoft Excel 12.0 Object Library*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\WINDOWS\system32\stdole2.tlb#OLE Automation(*\G{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}#2.4#0#C:\Program Files\Common Files\Microsoft Shared\OFFICE12\MSO.DLL#Microsoft Office 12.0 Object Library QgR ڝThisWorkbook0]52671f21ThisWorkbook& - Sheet10^52671f21 Sheet1:-Module10b52671f51Module10ThisWorkbook1055611c0be%ThisWorkbook1PP% Sheet2065611c0be' Sheet22h%h0PHLL;ݡ"I-RS }Hv«Gq;0[9\~Dv%4*4"A^ O&Excel+VBAWin16~Win32MacVBA6#Project1 stdole` VBAProjectOfficeu ThisWorkbook| _EvaluateSheet1Workbookk WorksheetModule1b say_hello0tMsgBoxR ThisWorkbook15]Sheet2x % 'Zf0* pHd VBAProject4@j = r QgR J< rstdole>stdole h%^*\G{00020430-C 0046}#2.0#0#C:\WINDOWS\syst em32\e2.tlb#OLE Automation`EOffDicEOficEE2DF8D04C-5BFA-101B-BDE5EAAC42Egram Files\CommonMicrosoft Shared\OFFICE12\MSO.0DLL#M 1q Ob Li`braryKڝThisWorkbookGTisWArkboU 2 HB1Bx-,B,!& *"B+BB|Sheet1GS@#e@Xt1H2N:@Module;du,2OP9!E9 @I^^! 2CUk1O&%P&P_2_ q32/2 =2 K*rU~~~~~~~k '0aKE11    A p  ! ) ThisWorkbookSheet1Module1 VBAProjectF /C:\PROGRA~1\COMMON~1\MICROS~1\VBA\VBA6\VBE6.DLLVBA 1 Y0F9C:\program files\microsoft office 2007\Office12\EXCEL.EXEExcel )Q@0FC:\WINDOWS\system32\stdole2.tlbstdole AiPL-[DR?C:\Program Files\Common Files\Microsoft Shared\OFFICE12\MSO.DLLOffice a` say_hello  Hello from PerlVBE6.DLL S Hello from Perl! ThisWorkbook1Sheet2rU tThisWorkbookThisWorkbookSheet1Sheet1Module1Module1ThisWorkbook1ThisWorkbook1Sh__SRP_1 RPROJECTwmPROJECT Feet2Sheet2ID="{8D807122-0657-42C8-BC6F-4B5FD08031C9}" Document=ThisWorkbook/&H00000000 Document=Sheet1/&H00000000 Module=Module1 Document=ThisWorkbook1/&H00000000 Document=Sheet2/&H00000000 Name="VBAProject" HelpContextID="0" VersionCompatible32="393222000" CMG="DBD966ECE4F0E4F0E4F0E4F0" DPB="5557E86E18E919E919E9" GC="CFCD72F0961011111111EE" [Host Extender Info] &H00000001={3832D640-CF90-11CF-8E43-00A0C911005A};VBE;&H00000000 [Workspace] ThisWorkbook=0, 0, 0, 0, C Sheet1=0, 0, 0, 0, C Module1=110, 145, 994, 721, ThisWorkbook1=0, 0, 0, 0, C Sheet2=0, 0, 0, 0, C XlsxWriter-1.1.2/examples/worksheet_protection.py0000644000076500000240000000203513263123126022337 0ustar Johnstaff00000000000000######################################################################## # # Example of cell locking and formula hiding in an Excel worksheet # using Python and the XlsxWriter module. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import xlsxwriter workbook = xlsxwriter.Workbook('protection.xlsx') worksheet = workbook.add_worksheet() # Create some cell formats with protection properties. unlocked = workbook.add_format({'locked': 0}) hidden = workbook.add_format({'hidden': 1}) # Format the columns to make the text more visible. worksheet.set_column('A:A', 40) # Turn worksheet protection on. worksheet.protect() # Write a locked, unlocked and hidden cell. worksheet.write('A1', 'Cell B1 is locked. It cannot be edited.') worksheet.write('A2', 'Cell B2 is unlocked. It can be edited.') worksheet.write('A3', "Cell B3 is hidden. The formula isn't visible.") worksheet.write_formula('B1', '=1+2') # Locked by default. worksheet.write_formula('B2', '=1+2', unlocked) worksheet.write_formula('B3', '=1+2', hidden) workbook.close() XlsxWriter-1.1.2/LICENSE.txt0000644000076500000240000000277512100640427015521 0ustar Johnstaff00000000000000Copyright (c) 2013, John McNamara All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project.XlsxWriter-1.1.2/MANIFEST.in0000644000076500000240000000031512501432251015417 0ustar Johnstaff00000000000000include README.rst include Changes include LICENSE.txt include xlsxwriter/*.py include examples/*.py include examples/*.txt include examples/*.bin include docs/readme.html recursive-include docs/_static * XlsxWriter-1.1.2/PKG-INFO0000644000076500000240000000603513362632140014770 0ustar Johnstaff00000000000000Metadata-Version: 1.1 Name: XlsxWriter Version: 1.1.2 Summary: A Python module for creating Excel XLSX files. Home-page: https://github.com/jmcnamara/XlsxWriter Author: John McNamara Author-email: jmcnamara@cpan.org License: BSD Description: XlsxWriter ========== **XlsxWriter** is a Python module for writing files in the Excel 2007+ XLSX file format. XlsxWriter can be used to write text, numbers, formulas and hyperlinks to multiple worksheets and it supports features such as formatting and many more, including: * 100% compatible Excel XLSX files. * Full formatting. * Merged cells. * Defined names. * Charts. * Autofilters. * Data validation and drop down lists. * Conditional formatting. * Worksheet PNG/JPEG/BMP/WMF/EMF images. * Rich multi-format strings. * Cell comments. * Integration with Pandas. * Textboxes. * Memory optimization mode for writing large files. It supports Python 2.5, 2.6, 2.7, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, Jython and PyPy and uses standard libraries only. Here is a simple example: .. code-block:: python import xlsxwriter # Create an new Excel file and add a worksheet. workbook = xlsxwriter.Workbook('demo.xlsx') worksheet = workbook.add_worksheet() # Widen the first column to make the text clearer. worksheet.set_column('A:A', 20) # Add a bold format to use to highlight cells. bold = workbook.add_format({'bold': True}) # Write some simple text. worksheet.write('A1', 'Hello') # Text with formatting. worksheet.write('A2', 'World', bold) # Write some numbers, with row/column notation. worksheet.write(2, 0, 123) worksheet.write(3, 0, 123.456) # Insert an image. worksheet.insert_image('B5', 'logo.png') workbook.close() .. image:: https://raw.github.com/jmcnamara/XlsxWriter/master/dev/docs/source/_images/demo.png See the full documentation at: https://xlsxwriter.readthedocs.io Release notes: https://xlsxwriter.readthedocs.io/changes.html Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 XlsxWriter-1.1.2/README.rst0000644000076500000240000000326013300031067015350 0ustar Johnstaff00000000000000XlsxWriter ========== **XlsxWriter** is a Python module for writing files in the Excel 2007+ XLSX file format. XlsxWriter can be used to write text, numbers, formulas and hyperlinks to multiple worksheets and it supports features such as formatting and many more, including: * 100% compatible Excel XLSX files. * Full formatting. * Merged cells. * Defined names. * Charts. * Autofilters. * Data validation and drop down lists. * Conditional formatting. * Worksheet PNG/JPEG/BMP/WMF/EMF images. * Rich multi-format strings. * Cell comments. * Integration with Pandas. * Textboxes. * Memory optimization mode for writing large files. It supports Python 2.5, 2.6, 2.7, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, Jython and PyPy and uses standard libraries only. Here is a simple example: .. code-block:: python import xlsxwriter # Create an new Excel file and add a worksheet. workbook = xlsxwriter.Workbook('demo.xlsx') worksheet = workbook.add_worksheet() # Widen the first column to make the text clearer. worksheet.set_column('A:A', 20) # Add a bold format to use to highlight cells. bold = workbook.add_format({'bold': True}) # Write some simple text. worksheet.write('A1', 'Hello') # Text with formatting. worksheet.write('A2', 'World', bold) # Write some numbers, with row/column notation. worksheet.write(2, 0, 123) worksheet.write(3, 0, 123.456) # Insert an image. worksheet.insert_image('B5', 'logo.png') workbook.close() .. image:: https://raw.github.com/jmcnamara/XlsxWriter/master/dev/docs/source/_images/demo.png See the full documentation at: https://xlsxwriter.readthedocs.io Release notes: https://xlsxwriter.readthedocs.io/changes.html XlsxWriter-1.1.2/setup.cfg0000644000076500000240000000007513362632140015512 0ustar Johnstaff00000000000000[wheel] universal = 1 [egg_info] tag_build = tag_date = 0 XlsxWriter-1.1.2/setup.py0000644000076500000240000000304513362631445015412 0ustar Johnstaff00000000000000import sys import subprocess from warnings import warn try: from setuptools import setup, Command except ImportError: from distutils.core import setup, Command if sys.version_info < (2, 5, 0): warn("The minimum Python version supported by XlsxWriter is 2.5.") exit() class PyTest(Command): user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): errno = subprocess.call(['python', '-m', 'unittest', 'discover']) raise SystemExit(errno) setup( name='XlsxWriter', version='1.1.2', author='John McNamara', author_email='jmcnamara@cpan.org', url='https://github.com/jmcnamara/XlsxWriter', packages=['xlsxwriter'], scripts=['examples/vba_extract.py'], cmdclass={'test': PyTest}, license='BSD', description='A Python module for creating Excel XLSX files.', long_description=open('README.rst').read(), classifiers=[ 'Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', 'Programming Language :: Python :: 2.5', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.1', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', ], ) XlsxWriter-1.1.2/xlsxwriter/0000755000076500000240000000000013362632140016122 5ustar Johnstaff00000000000000XlsxWriter-1.1.2/xlsxwriter/__init__.py0000644000076500000240000000011713362631445020241 0ustar Johnstaff00000000000000__version__ = '1.1.2' __VERSION__ = __version__ from .workbook import Workbook XlsxWriter-1.1.2/xlsxwriter/app.py0000644000076500000240000001315313263123126017256 0ustar Johnstaff00000000000000############################################################################### # # App - A class for writing the Excel XLSX App file. # # Copyright 2013-2018, 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 = {} 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 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 element. self._xml_data_element('Application', 'Microsoft Excel') def _write_doc_security(self): # Write the element. self._xml_data_element('DocSecurity', '0') def _write_scale_crop(self): # Write the element. self._xml_data_element('ScaleCrop', 'false') def _write_heading_pairs(self): # Write the 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 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 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 elements such as and . 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 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 element. self._xml_data_element('LinksUpToDate', 'false') def _write_shared_doc(self): # Write the element. self._xml_data_element('SharedDoc', 'false') def _write_hyperlink_base(self): # Write the 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 element. self._xml_data_element('HyperlinksChanged', 'false') def _write_app_version(self): # Write the element. self._xml_data_element('AppVersion', '12.0000') XlsxWriter-1.1.2/xlsxwriter/chart.py0000644000076500000240000035126713342563176017625 0ustar Johnstaff00000000000000############################################################################### # # Chart - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import re import copy from warnings import warn from .shape import Shape from . import xmlwriter from .utility import get_rgb_color from .utility import xl_rowcol_to_cell from .utility import xl_range_formula from .utility import supported_datetime from .utility import datetime_to_excel_datetime from .utility import quote_sheetname class Chart(xmlwriter.XMLwriter): """ A class for writing the Excel XLSX Chart file. """ ########################################################################### # # Public API. # ########################################################################### def __init__(self, options=None): """ Constructor. """ super(Chart, self).__init__() self.subtype = None self.sheet_type = 0x0200 self.orientation = 0x0 self.series = [] self.embedded = 0 self.id = -1 self.series_index = 0 self.style_id = 2 self.axis_ids = [] self.axis2_ids = [] self.cat_has_num_fmt = 0 self.requires_category = False self.legend = {} self.cat_axis_position = 'b' self.val_axis_position = 'l' self.formula_ids = {} self.formula_data = [] self.horiz_cat_axis = 0 self.horiz_val_axis = 1 self.protection = 0 self.chartarea = {} self.plotarea = {} self.x_axis = {} self.y_axis = {} self.y2_axis = {} self.x2_axis = {} self.chart_name = '' self.show_blanks = 'gap' self.show_hidden = 0 self.show_crosses = 1 self.width = 480 self.height = 288 self.x_scale = 1 self.y_scale = 1 self.x_offset = 0 self.y_offset = 0 self.table = None self.cross_between = 'between' self.default_marker = None self.series_gap_1 = None self.series_gap_2 = None self.series_overlap_1 = None self.series_overlap_2 = None self.drop_lines = None self.hi_low_lines = None self.up_down_bars = None self.smooth_allowed = False self.title_font = None self.title_name = None self.title_formula = None self.title_data_id = None self.title_layout = None self.title_overlay = None self.title_none = False self.date_category = False self.date_1904 = False self.remove_timezone = False self.label_positions = {} self.label_position_default = '' self.already_inserted = False self.combined = None self.is_secondary = False self.warn_sheetname = True self._set_default_properties() def add_series(self, options=None): """ Add a data series to a chart. Args: options: A dictionary of chart series options. Returns: Nothing. """ # Add a series and it's properties to a chart. if options is None: options = {} # Check that the required input has been specified. if 'values' not in options: warn("Must specify 'values' in add_series()") return if self.requires_category and 'categories' not in options: warn("Must specify 'categories' in add_series() " "for this chart type") return if len(self.series) == 255: warn("The maximum number of series that can be added to an " "Excel Chart is 255") return # Convert list into a formula string. values = self._list_to_formula(options.get('values')) categories = self._list_to_formula(options.get('categories')) # Switch name and name_formula parameters if required. name, name_formula = self._process_names(options.get('name'), options.get('name_formula')) # Get an id for the data equivalent to the range formula. cat_id = self._get_data_id(categories, options.get('categories_data')) val_id = self._get_data_id(values, options.get('values_data')) name_id = self._get_data_id(name_formula, options.get('name_data')) # Set the line properties for the series. line = Shape._get_line_properties(options.get('line')) # Allow 'border' as a synonym for 'line' in bar/column style charts. if options.get('border'): line = Shape._get_line_properties(options['border']) # Set the fill properties for the series. fill = Shape._get_fill_properties(options.get('fill')) # Set the pattern fill properties for the series. pattern = Shape._get_pattern_properties(options.get('pattern')) # Set the gradient fill properties for the series. gradient = Shape._get_gradient_properties(options.get('gradient')) # Pattern fill overrides solid fill. if pattern: self.fill = None # Gradient fill overrides the solid and pattern fill. if gradient: pattern = None fill = None # Set the marker properties for the series. marker = self._get_marker_properties(options.get('marker')) # Set the trendline properties for the series. trendline = self._get_trendline_properties(options.get('trendline')) # Set the line smooth property for the series. smooth = options.get('smooth') # Set the error bars properties for the series. y_error_bars = self._get_error_bars_props(options.get('y_error_bars')) x_error_bars = self._get_error_bars_props(options.get('x_error_bars')) error_bars = {'x_error_bars': x_error_bars, 'y_error_bars': y_error_bars} # Set the point properties for the series. points = self._get_points_properties(options.get('points')) # Set the labels properties for the series. labels = self._get_labels_properties(options.get('data_labels')) # Set the "invert if negative" fill property. invert_if_neg = options.get('invert_if_negative', False) # Set the secondary axis properties. x2_axis = options.get('x2_axis') y2_axis = options.get('y2_axis') # Store secondary status for combined charts. if x2_axis or y2_axis: self.is_secondary = True # Set the gap for Bar/Column charts. if options.get('gap') is not None: if y2_axis: self.series_gap_2 = options['gap'] else: self.series_gap_1 = options['gap'] # Set the overlap for Bar/Column charts. if options.get('overlap'): if y2_axis: self.series_overlap_2 = options['overlap'] else: self.series_overlap_1 = options['overlap'] # Add the user supplied data to the internal structures. series = { 'values': values, 'categories': categories, 'name': name, 'name_formula': name_formula, 'name_id': name_id, 'val_data_id': val_id, 'cat_data_id': cat_id, 'line': line, 'fill': fill, 'pattern': pattern, 'gradient': gradient, 'marker': marker, 'trendline': trendline, 'labels': labels, 'invert_if_neg': invert_if_neg, 'x2_axis': x2_axis, 'y2_axis': y2_axis, 'points': points, 'error_bars': error_bars, 'smooth': smooth } self.series.append(series) def set_x_axis(self, options): """ Set the chart X axis options. Args: options: A dictionary of axis options. Returns: Nothing. """ axis = self._convert_axis_args(self.x_axis, options) self.x_axis = axis def set_y_axis(self, options): """ Set the chart Y axis options. Args: options: A dictionary of axis options. Returns: Nothing. """ axis = self._convert_axis_args(self.y_axis, options) self.y_axis = axis def set_x2_axis(self, options): """ Set the chart secondary X axis options. Args: options: A dictionary of axis options. Returns: Nothing. """ axis = self._convert_axis_args(self.x2_axis, options) self.x2_axis = axis def set_y2_axis(self, options): """ Set the chart secondary Y axis options. Args: options: A dictionary of axis options. Returns: Nothing. """ axis = self._convert_axis_args(self.y2_axis, options) self.y2_axis = axis def set_title(self, options=None): """ Set the chart title options. Args: options: A dictionary of chart title options. Returns: Nothing. """ if options is None: options = {} name, name_formula = self._process_names(options.get('name'), options.get('name_formula')) data_id = self._get_data_id(name_formula, options.get('data')) self.title_name = name self.title_formula = name_formula self.title_data_id = data_id # Set the font properties if present. self.title_font = self._convert_font_args(options.get('name_font')) # Set the axis name layout. self.title_layout = self._get_layout_properties(options.get('layout'), True) # Set the title overlay option. self.title_overlay = options.get('overlay') # Set the automatic title option. self.title_none = options.get('none') def set_legend(self, options): """ Set the chart legend options. Args: options: A dictionary of chart legend options. Returns: Nothing. """ # Convert the user defined properties to internal properties. self.legend = self._get_legend_properties(options) def set_plotarea(self, options): """ Set the chart plot area options. Args: options: A dictionary of chart plot area options. Returns: Nothing. """ # Convert the user defined properties to internal properties. self.plotarea = self._get_area_properties(options) def set_chartarea(self, options): """ Set the chart area options. Args: options: A dictionary of chart area options. Returns: Nothing. """ # Convert the user defined properties to internal properties. self.chartarea = self._get_area_properties(options) def set_style(self, style_id): """ Set the chart style type. Args: style_id: An int representing the chart style. Returns: Nothing. """ # Set one of the 48 built-in Excel chart styles. The default is 2. if style_id is None: style_id = 2 if style_id < 0 or style_id > 48: style_id = 2 self.style_id = style_id def show_blanks_as(self, option): """ Set the option for displaying blank data in a chart. Args: option: A string representing the display option. Returns: Nothing. """ if not option: return valid_options = { 'gap': 1, 'zero': 1, 'span': 1, } if option not in valid_options: warn("Unknown show_blanks_as() option '%s'" % option) return self.show_blanks = option def show_hidden_data(self): """ Display data on charts from hidden rows or columns. Args: option: A string representing the display option. Returns: Nothing. """ self.show_hidden = 1 def set_size(self, options=None): """ Set size or scale of the chart. Args: options: A dictionary of chart size options. Returns: Nothing. """ if options is None: options = {} # Set dimensions or scale for the chart. self.width = options.get('width', self.width) self.height = options.get('height', self.height) self.x_scale = options.get('x_scale', 1) self.y_scale = options.get('y_scale', 1) self.x_offset = options.get('x_offset', 0) self.y_offset = options.get('y_offset', 0) def set_table(self, options=None): """ Set properties for an axis data table. Args: options: A dictionary of axis table options. Returns: Nothing. """ if options is None: options = {} table = {} table['horizontal'] = options.get('horizontal', 1) table['vertical'] = options.get('vertical', 1) table['outline'] = options.get('outline', 1) table['show_keys'] = options.get('show_keys', 0) table['font'] = self._convert_font_args(options.get('font')) self.table = table def set_up_down_bars(self, options=None): """ Set properties for the chart up-down bars. Args: options: A dictionary of options. Returns: Nothing. """ if options is None: options = {} # Defaults. up_line = None up_fill = None down_line = None down_fill = None # Set properties for 'up' bar. if options.get('up'): if 'border' in options['up']: # Map border to line. up_line = Shape._get_line_properties(options['up']['border']) if 'line' in options['up']: up_line = Shape._get_line_properties(options['up']['line']) if 'fill' in options['up']: up_fill = Shape._get_fill_properties(options['up']['fill']) # Set properties for 'down' bar. if options.get('down'): if 'border' in options['down']: # Map border to line. down_line = \ Shape._get_line_properties(options['down']['border']) if 'line' in options['down']: down_line = Shape._get_line_properties(options['down']['line']) if 'fill' in options['down']: down_fill = Shape._get_fill_properties(options['down']['fill']) self.up_down_bars = {'up': {'line': up_line, 'fill': up_fill, }, 'down': {'line': down_line, 'fill': down_fill, }, } def set_drop_lines(self, options=None): """ Set properties for the chart drop lines. Args: options: A dictionary of options. Returns: Nothing. """ if options is None: options = {} line = Shape._get_line_properties(options.get('line')) fill = Shape._get_fill_properties(options.get('fill')) # Set the pattern fill properties for the series. pattern = Shape._get_pattern_properties(options.get('pattern')) # Set the gradient fill properties for the series. gradient = Shape._get_gradient_properties(options.get('gradient')) # Pattern fill overrides solid fill. if pattern: self.fill = None # Gradient fill overrides the solid and pattern fill. if gradient: pattern = None fill = None self.drop_lines = {'line': line, 'fill': fill, 'pattern': pattern, 'gradient': gradient} def set_high_low_lines(self, options=None): """ Set properties for the chart high-low lines. Args: options: A dictionary of options. Returns: Nothing. """ if options is None: options = {} line = Shape._get_line_properties(options.get('line')) fill = Shape._get_fill_properties(options.get('fill')) # Set the pattern fill properties for the series. pattern = Shape._get_pattern_properties(options.get('pattern')) # Set the gradient fill properties for the series. gradient = Shape._get_gradient_properties(options.get('gradient')) # Pattern fill overrides solid fill. if pattern: self.fill = None # Gradient fill overrides the solid and pattern fill. if gradient: pattern = None fill = None self.hi_low_lines = {'line': line, 'fill': fill, 'pattern': pattern, 'gradient': gradient} def combine(self, chart=None): """ Create a combination chart with a secondary chart. Args: chart: The secondary chart to combine with the primary chart. Returns: Nothing. """ if chart is None: return self.combined = chart ########################################################################### # # Private API. # ########################################################################### def _assemble_xml_file(self): # Assemble and write the XML file. # Write the XML declaration. self._xml_declaration() # Write the c:chartSpace element. self._write_chart_space() # Write the c:lang element. self._write_lang() # Write the c:style element. self._write_style() # Write the c:protection element. self._write_protection() # Write the c:chart element. self._write_chart() # Write the c:spPr element for the chartarea formatting. self._write_sp_pr(self.chartarea) # Write the c:printSettings element. if self.embedded: self._write_print_settings() # Close the worksheet tag. self._xml_end_tag('c:chartSpace') # Close the file. self._xml_close() def _convert_axis_args(self, axis, user_options): # Convert user defined axis values into private hash values. options = axis['defaults'].copy() options.update(user_options) name, name_formula = self._process_names(options.get('name'), options.get('name_formula')) data_id = self._get_data_id(name_formula, options.get('data')) axis = { 'defaults': axis['defaults'], 'name': name, 'formula': name_formula, 'data_id': data_id, 'reverse': options.get('reverse'), 'min': options.get('min'), 'max': options.get('max'), 'minor_unit': options.get('minor_unit'), 'major_unit': options.get('major_unit'), 'minor_unit_type': options.get('minor_unit_type'), 'major_unit_type': options.get('major_unit_type'), 'display_units': options.get('display_units'), 'log_base': options.get('log_base'), 'crossing': options.get('crossing'), 'position_axis': options.get('position_axis'), 'position': options.get('position'), 'label_position': options.get('label_position'), 'label_align': options.get('label_align'), 'num_format': options.get('num_format'), 'num_format_linked': options.get('num_format_linked'), 'interval_unit': options.get('interval_unit'), 'interval_tick': options.get('interval_tick'), 'text_axis': False, } if 'visible' in options: axis['visible'] = options.get('visible') else: axis['visible'] = 1 # Convert the display units. axis['display_units'] = self._get_display_units(axis['display_units']) axis['display_units_visible'] = \ options.get('display_units_visible', True) # Map major_gridlines properties. if (options.get('major_gridlines') and options['major_gridlines']['visible']): axis['major_gridlines'] = \ self._get_gridline_properties(options['major_gridlines']) # Map minor_gridlines properties. if (options.get('minor_gridlines') and options['minor_gridlines']['visible']): axis['minor_gridlines'] = \ self._get_gridline_properties(options['minor_gridlines']) # Only use the first letter of bottom, top, left or right. if axis.get('position'): axis['position'] = axis['position'].lower()[0] # Set the position for a category axis on or between the tick marks. if axis.get('position_axis'): if axis['position_axis'] == 'on_tick': axis['position_axis'] = 'midCat' elif axis['position_axis'] == 'between': # Doesn't need to be modified. pass else: # Otherwise use the default value. axis['position_axis'] = None # Set the category axis as a date axis. if options.get('date_axis'): self.date_category = True # Set the category axis as a text axis. if options.get('text_axis'): self.date_category = False axis['text_axis'] = True # Convert datetime args if required. if axis.get('min') and supported_datetime(axis['min']): axis['min'] = datetime_to_excel_datetime(axis['min'], self.date_1904, self.remove_timezone) if axis.get('max') and supported_datetime(axis['max']): axis['max'] = datetime_to_excel_datetime(axis['max'], self.date_1904, self.remove_timezone) if axis.get('crossing') and supported_datetime(axis['crossing']): axis['crossing'] = datetime_to_excel_datetime(axis['crossing'], self.date_1904, self.remove_timezone) # Set the font properties if present. axis['num_font'] = self._convert_font_args(options.get('num_font')) axis['name_font'] = self._convert_font_args(options.get('name_font')) # Set the axis name layout. axis['name_layout'] = \ self._get_layout_properties(options.get('name_layout'), True) # Set the line properties for the axis. axis['line'] = Shape._get_line_properties(options.get('line')) # Set the fill properties for the axis. axis['fill'] = Shape._get_fill_properties(options.get('fill')) # Set the pattern fill properties for the series. axis['pattern'] = Shape._get_pattern_properties(options.get('pattern')) # Set the gradient fill properties for the series. axis['gradient'] = \ Shape._get_gradient_properties(options.get('gradient')) # Pattern fill overrides solid fill. if axis.get('pattern'): axis['fill'] = None # Gradient fill overrides the solid and pattern fill. if axis.get('gradient'): axis['pattern'] = None axis['fill'] = None # Set the tick marker types. axis['minor_tick_mark'] = \ self._get_tick_type(options.get('minor_tick_mark')) axis['major_tick_mark'] = \ self._get_tick_type(options.get('major_tick_mark')) return axis def _convert_font_args(self, options): # Convert user defined font values into private dict values. if not options: return font = { 'name': options.get('name'), 'color': options.get('color'), 'size': options.get('size'), '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', 0), 'rotation': options.get('rotation'), } # Convert font size units. if font['size']: font['size'] = int(font['size'] * 100) # Convert rotation into 60,000ths of a degree. if font['rotation']: font['rotation'] = 60000 * int(font['rotation']) return font def _list_to_formula(self, data): # Convert and list of row col values to a range formula. # If it isn't an array ref it is probably a formula already. if type(data) is not list: # Check for unquoted sheetnames. if (data and ' ' in data and "'" not in data and self.warn_sheetname): warn("Sheetname in '%s' contains spaces but isn't quoted. " "This may cause errors in Excel." % data) return data formula = xl_range_formula(*data) return formula def _process_names(self, name, name_formula): # Switch name and name_formula parameters if required. if name is not None: if isinstance(name, list): # Convert an list of values into a name formula. cell = xl_rowcol_to_cell(name[1], name[2], True, True) name_formula = quote_sheetname(name[0]) + '!' + cell name = '' elif re.match(r'^=?[^!]+!\$?[A-Z]+\$?[0-9]+', name): # Name looks like a formula, use it to set name_formula. name_formula = name name = '' return name, name_formula def _get_data_type(self, data): # Find the overall type of the data associated with a series. # Check for no data in the series. if data is None or len(data) == 0: return 'none' if isinstance(data[0], list): return 'multi_str' # Determine if data is numeric or strings. for token in data: if token is None: continue try: float(token) except ValueError: # Not a number. Assume entire data series is string data. return 'str' # The series data was all numeric. return 'num' def _get_data_id(self, formula, data): # Assign an id to a each unique series formula or title/axis formula. # Repeated formulas such as for categories get the same id. If the # series or title has user specified data associated with it then # that is also stored. This data is used to populate cached Excel # data when creating a chart. If there is no user defined data then # it will be populated by the parent Workbook._add_chart_data(). # Ignore series without a range formula. if not formula: return # Strip the leading '=' from the formula. if formula.startswith('='): formula = formula.lstrip('=') # Store the data id in a hash keyed by the formula and store the data # in a separate array with the same id. if formula not in self.formula_ids: # Haven't seen this formula before. formula_id = len(self.formula_data) self.formula_data.append(data) self.formula_ids[formula] = formula_id else: # Formula already seen. Return existing id. formula_id = self.formula_ids[formula] # Store user defined data if it isn't already there. if self.formula_data[formula_id] is None: self.formula_data[formula_id] = data return formula_id def _get_marker_properties(self, marker): # Convert user marker properties to the structure required internally. if not marker: return # Copy the user defined properties since they will be modified. marker = copy.deepcopy(marker) types = { 'automatic': 'automatic', 'none': 'none', 'square': 'square', 'diamond': 'diamond', 'triangle': 'triangle', 'x': 'x', 'star': 'star', 'dot': 'dot', 'short_dash': 'dot', 'dash': 'dash', 'long_dash': 'dash', 'circle': 'circle', 'plus': 'plus', 'picture': 'picture', } # Check for valid types. marker_type = marker.get('type') if marker_type is not None: if marker_type in types: marker['type'] = types[marker_type] else: warn("Unknown marker type '%s" % marker_type) return # Set the line properties for the marker. line = Shape._get_line_properties(marker.get('line')) # Allow 'border' as a synonym for 'line'. if 'border' in marker: line = Shape._get_line_properties(marker['border']) # Set the fill properties for the marker. fill = Shape._get_fill_properties(marker.get('fill')) # Set the pattern fill properties for the series. pattern = Shape._get_pattern_properties(marker.get('pattern')) # Set the gradient fill properties for the series. gradient = Shape._get_gradient_properties(marker.get('gradient')) # Pattern fill overrides solid fill. if pattern: self.fill = None # Gradient fill overrides the solid and pattern fill. if gradient: pattern = None fill = None marker['line'] = line marker['fill'] = fill marker['pattern'] = pattern marker['gradient'] = gradient return marker def _get_trendline_properties(self, trendline): # Convert user trendline properties to structure required internally. if not trendline: return # Copy the user defined properties since they will be modified. trendline = copy.deepcopy(trendline) types = { 'exponential': 'exp', 'linear': 'linear', 'log': 'log', 'moving_average': 'movingAvg', 'polynomial': 'poly', 'power': 'power', } # Check the trendline type. trend_type = trendline.get('type') if trend_type in types: trendline['type'] = types[trend_type] else: warn("Unknown trendline type '%s'" % trend_type) return # Set the line properties for the trendline. line = Shape._get_line_properties(trendline.get('line')) # Allow 'border' as a synonym for 'line'. if 'border' in trendline: line = Shape._get_line_properties(trendline['border']) # Set the fill properties for the trendline. fill = Shape._get_fill_properties(trendline.get('fill')) # Set the pattern fill properties for the series. pattern = Shape._get_pattern_properties(trendline.get('pattern')) # Set the gradient fill properties for the series. gradient = Shape._get_gradient_properties(trendline.get('gradient')) # Pattern fill overrides solid fill. if pattern: self.fill = None # Gradient fill overrides the solid and pattern fill. if gradient: pattern = None fill = None trendline['line'] = line trendline['fill'] = fill trendline['pattern'] = pattern trendline['gradient'] = gradient return trendline def _get_error_bars_props(self, options): # Convert user error bars properties to structure required internally. if not options: return # Default values. error_bars = { 'type': 'fixedVal', 'value': 1, 'endcap': 1, 'direction': 'both' } types = { 'fixed': 'fixedVal', 'percentage': 'percentage', 'standard_deviation': 'stdDev', 'standard_error': 'stdErr', 'custom': 'cust', } # Check the error bars type. error_type = options['type'] if error_type in types: error_bars['type'] = types[error_type] else: warn("Unknown error bars type '%s" % error_type) return # Set the value for error types that require it. if 'value' in options: error_bars['value'] = options['value'] # Set the end-cap style. if 'end_style' in options: error_bars['endcap'] = options['end_style'] # Set the error bar direction. if 'direction' in options: if options['direction'] == 'minus': error_bars['direction'] = 'minus' elif options['direction'] == 'plus': error_bars['direction'] = 'plus' else: # Default to 'both'. pass # Set any custom values. error_bars['plus_values'] = options.get('plus_values') error_bars['minus_values'] = options.get('minus_values') error_bars['plus_data'] = options.get('plus_data') error_bars['minus_data'] = options.get('minus_data') # Set the line properties for the error bars. error_bars['line'] = Shape._get_line_properties(options.get('line')) return error_bars def _get_gridline_properties(self, options): # Convert user gridline properties to structure required internally. # Set the visible property for the gridline. gridline = {'visible': options.get('visible')} # Set the line properties for the gridline. gridline['line'] = Shape._get_line_properties(options.get('line')) return gridline def _get_labels_properties(self, labels): # Convert user labels properties to the structure required internally. if not labels: return None # Copy the user defined properties since they will be modified. labels = copy.deepcopy(labels) # Map user defined label positions to Excel positions. position = labels.get('position') if position: if position in self.label_positions: if position == self.label_position_default: labels['position'] = None else: labels['position'] = self.label_positions[position] else: warn("Unsupported label position '%s' for this chart type" % position) return # Map the user defined label separator to the Excel separator. separator = labels.get('separator') separators = { ',': ', ', ';': '; ', '.': '. ', "\n": "\n", ' ': ' ', } if separator: if separator in separators: labels['separator'] = separators[separator] else: warn("Unsupported label separator") return # Set the font properties if present. labels['font'] = self._convert_font_args(labels.get('font')) return labels def _get_area_properties(self, options): # Convert user area properties to the structure required internally. area = {} # Set the line properties for the chartarea. line = Shape._get_line_properties(options.get('line')) # Allow 'border' as a synonym for 'line'. if options.get('border'): line = Shape._get_line_properties(options['border']) # Set the fill properties for the chartarea. fill = Shape._get_fill_properties(options.get('fill')) # Set the pattern fill properties for the series. pattern = Shape._get_pattern_properties(options.get('pattern')) # Set the gradient fill properties for the series. gradient = Shape._get_gradient_properties(options.get('gradient')) # Pattern fill overrides solid fill. if pattern: self.fill = None # Gradient fill overrides the solid and pattern fill. if gradient: pattern = None fill = None # Set the plotarea layout. layout = self._get_layout_properties(options.get('layout'), False) area['line'] = line area['fill'] = fill area['pattern'] = pattern area['layout'] = layout area['gradient'] = gradient return area def _get_legend_properties(self, options=None): # Convert user legend properties to the structure required internally. legend = {} if options is None: options = {} legend['position'] = options.get('position', 'right') legend['delete_series'] = options.get('delete_series') legend['font'] = self._convert_font_args(options.get('font')) legend['layout'] = self._get_layout_properties(options.get('layout'), False) # Turn off the legend. if options.get('none'): legend['position'] = 'none' # Set the line properties for the legend. line = Shape._get_line_properties(options.get('line')) # Allow 'border' as a synonym for 'line'. if options.get('border'): line = Shape._get_line_properties(options['border']) # Set the fill properties for the legend. fill = Shape._get_fill_properties(options.get('fill')) # Set the pattern fill properties for the series. pattern = Shape._get_pattern_properties(options.get('pattern')) # Set the gradient fill properties for the series. gradient = Shape._get_gradient_properties(options.get('gradient')) # Pattern fill overrides solid fill. if pattern: self.fill = None # Gradient fill overrides the solid and pattern fill. if gradient: pattern = None fill = None # Set the legend layout. layout = self._get_layout_properties(options.get('layout'), False) legend['line'] = line legend['fill'] = fill legend['pattern'] = pattern legend['layout'] = layout legend['gradient'] = gradient return legend def _get_layout_properties(self, args, is_text): # Convert user defined layout properties to format used internally. layout = {} if not args: return if is_text: properties = ('x', 'y') else: properties = ('x', 'y', 'width', 'height') # Check for valid properties. for key in args.keys(): if key not in properties: warn("Property '%s' allowed not in layout options" % key) return # Set the layout properties. for prop in properties: if prop not in args.keys(): warn("Property '%s' must be specified in layout options" % prop) return value = args[prop] try: float(value) except ValueError: warn("Property '%s' value '%s' must be numeric in layout" % (prop, value)) return if value < 0 or value > 1: warn("Property '%s' value '%s' must be in range " "0 < x <= 1 in layout options" % (prop, value)) return # Convert to the format used by Excel for easier testing layout[prop] = "%.17g" % value return layout def _get_points_properties(self, user_points): # Convert user points properties to structure required internally. points = [] if not user_points: return for user_point in user_points: point = {} if user_point is not None: # Set the line properties for the point. line = Shape._get_line_properties(user_point.get('line')) # Allow 'border' as a synonym for 'line'. if 'border' in user_point: line = Shape._get_line_properties(user_point['border']) # Set the fill properties for the chartarea. fill = Shape._get_fill_properties(user_point.get('fill')) # Set the pattern fill properties for the series. pattern = \ Shape._get_pattern_properties(user_point.get('pattern')) # Set the gradient fill properties for the series. gradient = \ Shape._get_gradient_properties(user_point.get('gradient')) # Pattern fill overrides solid fill. if pattern: self.fill = None # Gradient fill overrides the solid and pattern fill. if gradient: pattern = None fill = None point['line'] = line point['fill'] = fill point['pattern'] = pattern point['gradient'] = gradient points.append(point) return points def _get_display_units(self, display_units): # Convert user defined display units to internal units. if not display_units: return types = { 'hundreds': 'hundreds', 'thousands': 'thousands', 'ten_thousands': 'tenThousands', 'hundred_thousands': 'hundredThousands', 'millions': 'millions', 'ten_millions': 'tenMillions', 'hundred_millions': 'hundredMillions', 'billions': 'billions', 'trillions': 'trillions', } if display_units in types: display_units = types[display_units] else: warn("Unknown display_units type '%s'" % display_units) return return display_units def _get_tick_type(self, tick_type): # Convert user defined display units to internal units. if not tick_type: return types = { 'outside': 'out', 'inside': 'in', 'none': 'none', 'cross': 'cross', } if tick_type in types: tick_type = types[tick_type] else: warn("Unknown tick_type '%s'" % tick_type) return return tick_type def _get_primary_axes_series(self): # Returns series which use the primary axes. primary_axes_series = [] for series in self.series: if not series['y2_axis']: primary_axes_series.append(series) return primary_axes_series def _get_secondary_axes_series(self): # Returns series which use the secondary axes. secondary_axes_series = [] for series in self.series: if series['y2_axis']: secondary_axes_series.append(series) return secondary_axes_series def _add_axis_ids(self, args): # Add unique ids for primary or secondary axes chart_id = 5001 + int(self.id) axis_count = 1 + len(self.axis2_ids) + len(self.axis_ids) id1 = '%04d%04d' % (chart_id, axis_count) id2 = '%04d%04d' % (chart_id, axis_count + 1) if args['primary_axes']: self.axis_ids.append(id1) self.axis_ids.append(id2) if not args['primary_axes']: self.axis2_ids.append(id1) self.axis2_ids.append(id2) def _set_default_properties(self): # Setup the default properties for a chart. self.x_axis['defaults'] = { 'num_format': 'General', 'major_gridlines': {'visible': 0} } self.y_axis['defaults'] = { 'num_format': 'General', 'major_gridlines': {'visible': 1} } self.x2_axis['defaults'] = { 'num_format': 'General', 'label_position': 'none', 'crossing': 'max', 'visible': 0 } self.y2_axis['defaults'] = { 'num_format': 'General', 'major_gridlines': {'visible': 0}, 'position': 'right', 'visible': 1 } self.set_x_axis({}) self.set_y_axis({}) self.set_x2_axis({}) self.set_y2_axis({}) ########################################################################### # # XML methods. # ########################################################################### def _write_chart_space(self): # Write the element. schema = 'http://schemas.openxmlformats.org/' xmlns_c = schema + 'drawingml/2006/chart' xmlns_a = schema + 'drawingml/2006/main' xmlns_r = schema + 'officeDocument/2006/relationships' attributes = [ ('xmlns:c', xmlns_c), ('xmlns:a', xmlns_a), ('xmlns:r', xmlns_r), ] self._xml_start_tag('c:chartSpace', attributes) def _write_lang(self): # Write the element. val = 'en-US' attributes = [('val', val)] self._xml_empty_tag('c:lang', attributes) def _write_style(self): # Write the element. style_id = self.style_id # Don't write an element for the default style, 2. if style_id == 2: return attributes = [('val', style_id)] self._xml_empty_tag('c:style', attributes) def _write_chart(self): # Write the element. self._xml_start_tag('c:chart') if self.title_none: # Turn off the title. self._write_c_auto_title_deleted() else: # Write the chart title elements. if self.title_formula is not None: self._write_title_formula(self.title_formula, self.title_data_id, None, self.title_font, self.title_layout, self.title_overlay) elif self.title_name is not None: self._write_title_rich(self.title_name, None, self.title_font, self.title_layout, self.title_overlay) # Write the c:plotArea element. self._write_plot_area() # Write the c:legend element. self._write_legend() # Write the c:plotVisOnly element. self._write_plot_vis_only() # Write the c:dispBlanksAs element. self._write_disp_blanks_as() self._xml_end_tag('c:chart') def _write_disp_blanks_as(self): # Write the element. val = self.show_blanks # Ignore the default value. if val == 'gap': return attributes = [('val', val)] self._xml_empty_tag('c:dispBlanksAs', attributes) def _write_plot_area(self): # Write the element. self._xml_start_tag('c:plotArea') # Write the c:layout element. self._write_layout(self.plotarea.get('layout'), 'plot') # Write subclass chart type elements for primary and secondary axes. self._write_chart_type({'primary_axes': True}) self._write_chart_type({'primary_axes': False}) # 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({'primary_axes': True}) second_chart._write_chart_type({'primary_axes': False}) # Write the category and value elements for the primary axes. args = {'x_axis': self.x_axis, 'y_axis': self.y_axis, 'axis_ids': self.axis_ids} if self.date_category: self._write_date_axis(args) else: self._write_cat_axis(args) self._write_val_axis(args) # Write the category and value elements for the secondary axes. args = {'x_axis': self.x2_axis, 'y_axis': self.y2_axis, 'axis_ids': self.axis2_ids} self._write_val_axis(args) # Write the secondary axis for the secondary chart. if second_chart and second_chart.is_secondary: args = {'x_axis': second_chart.x2_axis, 'y_axis': second_chart.y2_axis, 'axis_ids': second_chart.axis2_ids} second_chart._write_val_axis(args) if self.date_category: self._write_date_axis(args) else: self._write_cat_axis(args) # Write the c:dTable element. self._write_d_table() # Write the c:spPr element for the plotarea formatting. self._write_sp_pr(self.plotarea) self._xml_end_tag('c:plotArea') def _write_layout(self, layout, layout_type): # Write the element. if not layout: # Automatic layout. self._xml_empty_tag('c:layout') else: # User defined manual layout. self._xml_start_tag('c:layout') self._write_manual_layout(layout, layout_type) self._xml_end_tag('c:layout') def _write_manual_layout(self, layout, layout_type): # Write the element. self._xml_start_tag('c:manualLayout') # Plotarea has a layoutTarget element. if layout_type == 'plot': self._xml_empty_tag('c:layoutTarget', [('val', 'inner')]) # Set the x, y positions. self._xml_empty_tag('c:xMode', [('val', 'edge')]) self._xml_empty_tag('c:yMode', [('val', 'edge')]) self._xml_empty_tag('c:x', [('val', layout['x'])]) self._xml_empty_tag('c:y', [('val', layout['y'])]) # For plotarea and legend set the width and height. if layout_type != 'text': self._xml_empty_tag('c:w', [('val', layout['width'])]) self._xml_empty_tag('c:h', [('val', layout['height'])]) self._xml_end_tag('c:manualLayout') def _write_chart_type(self, options): # Write the chart type element. This method should be overridden # by the subclasses. return def _write_grouping(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:grouping', attributes) def _write_series(self, series): # Write the series elements. self._write_ser(series) def _write_ser(self, series): # Write the 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['marker']) # Write the c:invertIfNegative element. self._write_c_invert_if_negative(series['invert_if_neg']) # Write the c:dPt element. self._write_d_pt(series['points']) # Write the c:dLbls element. self._write_d_lbls(series['labels']) # Write the c:trendline element. self._write_trendline(series['trendline']) # Write the c:errBars element. self._write_error_bars(series['error_bars']) # Write the c:cat element. self._write_cat(series) # Write the c:val element. self._write_val(series) # Write the c:smooth element. if self.smooth_allowed: self._write_c_smooth(series['smooth']) self._xml_end_tag('c:ser') def _write_idx(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:idx', attributes) def _write_order(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:order', attributes) def _write_series_name(self, series): # Write the series name. if series['name_formula'] is not None: self._write_tx_formula(series['name_formula'], series['name_id']) elif series['name'] is not None: self._write_tx_value(series['name']) def _write_c_smooth(self, smooth): # Write the element. if smooth: self._xml_empty_tag('c:smooth', [('val', '1')]) def _write_cat(self, series): # Write the element. formula = series['categories'] data_id = series['cat_data_id'] data = None if data_id is not None: data = self.formula_data[data_id] # Ignore elements for charts without category values. if not formula: return self._xml_start_tag('c:cat') # Check the type of cached data. cat_type = self._get_data_type(data) if cat_type == 'str': self.cat_has_num_fmt = 0 # Write the c:numRef element. self._write_str_ref(formula, data, cat_type) elif cat_type == 'multi_str': self.cat_has_num_fmt = 0 # Write the c:numRef element. self._write_multi_lvl_str_ref(formula, data) else: self.cat_has_num_fmt = 1 # Write the c:numRef element. self._write_num_ref(formula, data, cat_type) self._xml_end_tag('c:cat') def _write_val(self, series): # Write the element. formula = series['values'] data_id = series['val_data_id'] data = self.formula_data[data_id] self._xml_start_tag('c:val') # 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:val') def _write_num_ref(self, formula, data, ref_type): # Write the element. self._xml_start_tag('c:numRef') # Write the c:f element. self._write_series_formula(formula) if ref_type == 'num': # Write the c:numCache element. self._write_num_cache(data) elif ref_type == 'str': # Write the c:strCache element. self._write_str_cache(data) self._xml_end_tag('c:numRef') def _write_str_ref(self, formula, data, ref_type): # Write the element. self._xml_start_tag('c:strRef') # Write the c:f element. self._write_series_formula(formula) if ref_type == 'num': # Write the c:numCache element. self._write_num_cache(data) elif ref_type == 'str': # Write the c:strCache element. self._write_str_cache(data) self._xml_end_tag('c:strRef') def _write_multi_lvl_str_ref(self, formula, data): # Write the element. if not data: return self._xml_start_tag('c:multiLvlStrRef') # Write the c:f element. self._write_series_formula(formula) self._xml_start_tag('c:multiLvlStrCache') # Write the c:ptCount element. count = len(data[-1]) self._write_pt_count(count) for cat_data in reversed(data): self._xml_start_tag('c:lvl') for i, point in enumerate(cat_data): # Write the c:pt element. self._write_pt(i, cat_data[i]) self._xml_end_tag('c:lvl') self._xml_end_tag('c:multiLvlStrCache') self._xml_end_tag('c:multiLvlStrRef') def _write_series_formula(self, formula): # Write the element. # Strip the leading '=' from the formula. if formula.startswith('='): formula = formula.lstrip('=') self._xml_data_element('c:f', formula) def _write_axis_ids(self, args): # Write the elements for the primary or secondary axes. # Generate the axis ids. self._add_axis_ids(args) if args['primary_axes']: # Write the axis ids for the primary axes. self._write_axis_id(self.axis_ids[0]) self._write_axis_id(self.axis_ids[1]) else: # Write the axis ids for the secondary axes. self._write_axis_id(self.axis2_ids[0]) self._write_axis_id(self.axis2_ids[1]) def _write_axis_id(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:axId', attributes) def _write_cat_axis(self, args): # Write the element. Usually the X axis. x_axis = args['x_axis'] y_axis = args['y_axis'] axis_ids = args['axis_ids'] # If there are no axis_ids then we don't need to write this element. if axis_ids is None or not len(axis_ids): return position = self.cat_axis_position horiz = self.horiz_cat_axis # Overwrite the default axis position with a user supplied value. if x_axis.get('position'): position = x_axis['position'] self._xml_start_tag('c:catAx') self._write_axis_id(axis_ids[0]) # Write the c:scaling element. self._write_scaling(x_axis.get('reverse'), None, None, None) if not x_axis.get('visible'): self._write_delete(1) # Write the c:axPos element. self._write_axis_pos(position, y_axis.get('reverse')) # Write the c:majorGridlines element. self._write_major_gridlines(x_axis.get('major_gridlines')) # Write the c:minorGridlines element. self._write_minor_gridlines(x_axis.get('minor_gridlines')) # Write the axis title elements. if x_axis['formula'] is not None: self._write_title_formula(x_axis['formula'], x_axis['data_id'], horiz, x_axis['name_font'], x_axis['name_layout']) elif x_axis['name'] is not None: self._write_title_rich(x_axis['name'], horiz, x_axis['name_font'], x_axis['name_layout']) # Write the c:numFmt element. self._write_cat_number_format(x_axis) # Write the c:majorTickMark element. self._write_major_tick_mark(x_axis.get('major_tick_mark')) # Write the c:minorTickMark element. self._write_minor_tick_mark(x_axis.get('minor_tick_mark')) # Write the c:tickLblPos element. self._write_tick_label_pos(x_axis.get('label_position')) # Write the c:spPr element for the axis line. self._write_sp_pr(x_axis) # Write the axis font elements. self._write_axis_font(x_axis.get('num_font')) # Write the c:crossAx element. self._write_cross_axis(axis_ids[1]) if self.show_crosses or x_axis.get('visible'): # Note, the category crossing comes from the value axis. if (y_axis.get('crossing') is None or y_axis.get('crossing') == 'max'): # Write the c:crosses element. self._write_crosses(y_axis.get('crossing')) else: # Write the c:crossesAt element. self._write_c_crosses_at(y_axis.get('crossing')) # Write the c:auto element. if not x_axis.get('text_axis'): self._write_auto(1) # Write the c:labelAlign element. self._write_label_align(x_axis.get('label_align')) # Write the c:labelOffset element. self._write_label_offset(100) # Write the c:tickLblSkip element. self._write_c_tick_lbl_skip(x_axis.get('interval_unit')) # Write the c:tickMarkSkip element. self._write_c_tick_mark_skip(x_axis.get('interval_tick')) self._xml_end_tag('c:catAx') def _write_val_axis(self, args): # Write the element. Usually the Y axis. x_axis = args['x_axis'] y_axis = args['y_axis'] axis_ids = args['axis_ids'] position = args.get('position', self.val_axis_position) horiz = self.horiz_val_axis # If there are no axis_ids then we don't need to write this element. if axis_ids is None or not len(axis_ids): return # Overwrite the default axis position with a user supplied value. position = y_axis.get('position') or position self._xml_start_tag('c:valAx') self._write_axis_id(axis_ids[1]) # Write the c:scaling element. self._write_scaling(y_axis.get('reverse'), y_axis.get('min'), y_axis.get('max'), y_axis.get('log_base')) if not y_axis.get('visible'): self._write_delete(1) # Write the c:axPos element. self._write_axis_pos(position, x_axis.get('reverse')) # Write the c:majorGridlines element. self._write_major_gridlines(y_axis.get('major_gridlines')) # Write the c:minorGridlines element. self._write_minor_gridlines(y_axis.get('minor_gridlines')) # Write the axis title elements. if y_axis['formula'] is not None: self._write_title_formula(y_axis['formula'], y_axis['data_id'], horiz, y_axis['name_font'], y_axis['name_layout']) elif y_axis['name'] is not None: self._write_title_rich(y_axis['name'], horiz, y_axis.get('name_font'), y_axis.get('name_layout')) # Write the c:numberFormat element. self._write_number_format(y_axis) # Write the c:majorTickMark element. self._write_major_tick_mark(y_axis.get('major_tick_mark')) # Write the c:minorTickMark element. self._write_minor_tick_mark(y_axis.get('minor_tick_mark')) # Write the c:tickLblPos element. self._write_tick_label_pos(y_axis.get('label_position')) # Write the c:spPr element for the axis line. self._write_sp_pr(y_axis) # Write the axis font elements. self._write_axis_font(y_axis.get('num_font')) # Write the c:crossAx element. self._write_cross_axis(axis_ids[0]) # Note, the category crossing comes from the value axis. if x_axis.get('crossing') is None or x_axis['crossing'] == 'max': # Write the c:crosses element. self._write_crosses(x_axis.get('crossing')) else: # Write the c:crossesAt element. self._write_c_crosses_at(x_axis.get('crossing')) # Write the c:crossBetween element. self._write_cross_between(x_axis.get('position_axis')) # Write the c:majorUnit element. self._write_c_major_unit(y_axis.get('major_unit')) # Write the c:minorUnit element. self._write_c_minor_unit(y_axis.get('minor_unit')) # Write the c:dispUnits element. self._write_disp_units(y_axis.get('display_units'), y_axis.get('display_units_visible')) self._xml_end_tag('c:valAx') def _write_cat_val_axis(self, args): # Write the element. This is for the second valAx # in scatter plots. Usually the X axis. x_axis = args['x_axis'] y_axis = args['y_axis'] axis_ids = args['axis_ids'] position = args['position'] or self.val_axis_position horiz = self.horiz_val_axis # If there are no axis_ids then we don't need to write this element. if axis_ids is None or not len(axis_ids): return # Overwrite the default axis position with a user supplied value. position = x_axis.get('position') or position self._xml_start_tag('c:valAx') self._write_axis_id(axis_ids[0]) # Write the c:scaling element. self._write_scaling(x_axis.get('reverse'), x_axis.get('min'), x_axis.get('max'), x_axis.get('log_base')) if not x_axis.get('visible'): self._write_delete(1) # Write the c:axPos element. self._write_axis_pos(position, y_axis.get('reverse')) # Write the c:majorGridlines element. self._write_major_gridlines(x_axis.get('major_gridlines')) # Write the c:minorGridlines element. self._write_minor_gridlines(x_axis.get('minor_gridlines')) # Write the axis title elements. if x_axis['formula'] is not None: self._write_title_formula(x_axis['formula'], x_axis['data_id'], horiz, x_axis['name_font'], x_axis['name_layout']) elif x_axis['name'] is not None: self._write_title_rich(x_axis['name'], horiz, x_axis['name_font'], x_axis['name_layout']) # Write the c:numberFormat element. self._write_number_format(x_axis) # Write the c:majorTickMark element. self._write_major_tick_mark(x_axis.get('major_tick_mark')) # Write the c:minorTickMark element. self._write_minor_tick_mark(x_axis.get('minor_tick_mark')) # Write the c:tickLblPos element. self._write_tick_label_pos(x_axis.get('label_position')) # Write the c:spPr element for the axis line. self._write_sp_pr(x_axis) # Write the axis font elements. self._write_axis_font(x_axis.get('num_font')) # Write the c:crossAx element. self._write_cross_axis(axis_ids[1]) # Note, the category crossing comes from the value axis. if y_axis.get('crossing') is None or y_axis['crossing'] == 'max': # Write the c:crosses element. self._write_crosses(y_axis.get('crossing')) else: # Write the c:crossesAt element. self._write_c_crosses_at(y_axis.get('crossing')) # Write the c:crossBetween element. self._write_cross_between(y_axis.get('position_axis')) # Write the c:majorUnit element. self._write_c_major_unit(x_axis.get('major_unit')) # Write the c:minorUnit element. self._write_c_minor_unit(x_axis.get('minor_unit')) # Write the c:dispUnits element. self._write_disp_units(x_axis.get('display_units'), x_axis.get('display_units_visible')) self._xml_end_tag('c:valAx') def _write_date_axis(self, args): # Write the element. Usually the X axis. x_axis = args['x_axis'] y_axis = args['y_axis'] axis_ids = args['axis_ids'] # If there are no axis_ids then we don't need to write this element. if axis_ids is None or not len(axis_ids): return position = self.cat_axis_position # Overwrite the default axis position with a user supplied value. position = x_axis.get('position') or position self._xml_start_tag('c:dateAx') self._write_axis_id(axis_ids[0]) # Write the c:scaling element. self._write_scaling(x_axis.get('reverse'), x_axis.get('min'), x_axis.get('max'), x_axis.get('log_base')) if not x_axis.get('visible'): self._write_delete(1) # Write the c:axPos element. self._write_axis_pos(position, y_axis.get('reverse')) # Write the c:majorGridlines element. self._write_major_gridlines(x_axis.get('major_gridlines')) # Write the c:minorGridlines element. self._write_minor_gridlines(x_axis.get('minor_gridlines')) # Write the axis title elements. if x_axis['formula'] is not None: self._write_title_formula(x_axis['formula'], x_axis['data_id'], None, x_axis['name_font'], x_axis['name_layout']) elif x_axis['name'] is not None: self._write_title_rich(x_axis['name'], None, x_axis['name_font'], x_axis['name_layout']) # Write the c:numFmt element. self._write_number_format(x_axis) # Write the c:majorTickMark element. self._write_major_tick_mark(x_axis.get('major_tick_mark')) # Write the c:minorTickMark element. self._write_minor_tick_mark(x_axis.get('minor_tick_mark')) # Write the c:tickLblPos element. self._write_tick_label_pos(x_axis.get('label_position')) # Write the c:spPr element for the axis line. self._write_sp_pr(x_axis) # Write the axis font elements. self._write_axis_font(x_axis.get('num_font')) # Write the c:crossAx element. self._write_cross_axis(axis_ids[1]) if self.show_crosses or x_axis.get('visible'): # Note, the category crossing comes from the value axis. if (y_axis.get('crossing') is None or y_axis.get('crossing') == 'max'): # Write the c:crosses element. self._write_crosses(y_axis.get('crossing')) else: # Write the c:crossesAt element. self._write_c_crosses_at(y_axis.get('crossing')) # Write the c:auto element. self._write_auto(1) # Write the c:labelOffset element. self._write_label_offset(100) # Write the c:tickLblSkip element. self._write_c_tick_lbl_skip(x_axis.get('interval_unit')) # Write the c:tickMarkSkip element. self._write_c_tick_mark_skip(x_axis.get('interval_tick')) # Write the c:majorUnit element. self._write_c_major_unit(x_axis.get('major_unit')) # Write the c:majorTimeUnit element. if x_axis.get('major_unit'): self._write_c_major_time_unit(x_axis['major_unit_type']) # Write the c:minorUnit element. self._write_c_minor_unit(x_axis.get('minor_unit')) # Write the c:minorTimeUnit element. if x_axis.get('minor_unit'): self._write_c_minor_time_unit(x_axis['minor_unit_type']) self._xml_end_tag('c:dateAx') def _write_scaling(self, reverse, min_val, max_val, log_base): # Write the element. self._xml_start_tag('c:scaling') # Write the c:logBase element. self._write_c_log_base(log_base) # Write the c:orientation element. self._write_orientation(reverse) # Write the c:max element. self._write_c_max(max_val) # Write the c:min element. self._write_c_min(min_val) self._xml_end_tag('c:scaling') def _write_c_log_base(self, val): # Write the element. if not val: return attributes = [('val', val)] self._xml_empty_tag('c:logBase', attributes) def _write_orientation(self, reverse): # Write the element. val = 'minMax' if reverse: val = 'maxMin' attributes = [('val', val)] self._xml_empty_tag('c:orientation', attributes) def _write_c_max(self, max_val): # Write the element. if max_val is None: return attributes = [('val', max_val)] self._xml_empty_tag('c:max', attributes) def _write_c_min(self, min_val): # Write the element. if min_val is None: return attributes = [('val', min_val)] self._xml_empty_tag('c:min', attributes) def _write_axis_pos(self, val, reverse): # Write the element. if reverse: if val == 'l': val = 'r' if val == 'b': val = 't' attributes = [('val', val)] self._xml_empty_tag('c:axPos', attributes) def _write_number_format(self, axis): # Write the element. Note: It is assumed that if # a user defined number format is supplied (i.e., non-default) then # the sourceLinked attribute is 0. # The user can override this if required. format_code = axis.get('num_format') source_linked = 1 # Check if a user defined number format has been set. if (format_code is not None and format_code != axis['defaults']['num_format']): source_linked = 0 # User override of sourceLinked. if axis.get('num_format_linked'): source_linked = 1 attributes = [ ('formatCode', format_code), ('sourceLinked', source_linked), ] self._xml_empty_tag('c:numFmt', attributes) def _write_cat_number_format(self, axis): # Write the element. Special case handler for category # axes which don't always have a number format. format_code = axis.get('num_format') source_linked = 1 default_format = 1 # Check if a user defined number format has been set. if (format_code is not None and format_code != axis['defaults']['num_format']): source_linked = 0 default_format = 0 # User override of sourceLinked. if axis.get('num_format_linked'): source_linked = 1 # Skip if cat doesn't have a num format (unless it is non-default). if not self.cat_has_num_fmt and default_format: return attributes = [ ('formatCode', format_code), ('sourceLinked', source_linked), ] self._xml_empty_tag('c:numFmt', attributes) def _write_data_label_number_format(self, format_code): # Write the element for data labels. source_linked = 0 attributes = [ ('formatCode', format_code), ('sourceLinked', source_linked), ] self._xml_empty_tag('c:numFmt', attributes) def _write_major_tick_mark(self, val): # Write the element. if not val: return attributes = [('val', val)] self._xml_empty_tag('c:majorTickMark', attributes) def _write_minor_tick_mark(self, val): # Write the element. if not val: return attributes = [('val', val)] self._xml_empty_tag('c:minorTickMark', attributes) def _write_tick_label_pos(self, val=None): # Write the element. if val is None or val == 'next_to': val = 'nextTo' attributes = [('val', val)] self._xml_empty_tag('c:tickLblPos', attributes) def _write_cross_axis(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:crossAx', attributes) def _write_crosses(self, val=None): # Write the element. if val is None: val = 'autoZero' attributes = [('val', val)] self._xml_empty_tag('c:crosses', attributes) def _write_c_crosses_at(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:crossesAt', attributes) def _write_auto(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:auto', attributes) def _write_label_align(self, val=None): # Write the element. if val is None: val = 'ctr' if val is 'right': val = 'r' if val is 'left': val = 'l' attributes = [('val', val)] self._xml_empty_tag('c:lblAlgn', attributes) def _write_label_offset(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:lblOffset', attributes) def _write_c_tick_lbl_skip(self, val): # Write the element. if val is None: return attributes = [('val', val)] self._xml_empty_tag('c:tickLblSkip', attributes) def _write_c_tick_mark_skip(self, val): # Write the element. if val is None: return attributes = [('val', val)] self._xml_empty_tag('c:tickMarkSkip', attributes) def _write_major_gridlines(self, gridlines): # Write the element. if not gridlines: return if not gridlines['visible']: return if gridlines['line']['defined']: self._xml_start_tag('c:majorGridlines') # Write the c:spPr element. self._write_sp_pr(gridlines) self._xml_end_tag('c:majorGridlines') else: self._xml_empty_tag('c:majorGridlines') def _write_minor_gridlines(self, gridlines): # Write the element. if not gridlines: return if not gridlines['visible']: return if gridlines['line']['defined']: self._xml_start_tag('c:minorGridlines') # Write the c:spPr element. self._write_sp_pr(gridlines) self._xml_end_tag('c:minorGridlines') else: self._xml_empty_tag('c:minorGridlines') def _write_cross_between(self, val): # Write the element. if val is None: val = self.cross_between attributes = [('val', val)] self._xml_empty_tag('c:crossBetween', attributes) def _write_c_major_unit(self, val): # Write the element. if not val: return attributes = [('val', val)] self._xml_empty_tag('c:majorUnit', attributes) def _write_c_minor_unit(self, val): # Write the element. if not val: return attributes = [('val', val)] self._xml_empty_tag('c:minorUnit', attributes) def _write_c_major_time_unit(self, val=None): # Write the element. if val is None: val = 'days' attributes = [('val', val)] self._xml_empty_tag('c:majorTimeUnit', attributes) def _write_c_minor_time_unit(self, val=None): # Write the element. if val is None: val = 'days' attributes = [('val', val)] self._xml_empty_tag('c:minorTimeUnit', attributes) def _write_legend(self): # Write the 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() if font: self._write_tx_pr(None, font) # Write the c:spPr element. self._write_sp_pr(legend) self._xml_end_tag('c:legend') def _write_legend_pos(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:legendPos', attributes) def _write_legend_entry(self, index): # Write the element. self._xml_start_tag('c:legendEntry') # Write the c:idx element. self._write_idx(index) # Write the c:delete element. self._write_delete(1) self._xml_end_tag('c:legendEntry') def _write_overlay(self): # Write the element. val = 1 attributes = [('val', val)] self._xml_empty_tag('c:overlay', attributes) def _write_plot_vis_only(self): # Write the element. val = 1 # Ignore this element if we are plotting hidden data. if self.show_hidden: return attributes = [('val', val)] self._xml_empty_tag('c:plotVisOnly', attributes) def _write_print_settings(self): # Write the element. self._xml_start_tag('c:printSettings') # Write the c:headerFooter element. self._write_header_footer() # Write the c:pageMargins element. self._write_page_margins() # Write the c:pageSetup element. self._write_page_setup() self._xml_end_tag('c:printSettings') def _write_header_footer(self): # Write the element. self._xml_empty_tag('c:headerFooter') def _write_page_margins(self): # Write the element. bottom = 0.75 left = 0.7 right = 0.7 top = 0.75 header = 0.3 footer = 0.3 attributes = [ ('b', bottom), ('l', left), ('r', right), ('t', top), ('header', header), ('footer', footer), ] self._xml_empty_tag('c:pageMargins', attributes) def _write_page_setup(self): # Write the element. self._xml_empty_tag('c:pageSetup') def _write_c_auto_title_deleted(self): # Write the element. self._xml_empty_tag('c:autoTitleDeleted', [('val', 1)]) def _write_title_rich(self, title, horiz, font, layout, overlay=False): # Write the element for a rich string. self._xml_start_tag('c:title') # Write the c:tx element. self._write_tx_rich(title, horiz, font) # Write the c:layout element. self._write_layout(layout, 'text') # Write the c:overlay element. if overlay: self._write_overlay() self._xml_end_tag('c:title') def _write_title_formula(self, title, data_id, horiz, font, layout, overlay=False): # Write the element for a rich string. self._xml_start_tag('c:title') # Write the c:tx element. self._write_tx_formula(title, data_id) # Write the c:layout element. self._write_layout(layout, 'text') # Write the c:overlay element. if overlay: self._write_overlay() # Write the c:txPr element. self._write_tx_pr(horiz, font) self._xml_end_tag('c:title') def _write_tx_rich(self, title, horiz, font): # Write the element. self._xml_start_tag('c:tx') # Write the c:rich element. self._write_rich(title, horiz, font) self._xml_end_tag('c:tx') def _write_tx_value(self, title): # Write the element with a value such as for series names. self._xml_start_tag('c:tx') # Write the c:v element. self._write_v(title) self._xml_end_tag('c:tx') def _write_tx_formula(self, title, data_id): # Write the element. data = None if data_id is not None: data = self.formula_data[data_id] self._xml_start_tag('c:tx') # Write the c:strRef element. self._write_str_ref(title, data, 'str') self._xml_end_tag('c:tx') def _write_rich(self, title, horiz, font): # Write the element. if font and font.get('rotation'): rotation = font['rotation'] else: rotation = None self._xml_start_tag('c:rich') # 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_rich(title, font) self._xml_end_tag('c:rich') def _write_a_body_pr(self, rotation, horiz): # Write the element. attributes = [] if rotation is None and horiz: rotation = -5400000 if rotation is not None: attributes.append(('rot', rotation)) if horiz: attributes.append(('vert', 'horz')) self._xml_empty_tag('a:bodyPr', attributes) def _write_a_lst_style(self): # Write the element. self._xml_empty_tag('a:lstStyle') def _write_a_p_rich(self, title, font): # Write the element for rich string titles. self._xml_start_tag('a:p') # Write the a:pPr element. self._write_a_p_pr_rich(font) # Write the a:r element. self._write_a_r(title, font) self._xml_end_tag('a:p') def _write_a_p_formula(self, font): # Write the element for formula titles. self._xml_start_tag('a:p') # Write the a:pPr element. self._write_a_p_pr_formula(font) # Write the a:endParaRPr element. self._write_a_end_para_rpr() self._xml_end_tag('a:p') def _write_a_p_pr_rich(self, font): # Write the element for rich string titles. self._xml_start_tag('a:pPr') # Write the a:defRPr element. self._write_a_def_rpr(font) self._xml_end_tag('a:pPr') def _write_a_p_pr_formula(self, font): # Write the element for formula titles. self._xml_start_tag('a:pPr') # Write the a:defRPr element. self._write_a_def_rpr(font) self._xml_end_tag('a:pPr') def _write_a_def_rpr(self, font): # Write the element. has_color = 0 style_attributes = Shape._get_font_style_attributes(font) latin_attributes = Shape._get_font_latin_attributes(font) if font and font.get('color') is not None: has_color = 1 if latin_attributes or has_color: self._xml_start_tag('a:defRPr', style_attributes) if has_color: self._write_a_solid_fill({'color': font['color']}) if latin_attributes: self._write_a_latin(latin_attributes) self._xml_end_tag('a:defRPr') else: self._xml_empty_tag('a:defRPr', style_attributes) def _write_a_end_para_rpr(self): # Write the element. lang = 'en-US' attributes = [('lang', lang)] self._xml_empty_tag('a:endParaRPr', attributes) def _write_a_r(self, title, font): # Write the element. self._xml_start_tag('a:r') # Write the a:rPr element. self._write_a_r_pr(font) # Write the a:t element. self._write_a_t(title) self._xml_end_tag('a:r') def _write_a_r_pr(self, font): # Write the element. has_color = 0 lang = 'en-US' style_attributes = Shape._get_font_style_attributes(font) latin_attributes = Shape._get_font_latin_attributes(font) if font and font['color'] is not None: has_color = 1 # Add the lang type to the attributes. style_attributes.insert(0, ('lang', lang)) if latin_attributes or has_color: self._xml_start_tag('a:rPr', style_attributes) if has_color: self._write_a_solid_fill({'color': font['color']}) if latin_attributes: self._write_a_latin(latin_attributes) self._xml_end_tag('a:rPr') else: self._xml_empty_tag('a:rPr', style_attributes) def _write_a_t(self, title): # Write the element. self._xml_data_element('a:t', title) def _write_tx_pr(self, horiz, font): # Write the element. 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_formula(font) self._xml_end_tag('c:txPr') def _write_marker(self, marker): # Write the element. if marker is None: marker = self.default_marker if not marker: return if marker['type'] == 'automatic': return self._xml_start_tag('c:marker') # Write the c:symbol element. self._write_symbol(marker['type']) # Write the c:size element. if marker.get('size'): self._write_marker_size(marker['size']) # Write the c:spPr element. self._write_sp_pr(marker) self._xml_end_tag('c:marker') def _write_marker_size(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:size', attributes) def _write_symbol(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:symbol', attributes) def _write_sp_pr(self, series): # Write the element. has_fill = False has_line = False has_pattern = series.get('pattern') has_gradient = series.get('gradient') if series.get('fill') and series['fill']['defined']: has_fill = True if series.get('line') and series['line']['defined']: has_line = True if (not has_fill and not has_line and not has_pattern and not has_gradient): return self._xml_start_tag('c:spPr') # Write the fill elements for solid charts such as pie and bar. if series.get('fill') and series['fill']['defined']: if 'none' in series['fill']: # Write the a:noFill element. self._write_a_no_fill() else: # Write the a:solidFill element. self._write_a_solid_fill(series['fill']) if series.get('pattern'): # Write the a:gradFill element. self._write_a_patt_fill(series['pattern']) if series.get('gradient'): # Write the a:gradFill element. self._write_a_grad_fill(series['gradient']) # Write the a:ln element. if series.get('line') and series['line']['defined']: self._write_a_ln(series['line']) self._xml_end_tag('c:spPr') def _write_a_ln(self, line): # Write the element. attributes = [] # Add the line width as an attribute. width = line.get('width') if width: # Round width to nearest 0.25, like Excel. width = int((width + 0.125) * 4) / 4.0 # Convert to internal units. width = int(0.5 + (12700 * width)) attributes = [('w', width)] self._xml_start_tag('a:ln', attributes) # Write the line fill. if 'none' in line: # Write the a:noFill element. self._write_a_no_fill() elif 'color' in line: # Write the a:solidFill element. self._write_a_solid_fill(line) # Write the line/dash type. line_type = line.get('dash_type') if line_type: # Write the a:prstDash element. self._write_a_prst_dash(line_type) self._xml_end_tag('a:ln') def _write_a_no_fill(self): # Write the element. self._xml_empty_tag('a:noFill') def _write_a_solid_fill(self, fill): # Write the element. self._xml_start_tag('a:solidFill') if 'color' in fill: color = get_rgb_color(fill['color']) transparency = fill.get('transparency') # Write the a:srgbClr element. self._write_a_srgb_clr(color, transparency) self._xml_end_tag('a:solidFill') def _write_a_srgb_clr(self, val, transparency=None): # Write the element. attributes = [('val', val)] if transparency: self._xml_start_tag('a:srgbClr', attributes) # Write the a:alpha element. self._write_a_alpha(transparency) self._xml_end_tag('a:srgbClr') else: self._xml_empty_tag('a:srgbClr', attributes) def _write_a_alpha(self, val): # Write the element. val = int((100 - int(val)) * 1000) attributes = [('val', val)] self._xml_empty_tag('a:alpha', attributes) def _write_a_prst_dash(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('a:prstDash', attributes) def _write_trendline(self, trendline): # Write the element. if not trendline: return self._xml_start_tag('c:trendline') # Write the c:name element. self._write_name(trendline.get('name')) # Write the c:spPr element. self._write_sp_pr(trendline) # Write the c:trendlineType element. self._write_trendline_type(trendline['type']) # Write the c:order element for polynomial trendlines. if trendline['type'] == 'poly': self._write_trendline_order(trendline.get('order')) # Write the c:period element for moving average trendlines. if trendline['type'] == 'movingAvg': self._write_period(trendline.get('period')) # Write the c:forward element. self._write_forward(trendline.get('forward')) # Write the c:backward element. self._write_backward(trendline.get('backward')) if 'intercept' in trendline: # Write the c:intercept element. self._write_c_intercept(trendline['intercept']) if trendline.get('display_r_squared'): # Write the c:dispRSqr element. self._write_c_disp_rsqr() if trendline.get('display_equation'): # Write the c:dispEq element. self._write_c_disp_eq() # Write the c:trendlineLbl element. self._write_c_trendline_lbl() self._xml_end_tag('c:trendline') def _write_trendline_type(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:trendlineType', attributes) def _write_name(self, data): # Write the element. if data is None: return self._xml_data_element('c:name', data) def _write_trendline_order(self, val): # Write the element. if val < 2: val = 2 attributes = [('val', val)] self._xml_empty_tag('c:order', attributes) def _write_period(self, val): # Write the element. if val < 2: val = 2 attributes = [('val', val)] self._xml_empty_tag('c:period', attributes) def _write_forward(self, val): # Write the element. if not val: return attributes = [('val', val)] self._xml_empty_tag('c:forward', attributes) def _write_backward(self, val): # Write the element. if not val: return attributes = [('val', val)] self._xml_empty_tag('c:backward', attributes) def _write_c_intercept(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:intercept', attributes) def _write_c_disp_eq(self): # Write the element. attributes = [('val', 1)] self._xml_empty_tag('c:dispEq', attributes) def _write_c_disp_rsqr(self): # Write the element. attributes = [('val', 1)] self._xml_empty_tag('c:dispRSqr', attributes) def _write_c_trendline_lbl(self): # Write the element. self._xml_start_tag('c:trendlineLbl') # Write the c:layout element. self._write_layout(None, None) # Write the c:numFmt element. self._write_trendline_num_fmt() self._xml_end_tag('c:trendlineLbl') def _write_trendline_num_fmt(self): # Write the element. attributes = [ ('formatCode', 'General'), ('sourceLinked', 0), ] self._xml_empty_tag('c:numFmt', attributes) def _write_hi_low_lines(self): # Write the element. hi_low_lines = self.hi_low_lines if hi_low_lines is None: return if 'line' in hi_low_lines and hi_low_lines['line']['defined']: self._xml_start_tag('c:hiLowLines') # Write the c:spPr element. self._write_sp_pr(hi_low_lines) self._xml_end_tag('c:hiLowLines') else: self._xml_empty_tag('c:hiLowLines') def _write_drop_lines(self): # Write the element. drop_lines = self.drop_lines if drop_lines is None: return if drop_lines['line']['defined']: self._xml_start_tag('c:dropLines') # Write the c:spPr element. self._write_sp_pr(drop_lines) self._xml_end_tag('c:dropLines') else: self._xml_empty_tag('c:dropLines') def _write_overlap(self, val): # Write the element. if val is None: return attributes = [('val', val)] self._xml_empty_tag('c:overlap', attributes) def _write_num_cache(self, data): # Write the element. if data: count = len(data) else: count = 0 self._xml_start_tag('c:numCache') # Write the c:formatCode element. self._write_format_code('General') # Write the c:ptCount element. self._write_pt_count(count) for i in range(count): token = data[i] if token is None: continue try: float(token) except ValueError: # Write non-numeric data as 0. token = 0 # Write the c:pt element. self._write_pt(i, token) self._xml_end_tag('c:numCache') def _write_str_cache(self, data): # Write the element. count = len(data) self._xml_start_tag('c:strCache') # Write the c:ptCount element. self._write_pt_count(count) for i in range(count): # Write the c:pt element. self._write_pt(i, data[i]) self._xml_end_tag('c:strCache') def _write_format_code(self, data): # Write the element. self._xml_data_element('c:formatCode', data) def _write_pt_count(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:ptCount', attributes) def _write_pt(self, idx, value): # Write the element. if value is None: return attributes = [('idx', idx)] self._xml_start_tag('c:pt', attributes) # Write the c:v element. self._write_v(value) self._xml_end_tag('c:pt') def _write_v(self, data): # Write the element. self._xml_data_element('c:v', data) def _write_protection(self): # Write the element. if not self.protection: return self._xml_empty_tag('c:protection') def _write_d_pt(self, points): # Write the elements. index = -1 if not points: return for point in points: index += 1 if not point: continue self._write_d_pt_point(index, point) def _write_d_pt_point(self, index, point): # Write an individual element. self._xml_start_tag('c:dPt') # Write the c:idx element. self._write_idx(index) # Write the c:spPr element. self._write_sp_pr(point) self._xml_end_tag('c:dPt') def _write_d_lbls(self, labels): # Write the element. if not labels: return self._xml_start_tag('c:dLbls') # Write the c:numFmt element. if labels.get('num_format'): self._write_data_label_number_format(labels['num_format']) # Write the data label font elements. if labels.get('font'): self._write_axis_font(labels['font']) # Write the c:dLblPos element. if labels.get('position'): self._write_d_lbl_pos(labels['position']) # Write the c:showLegendKey element. if labels.get('legend_key'): self._write_show_legend_key() # Write the c:showVal element. if labels.get('value'): self._write_show_val() # Write the c:showCatName element. if labels.get('category'): self._write_show_cat_name() # Write the c:showSerName element. if labels.get('series_name'): self._write_show_ser_name() # Write the c:showPercent element. if labels.get('percentage'): self._write_show_percent() # Write the c:separator element. if labels.get('separator'): self._write_separator(labels['separator']) # Write the c:showLeaderLines element. if labels.get('leader_lines'): self._write_show_leader_lines() self._xml_end_tag('c:dLbls') def _write_show_legend_key(self): # Write the element. val = '1' attributes = [('val', val)] self._xml_empty_tag('c:showLegendKey', attributes) def _write_show_val(self): # Write the element. val = 1 attributes = [('val', val)] self._xml_empty_tag('c:showVal', attributes) def _write_show_cat_name(self): # Write the element. val = 1 attributes = [('val', val)] self._xml_empty_tag('c:showCatName', attributes) def _write_show_ser_name(self): # Write the element. val = 1 attributes = [('val', val)] self._xml_empty_tag('c:showSerName', attributes) def _write_show_percent(self): # Write the element. val = 1 attributes = [('val', val)] self._xml_empty_tag('c:showPercent', attributes) def _write_separator(self, data): # Write the element. self._xml_data_element('c:separator', data) def _write_show_leader_lines(self): # Write the element. val = 1 attributes = [('val', val)] self._xml_empty_tag('c:showLeaderLines', attributes) def _write_d_lbl_pos(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:dLblPos', attributes) def _write_delete(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:delete', attributes) def _write_c_invert_if_negative(self, invert): # Write the element. val = 1 if not invert: return attributes = [('val', val)] self._xml_empty_tag('c:invertIfNegative', attributes) def _write_axis_font(self, font): # Write the axis font elements. if not font: return self._xml_start_tag('c:txPr') self._write_a_body_pr(font.get('rotation'), None) self._write_a_lst_style() self._xml_start_tag('a:p') self._write_a_p_pr_rich(font) self._write_a_end_para_rpr() self._xml_end_tag('a:p') self._xml_end_tag('c:txPr') def _write_a_latin(self, attributes): # Write the element. self._xml_empty_tag('a:latin', attributes) def _write_d_table(self): # Write the element. table = self.table if not table: return self._xml_start_tag('c:dTable') if table['horizontal']: # Write the c:showHorzBorder element. self._write_show_horz_border() if table['vertical']: # Write the c:showVertBorder element. self._write_show_vert_border() if table['outline']: # Write the c:showOutline element. self._write_show_outline() if table['show_keys']: # Write the c:showKeys element. self._write_show_keys() if table['font']: # Write the table font. self._write_tx_pr(None, table['font']) self._xml_end_tag('c:dTable') def _write_show_horz_border(self): # Write the element. attributes = [('val', 1)] self._xml_empty_tag('c:showHorzBorder', attributes) def _write_show_vert_border(self): # Write the element. attributes = [('val', 1)] self._xml_empty_tag('c:showVertBorder', attributes) def _write_show_outline(self): # Write the element. attributes = [('val', 1)] self._xml_empty_tag('c:showOutline', attributes) def _write_show_keys(self): # Write the element. attributes = [('val', 1)] self._xml_empty_tag('c:showKeys', attributes) def _write_error_bars(self, error_bars): # Write the X and Y error bars. if not error_bars: return if error_bars['x_error_bars']: self._write_err_bars('x', error_bars['x_error_bars']) if error_bars['y_error_bars']: self._write_err_bars('y', error_bars['y_error_bars']) def _write_err_bars(self, direction, error_bars): # Write the element. if not error_bars: return self._xml_start_tag('c:errBars') # Write the c:errDir element. self._write_err_dir(direction) # Write the c:errBarType element. self._write_err_bar_type(error_bars['direction']) # Write the c:errValType element. self._write_err_val_type(error_bars['type']) if not error_bars['endcap']: # Write the c:noEndCap element. self._write_no_end_cap() if error_bars['type'] == 'stdErr': # Don't need to write a c:errValType tag. pass elif error_bars['type'] == 'cust': # Write the custom error tags. self._write_custom_error(error_bars) else: # Write the c:val element. self._write_error_val(error_bars['value']) # Write the c:spPr element. self._write_sp_pr(error_bars) self._xml_end_tag('c:errBars') def _write_err_dir(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:errDir', attributes) def _write_err_bar_type(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:errBarType', attributes) def _write_err_val_type(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('c:errValType', attributes) def _write_no_end_cap(self): # Write the element. attributes = [('val', 1)] self._xml_empty_tag('c:noEndCap', attributes) def _write_error_val(self, val): # Write the element for error bars. attributes = [('val', val)] self._xml_empty_tag('c:val', attributes) def _write_custom_error(self, error_bars): # Write the custom error bars tags. if error_bars['plus_values']: # Write the c:plus element. self._xml_start_tag('c:plus') if isinstance(error_bars['plus_values'], list): self._write_num_lit(error_bars['plus_values']) else: self._write_num_ref(error_bars['plus_values'], error_bars['plus_data'], 'num') self._xml_end_tag('c:plus') if error_bars['minus_values']: # Write the c:minus element. self._xml_start_tag('c:minus') if isinstance(error_bars['minus_values'], list): self._write_num_lit(error_bars['minus_values']) else: self._write_num_ref(error_bars['minus_values'], error_bars['minus_data'], 'num') self._xml_end_tag('c:minus') def _write_num_lit(self, data): # Write the element for literal number list elements. count = len(data) # Write the c:numLit element. self._xml_start_tag('c:numLit') # Write the c:formatCode element. self._write_format_code('General') # Write the c:ptCount element. self._write_pt_count(count) for i in range(count): token = data[i] if token is None: continue try: float(token) except ValueError: # Write non-numeric data as 0. token = 0 # Write the c:pt element. self._write_pt(i, token) self._xml_end_tag('c:numLit') def _write_up_down_bars(self): # Write the element. up_down_bars = self.up_down_bars if up_down_bars is None: return self._xml_start_tag('c:upDownBars') # Write the c:gapWidth element. self._write_gap_width(150) # Write the c:upBars element. self._write_up_bars(up_down_bars.get('up')) # Write the c:downBars element. self._write_down_bars(up_down_bars.get('down')) self._xml_end_tag('c:upDownBars') def _write_gap_width(self, val): # Write the element. if val is None: return attributes = [('val', val)] self._xml_empty_tag('c:gapWidth', attributes) def _write_up_bars(self, bar_format): # Write the element. if bar_format['line'] and bar_format['line']['defined']: self._xml_start_tag('c:upBars') # Write the c:spPr element. self._write_sp_pr(bar_format) self._xml_end_tag('c:upBars') else: self._xml_empty_tag('c:upBars') def _write_down_bars(self, bar_format): # Write the element. if bar_format['line'] and bar_format['line']['defined']: self._xml_start_tag('c:downBars') # Write the c:spPr element. self._write_sp_pr(bar_format) self._xml_end_tag('c:downBars') else: self._xml_empty_tag('c:downBars') def _write_disp_units(self, units, display): # Write the element. if not units: return attributes = [('val', units)] self._xml_start_tag('c:dispUnits') self._xml_empty_tag('c:builtInUnit', attributes) if display: self._xml_start_tag('c:dispUnitsLbl') self._xml_empty_tag('c:layout') self._xml_end_tag('c:dispUnitsLbl') self._xml_end_tag('c:dispUnits') def _write_a_grad_fill(self, gradient): # Write the element. attributes = [('flip', 'none'), ('rotWithShape', '1')] if gradient['type'] == 'linear': attributes = [] self._xml_start_tag('a:gradFill', attributes) # Write the a:gsLst element. self._write_a_gs_lst(gradient) if gradient['type'] == 'linear': # Write the a:lin element. self._write_a_lin(gradient['angle']) else: # Write the a:path element. self._write_a_path(gradient['type']) # Write the a:tileRect element. self._write_a_tile_rect(gradient['type']) self._xml_end_tag('a:gradFill') def _write_a_gs_lst(self, gradient): # Write the element. positions = gradient['positions'] colors = gradient['colors'] self._xml_start_tag('a:gsLst') for i in range(len(colors)): pos = int(positions[i] * 1000) attributes = [('pos', pos)] self._xml_start_tag('a:gs', attributes) # Write the a:srgbClr element. # TODO: Wait for a feature request to support transparency. color = get_rgb_color(colors[i]) self._write_a_srgb_clr(color) self._xml_end_tag('a:gs') self._xml_end_tag('a:gsLst') def _write_a_lin(self, angle): # Write the element. angle = int(60000 * angle) attributes = [ ('ang', angle), ('scaled', '0'), ] self._xml_empty_tag('a:lin', attributes) def _write_a_path(self, gradient_type): # Write the element. attributes = [('path', gradient_type)] self._xml_start_tag('a:path', attributes) # Write the a:fillToRect element. self._write_a_fill_to_rect(gradient_type) self._xml_end_tag('a:path') def _write_a_fill_to_rect(self, gradient_type): # Write the element. if gradient_type == 'shape': attributes = [ ('l', '50000'), ('t', '50000'), ('r', '50000'), ('b', '50000'), ] else: attributes = [ ('l', '100000'), ('t', '100000'), ] self._xml_empty_tag('a:fillToRect', attributes) def _write_a_tile_rect(self, gradient_type): # Write the element. if gradient_type == 'shape': attributes = [] else: attributes = [ ('r', '-100000'), ('b', '-100000'), ] self._xml_empty_tag('a:tileRect', attributes) def _write_a_patt_fill(self, pattern): # Write the element. attributes = [('prst', pattern['pattern'])] self._xml_start_tag('a:pattFill', attributes) # Write the a:fgClr element. self._write_a_fg_clr(pattern['fg_color']) # Write the a:bgClr element. self._write_a_bg_clr(pattern['bg_color']) self._xml_end_tag('a:pattFill') def _write_a_fg_clr(self, color): # Write the element. color = get_rgb_color(color) self._xml_start_tag('a:fgClr') # Write the a:srgbClr element. self._write_a_srgb_clr(color) self._xml_end_tag('a:fgClr') def _write_a_bg_clr(self, color): # Write the element. color = get_rgb_color(color) self._xml_start_tag('a:bgClr') # Write the a:srgbClr element. self._write_a_srgb_clr(color) self._xml_end_tag('a:bgClr') XlsxWriter-1.1.2/xlsxwriter/chart_area.py0000644000076500000240000000511413263123126020565 0ustar Johnstaff00000000000000############################################################################### # # ChartArea - A class for writing the Excel XLSX Area charts. # # Copyright 2013-2018, 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 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') XlsxWriter-1.1.2/xlsxwriter/chart_bar.py0000644000076500000240000001127213263123126020423 0ustar Johnstaff00000000000000############################################################################### # # ChartBar - A class for writing the Excel XLSX Bar charts. # # Copyright 2013-2018, 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 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: if 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 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 XlsxWriter-1.1.2/xlsxwriter/chart_column.py0000644000076500000240000000673113263123126021160 0ustar Johnstaff00000000000000############################################################################### # # ChartColumn - A class for writing the Excel XLSX Column charts. # # Copyright 2013-2018, 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 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: if 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 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 XlsxWriter-1.1.2/xlsxwriter/chart_doughnut.py0000644000076500000240000000524113263123126021513 0ustar Johnstaff00000000000000############################################################################### # # ChartDoughnut - A class for writing the Excel XLSX Doughnut charts. # # Copyright 2013-2018, 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__() if options is None: options = {} 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 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 element. attributes = [('val', self.hole_size)] self._xml_empty_tag('c:holeSize', attributes) XlsxWriter-1.1.2/xlsxwriter/chart_line.py0000644000076500000240000000633713263123126020614 0ustar Johnstaff00000000000000############################################################################### # # ChartLine - A class for writing the Excel XLSX Line charts. # # Copyright 2013-2018, 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.default_marker = {'type': 'none'} self.smooth_allowed = 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'} ########################################################################### # # 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 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:lineChart') # Write the c:grouping element. self._write_grouping('standard') # 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 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 element without a sub-element. attributes = [('val', 1)] self._xml_empty_tag('c:marker', attributes) XlsxWriter-1.1.2/xlsxwriter/chart_pie.py0000644000076500000240000001440513340606703020440 0ustar Johnstaff00000000000000############################################################################### # # ChartPie - A class for writing the Excel XLSX Pie charts. # # Copyright 2013-2018, 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__() if options is None: options = {} 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 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 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) # 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 to legend. # Write the 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 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 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 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 element. attributes = [('val', 1)] self._xml_empty_tag('c:varyColors', attributes) def _write_first_slice_ang(self): # Write the element. attributes = [('val', self.rotation)] self._xml_empty_tag('c:firstSliceAng', attributes) XlsxWriter-1.1.2/xlsxwriter/chart_radar.py0000644000076500000240000000521313263123126020746 0ustar Johnstaff00000000000000############################################################################### # # ChartRadar - A class for writing the Excel XLSX Radar charts. # # Copyright 2013-2018, 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 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 element. val = 'marker' if self.subtype == 'filled': val = 'filled' attributes = [('val', val)] self._xml_empty_tag('c:radarStyle', attributes) XlsxWriter-1.1.2/xlsxwriter/chart_scatter.py0000644000076500000240000002261613263123126021330 0ustar Johnstaff00000000000000############################################################################### # # ChartScatter - A class for writing the Excel XLSX Scatter charts. # # Copyright 2013-2018, 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 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 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 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 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) # TODO. Can a scatter plot have non-numeric 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 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 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 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') XlsxWriter-1.1.2/xlsxwriter/chart_stock.py0000644000076500000240000000702413263123126021002 0ustar Johnstaff00000000000000############################################################################### # # ChartStock - A class for writing the Excel XLSX Stock charts. # # Copyright 2013-2018, 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__() if options is None: options = {} 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 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 XlsxWriter-1.1.2/xlsxwriter/chartsheet.py0000644000076500000240000001267213346556565020660 0ustar Johnstaff00000000000000############################################################################### # # Chartsheet - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013-2018, 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 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 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) XlsxWriter-1.1.2/xlsxwriter/comments.py0000644000076500000240000001266113350306223020324 0ustar Johnstaff00000000000000############################################################################### # # Comments - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import re from . import xmlwriter 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 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 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 element. self._xml_data_element('author', data) def _write_comment_list(self, comment_data): # Write the 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 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 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 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 element. attributes = [] if re.search(r'^\s', text) or re.search(r'\s$', text): attributes.append(('xml:space', 'preserve')) self._xml_data_element('t', text, attributes) def _write_r_pr(self, font): # Write the 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 element. attributes = [('val', font_size)] self._xml_empty_tag('sz', attributes) def _write_color(self): # Write the element. attributes = [('indexed', 81)] self._xml_empty_tag('color', attributes) def _write_r_font(self, font_name): # Write the element. attributes = [('val', font_name)] self._xml_empty_tag('rFont', attributes) def _write_family(self, font_family): # Write the element. attributes = [('val', font_family)] self._xml_empty_tag('family', attributes) XlsxWriter-1.1.2/xlsxwriter/compat_collections.py0000644000076500000240000001744713123761763022403 0ustar Johnstaff00000000000000""" From the GC3Pie project: https://code.google.com/p/gc3pie/ A backport of the Python standard `collections` package, providing `namedtuple` and `defaultdict` also on Python 2.4 and 2.5. This package actually imports your Python `collections`, and adds its own versions of `namedtuple` and `defaultdict` only if they are missing. """ from collections import * import sys try: defaultdict except NameError: class defaultdict(dict): """ A backport of `defaultdict` to Python 2.4 See http://docs.python.org/library/collections.html """ def __new__(cls, default_factory=None): return dict.__new__(cls) def __init__(self, default_factory): self.default_factory = default_factory def __missing__(self, key): try: return self.default_factory() except: raise KeyError("Key '%s' not in dictionary" % key) def __getitem__(self, key): if not dict.__contains__(self, key): dict.__setitem__(self, key, self.__missing__(key)) return dict.__getitem__(self, key) try: namedtuple except NameError: # Use Raymond Hettinger's original `namedtuple` package. # # Source originally taken from: # http://code.activestate.com/recipes/500261-named-tuples/ from operator import itemgetter as _itemgetter from keyword import iskeyword as _iskeyword import sys as _sys def namedtuple(typename, field_names, verbose=False, rename=False): """Returns a new subclass of tuple with named fields. >>> Point = namedtuple('Point', 'x y') >>> Point.__doc__ # docstring for the new class 'Point(x, y)' >>> p = Point(11, y=22) # instantiate with positional args or keywords >>> p[0] + p[1] # indexable like a plain tuple 33 >>> x, y = p # unpack like a regular tuple >>> x, y (11, 22) >>> p.x + p.y # fields also accessible by name 33 >>> d = p._asdict() # convert to a dictionary >>> d['x'] 11 >>> Point(**d) # convert from a dictionary Point(x=11, y=22) >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields Point(x=100, y=22) """ # Parse and validate the field names. Validation serves two purposes, # generating informative error messages and preventing template injection attacks. if isinstance(field_names, basestring): field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas field_names = tuple(map(str, field_names)) if rename: names = list(field_names) seen = set() for i, name in enumerate(names): if (not min(c.isalnum() or c == '_' for c in name) or _iskeyword(name) or not name or name[0].isdigit() or name.startswith('_') or name in seen): names[i] = '_%d' % i seen.add(name) field_names = tuple(names) for name in (typename,) + field_names: if not min(c.isalnum() or c == '_' for c in name): raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name) if _iskeyword(name): raise ValueError('Type names and field names cannot be a keyword: %r' % name) if name[0].isdigit(): raise ValueError('Type names and field names cannot start with a number: %r' % name) seen_names = set() for name in field_names: if name.startswith('_') and not rename: raise ValueError('Field names cannot start with an underscore: %r' % name) if name in seen_names: raise ValueError('Encountered duplicate field name: %r' % name) seen_names.add(name) # Create and fill-in the class template numfields = len(field_names) argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes reprtxt = ', '.join('%s=%%r' % name for name in field_names) template = '''class %(typename)s(tuple): '%(typename)s(%(argtxt)s)' \n __slots__ = () \n _fields = %(field_names)r \n def __new__(_cls, %(argtxt)s): return _tuple.__new__(_cls, (%(argtxt)s)) \n @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new %(typename)s object from a sequence or iterable' result = new(cls, iterable) if len(result) != %(numfields)d: raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result)) return result \n def __repr__(self): return '%(typename)s(%(reprtxt)s)' %% self \n def _asdict(self): 'Return a new dict which maps field names to their values' return dict(zip(self._fields, self)) \n def _replace(_self, **kwds): 'Return a new %(typename)s object replacing specified fields with new values' result = _self._make(map(kwds.pop, %(field_names)r, _self)) if kwds: raise ValueError('Got unexpected field names: %%r' %% kwds.keys()) return result \n def __getnewargs__(self): return tuple(self) \n\n''' % locals() for i, name in enumerate(field_names): template += ' %s = _property(_itemgetter(%d))\n' % (name, i) if verbose: print(template) # Execute the template string in a temporary namespace namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename, _property=property, _tuple=tuple) try: exec(template) in namespace except SyntaxError: e = sys.exc_info()[1] raise SyntaxError(str(e) + ':\n' + template) result = namespace[typename] # For pickling to work, the __module__ variable needs to be set to the frame # where the named tuple is created. Bypass this step in environments where # sys._getframe is not defined (Jython for example) or sys._getframe is not # defined for arguments greater than 0 (IronPython). try: result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass return result if __name__ == '__main__': # verify that instances can be pickled from cPickle import loads, dumps Point = namedtuple('Point', 'x, y', True) p = Point(x=10, y=20) assert p == loads(dumps(p, -1)) # test and demonstrate ability to override methods class Point(namedtuple('Point', 'x y')): @property def hypot(self): return (self.x ** 2 + self.y ** 2) ** 0.5 def __str__(self): return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot) for p in Point(3, 4), Point(14, 5), Point(9. / 7, 6): print(p) class Point(namedtuple('Point', 'x y')): 'Point class with optimized _make() and _replace() without error-checking' _make = classmethod(tuple.__new__) def _replace(self, _map=map, **kwds): return self._make(_map(kwds.get, ('x', 'y'), self)) print(Point(11, 22)._replace(x=100)) import doctest TestResults = namedtuple('TestResults', 'failed attempted') print(TestResults(*doctest.testmod())) XlsxWriter-1.1.2/xlsxwriter/compatibility.py0000644000076500000240000000246313263123126021351 0ustar Johnstaff00000000000000############################################################################### # # Python 2/3 compatibility functions for XlsxWriter. # # Copyright (c), 2013-2018, John McNamara, jmcnamara@cpan.org # import sys from decimal import Decimal try: # For compatibility between Python 2 and 3. from StringIO import StringIO except ImportError: from io import StringIO try: # For Python 2.6+. from fractions import Fraction except ImportError: Fraction = float try: # For Python 2.6+. from collections import defaultdict from collections import namedtuple except ImportError: # For Python 2.5 support. from .compat_collections import defaultdict from .compat_collections import namedtuple # Types to check in Python 2/3. if sys.version_info[0] == 2: int_types = (int, long) num_types = (float, int, long, Decimal, Fraction) str_types = basestring else: int_types = (int) num_types = (float, int, Decimal, Fraction) str_types = str if sys.version_info < (2, 6, 0): from StringIO import StringIO as BytesIO else: from io import BytesIO as BytesIO def force_unicode(string): """Return string as a native string""" if sys.version_info[0] == 2: if isinstance(string, unicode): return string.encode('utf-8') return string XlsxWriter-1.1.2/xlsxwriter/contenttypes.py0000644000076500000240000001566713300030011021227 0ustar Johnstaff00000000000000############################################################################### # # ContentTypes - A class for writing the Excel XLSX ContentTypes file. # # Copyright 2013-2018, 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': self.overrides[i][1] = 'application/vnd.ms-excel.' \ 'sheet.macroEnabled.main+xml' 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')) ########################################################################### # # XML methods. # ########################################################################### def _write_defaults(self): # Write out all of the 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 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 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 element. attributes = [ ('Extension', extension), ('ContentType', content_type), ] self._xml_empty_tag('Default', attributes) def _write_override(self, part_name, content_type): # Write the element. attributes = [ ('PartName', part_name), ('ContentType', content_type), ] self._xml_empty_tag('Override', attributes) XlsxWriter-1.1.2/xlsxwriter/core.py0000644000076500000240000001253213263123126017426 0ustar Johnstaff00000000000000############################################################################### # # Core - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013-2018, 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 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 element. data = self.properties.get('author', '') self._xml_data_element('dc:creator', data) def _write_cp_last_modified_by(self): # Write the element. data = self.properties.get('author', '') self._xml_data_element('cp:lastModifiedBy', data) def _write_dcterms_created(self): # Write the 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 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 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 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 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 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 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 element. if 'status' in self.properties: data = self.properties['status'] else: return self._xml_data_element('cp:contentStatus', data) XlsxWriter-1.1.2/xlsxwriter/custom.py0000644000076500000240000000731513263123126020013 0ustar Johnstaff00000000000000############################################################################### # # Custom - A class for writing the Excel XLSX Custom Property file. # # Copyright 2013-2018, 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 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 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 element. self._xml_data_element('vt:lpwstr', value) def _write_vt_filetime(self, value): # Write the element. self._xml_data_element('vt:filetime', value) def _write_vt_i4(self, value): # Write the element. self._xml_data_element('vt:i4', value) def _write_vt_r8(self, value): # Write the element. self._xml_data_element('vt:r8', value) def _write_vt_bool(self, value): # Write the element. if value: value = 'true' else: value = 'false' self._xml_data_element('vt:bool', value) XlsxWriter-1.1.2/xlsxwriter/drawing.py0000644000076500000240000007610113263123126020133 0ustar Johnstaff00000000000000############################################################################### # # Drawing - A class for writing the Excel XLSX Drawing file. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # from . import xmlwriter from .shape import Shape from .utility import get_rgb_color class Drawing(xmlwriter.XMLwriter): """ A class for writing the Excel XLSX Drawing file. """ ########################################################################### # # Public API. # ########################################################################### def __init__(self): """ Constructor. """ super(Drawing, self).__init__() self.drawings = [] self.embedded = 0 self.orientation = 0 ########################################################################### # # Private API. # ########################################################################### def _assemble_xml_file(self): # Assemble and write the XML file. # Write the XML declaration. self._xml_declaration() # Write the xdr:wsDr element. self._write_drawing_workspace() if self.embedded: index = 1 for drawing in self.drawings: # Write the xdr:twoCellAnchor element. self._write_two_cell_anchor(index, drawing) index += 1 if drawing['url']: index += 1 else: # Write the xdr:absoluteAnchor element. self._write_absolute_anchor(1) self._xml_end_tag('xdr:wsDr') # Close the file. self._xml_close() def _add_drawing_object(self, drawing_object): # Add a chart, image or shape sub object to the drawing. obj = { 'anchor_type': drawing_object[0], 'col_from': drawing_object[1], 'row_from': drawing_object[2], 'col_from_offset': drawing_object[3], 'row_from_offset': drawing_object[4], 'col_to': drawing_object[5], 'row_to': drawing_object[6], 'col_to_offset': drawing_object[7], 'row_to_offset': drawing_object[8], 'col_absolute': drawing_object[9], 'row_absolute': drawing_object[10], 'width': drawing_object[11], 'height': drawing_object[12], 'description': drawing_object[13], 'shape': drawing_object[14], 'url': None, 'tip': None, 'anchor': None } if len(drawing_object) > 15: obj['url'] = drawing_object[15] obj['tip'] = drawing_object[16] obj['anchor'] = drawing_object[17] self.drawings.append(obj) ########################################################################### # # XML methods. # ########################################################################### def _write_drawing_workspace(self): # Write the element. schema = 'http://schemas.openxmlformats.org/drawingml/' xmlns_xdr = schema + '2006/spreadsheetDrawing' xmlns_a = schema + '2006/main' attributes = [ ('xmlns:xdr', xmlns_xdr), ('xmlns:a', xmlns_a), ] self._xml_start_tag('xdr:wsDr', attributes) def _write_two_cell_anchor(self, index, drawing): # Write the element. shape = drawing['shape'] options = { 'description': drawing['description'], 'url': drawing['url'], 'tip': drawing['tip'] } attributes = [] # Add attribute for images. if drawing['anchor_type'] == 2: if drawing['anchor'] == 3: attributes.append(('editAs', 'absolute')) elif drawing['anchor'] == 1: pass else: attributes.append(('editAs', 'oneCell')) # Add editAs attribute for shapes. if shape and shape.edit_as: attributes.append(('editAs', shape.edit_as)) self._xml_start_tag('xdr:twoCellAnchor', attributes) # Write the xdr:from element. self._write_from( drawing['col_from'], drawing['row_from'], drawing['col_from_offset'], drawing['row_from_offset']) # Write the xdr:from element. self._write_to( drawing['col_to'], drawing['row_to'], drawing['col_to_offset'], drawing['row_to_offset']) if drawing['anchor_type'] == 1: # Graphic frame. # Write the xdr:graphicFrame element for charts. self._write_graphic_frame(index, drawing['description']) elif drawing['anchor_type'] == 2: # Write the xdr:pic element. self._write_pic(index, drawing['col_absolute'], drawing['row_absolute'], drawing['width'], drawing['height'], shape, options) else: # Write the xdr:sp element for shapes. self._write_sp(index, drawing['col_absolute'], drawing['row_absolute'], drawing['width'], drawing['height'], shape) # Write the xdr:clientData element. self._write_client_data() self._xml_end_tag('xdr:twoCellAnchor') def _write_absolute_anchor(self, frame_index): self._xml_start_tag('xdr:absoluteAnchor') # Write the element. # Different co-ordinates for horizontal (= 0) and vertical (= 1). if self.orientation == 0: # Write the xdr:pos element. self._write_pos(0, 0) # Write the xdr:ext element. self._write_ext(9308969, 6078325) else: # Write the xdr:pos element. self._write_pos(0, -47625) # Write the xdr:ext element. self._write_ext(6162675, 6124575) # Write the xdr:graphicFrame element. self._write_graphic_frame(frame_index) # Write the xdr:clientData element. self._write_client_data() self._xml_end_tag('xdr:absoluteAnchor') def _write_from(self, col, row, col_offset, row_offset): # Write the element. self._xml_start_tag('xdr:from') # Write the xdr:col element. self._write_col(col) # Write the xdr:colOff element. self._write_col_off(col_offset) # Write the xdr:row element. self._write_row(row) # Write the xdr:rowOff element. self._write_row_off(row_offset) self._xml_end_tag('xdr:from') def _write_to(self, col, row, col_offset, row_offset): # Write the element. self._xml_start_tag('xdr:to') # Write the xdr:col element. self._write_col(col) # Write the xdr:colOff element. self._write_col_off(col_offset) # Write the xdr:row element. self._write_row(row) # Write the xdr:rowOff element. self._write_row_off(row_offset) self._xml_end_tag('xdr:to') def _write_col(self, data): # Write the element. self._xml_data_element('xdr:col', data) def _write_col_off(self, data): # Write the element. self._xml_data_element('xdr:colOff', data) def _write_row(self, data): # Write the element. self._xml_data_element('xdr:row', data) def _write_row_off(self, data): # Write the element. self._xml_data_element('xdr:rowOff', data) def _write_pos(self, x, y): # Write the element. attributes = [('x', x), ('y', y)] self._xml_empty_tag('xdr:pos', attributes) def _write_ext(self, cx, cy): # Write the element. attributes = [('cx', cx), ('cy', cy)] self._xml_empty_tag('xdr:ext', attributes) def _write_graphic_frame(self, index, name=None): # Write the element. attributes = [('macro', '')] self._xml_start_tag('xdr:graphicFrame', attributes) # Write the xdr:nvGraphicFramePr element. self._write_nv_graphic_frame_pr(index, name) # Write the xdr:xfrm element. self._write_xfrm() # Write the a:graphic element. self._write_atag_graphic(index) self._xml_end_tag('xdr:graphicFrame') def _write_nv_graphic_frame_pr(self, index, name): # Write the element. if not name: name = 'Chart ' + str(index) self._xml_start_tag('xdr:nvGraphicFramePr') # Write the xdr:cNvPr element. self._write_c_nv_pr(index + 1, name) # Write the xdr:cNvGraphicFramePr element. self._write_c_nv_graphic_frame_pr() self._xml_end_tag('xdr:nvGraphicFramePr') def _write_c_nv_pr(self, index, name, options=None): # Write the element. if options is None: options = {} descr = options.get('description', None) url = options.get('url', None) tip = options.get('tip', None) attributes = [('id', index), ('name', name)] # Add description attribute for images. if descr is not None: attributes.append(('descr', descr)) if url: self._xml_start_tag('xdr:cNvPr', attributes) schema = "http://schemas.openxmlformats.org" att = [ ('xmlns:r', schema + "/officeDocument/2006/relationships"), ('r:id', "rId" + str(index - 1)) ] if tip: att.append(('tooltip', tip)) self._xml_empty_tag('a:hlinkClick', att) self._xml_end_tag('xdr:cNvPr') else: self._xml_empty_tag('xdr:cNvPr', attributes) def _write_c_nv_graphic_frame_pr(self): # Write the element. if self.embedded: self._xml_empty_tag('xdr:cNvGraphicFramePr') else: self._xml_start_tag('xdr:cNvGraphicFramePr') # Write the a:graphicFrameLocks element. self._write_a_graphic_frame_locks() self._xml_end_tag('xdr:cNvGraphicFramePr') def _write_a_graphic_frame_locks(self): # Write the element. attributes = [('noGrp', 1)] self._xml_empty_tag('a:graphicFrameLocks', attributes) def _write_xfrm(self): # Write the element. self._xml_start_tag('xdr:xfrm') # Write the xfrmOffset element. self._write_xfrm_offset() # Write the xfrmOffset element. self._write_xfrm_extension() self._xml_end_tag('xdr:xfrm') def _write_xfrm_offset(self): # Write the xfrm sub-element. attributes = [ ('x', 0), ('y', 0), ] self._xml_empty_tag('a:off', attributes) def _write_xfrm_extension(self): # Write the xfrm sub-element. attributes = [ ('cx', 0), ('cy', 0), ] self._xml_empty_tag('a:ext', attributes) def _write_atag_graphic(self, index): # Write the element. self._xml_start_tag('a:graphic') # Write the a:graphicData element. self._write_atag_graphic_data(index) self._xml_end_tag('a:graphic') def _write_atag_graphic_data(self, index): # Write the element. uri = 'http://schemas.openxmlformats.org/drawingml/2006/chart' attributes = [('uri', uri,)] self._xml_start_tag('a:graphicData', attributes) # Write the c:chart element. self._write_c_chart('rId' + str(index)) self._xml_end_tag('a:graphicData') def _write_c_chart(self, r_id): # Write the element. schema = 'http://schemas.openxmlformats.org/' xmlns_c = schema + 'drawingml/2006/chart' xmlns_r = schema + 'officeDocument/2006/relationships' attributes = [ ('xmlns:c', xmlns_c), ('xmlns:r', xmlns_r), ('r:id', r_id), ] self._xml_empty_tag('c:chart', attributes) def _write_client_data(self): # Write the element. self._xml_empty_tag('xdr:clientData') def _write_sp(self, index, col_absolute, row_absolute, width, height, shape): # Write the element. if shape and shape.connect: attributes = [('macro', '')] self._xml_start_tag('xdr:cxnSp', attributes) # Write the xdr:nvCxnSpPr element. self._write_nv_cxn_sp_pr(index, shape) # Write the xdr:spPr element. self._write_xdr_sp_pr(index, col_absolute, row_absolute, width, height, shape) self._xml_end_tag('xdr:cxnSp') else: # Add attribute for shapes. attributes = [('macro', ''), ('textlink', '')] self._xml_start_tag('xdr:sp', attributes) # Write the xdr:nvSpPr element. self._write_nv_sp_pr(index, shape) # Write the xdr:spPr element. self._write_xdr_sp_pr(index, col_absolute, row_absolute, width, height, shape) # Write the xdr:style element. self._write_style() # Write the xdr:txBody element. if shape.text is not None: self._write_tx_body(col_absolute, row_absolute, width, height, shape) self._xml_end_tag('xdr:sp') def _write_nv_cxn_sp_pr(self, index, shape): # Write the element. self._xml_start_tag('xdr:nvCxnSpPr') name = shape.name + ' ' + str(index) if name is not None: self._write_c_nv_pr(index, name) self._xml_start_tag('xdr:cNvCxnSpPr') attributes = [('noChangeShapeType', '1')] self._xml_empty_tag('a:cxnSpLocks', attributes) if shape.start: attributes = [('id', shape.start), ('idx', shape.start_index)] self._xml_empty_tag('a:stCxn', attributes) if shape.end: attributes = [('id', shape.end), ('idx', shape.end_index)] self._xml_empty_tag('a:endCxn', attributes) self._xml_end_tag('xdr:cNvCxnSpPr') self._xml_end_tag('xdr:nvCxnSpPr') def _write_nv_sp_pr(self, index, shape): # Write the element. attributes = [] self._xml_start_tag('xdr:nvSpPr') name = shape.name + ' ' + str(index) self._write_c_nv_pr(index + 1, name) if shape.name == 'TextBox': attributes = [('txBox', 1)] self._xml_empty_tag('xdr:cNvSpPr', attributes) # attributes = [('noChangeArrowheads', '1')] # self._xml_empty_tag('a:spLocks', attributes) # self._xml_end_tag('xdr:cNvSpPr') self._xml_end_tag('xdr:nvSpPr') def _write_pic(self, index, col_absolute, row_absolute, width, height, shape, options): # Write the element. self._xml_start_tag('xdr:pic') # Write the xdr:nvPicPr element. self._write_nv_pic_pr(index, options) # Write the xdr:blipFill element. if options.get('url', None): index = index + 1 self._write_blip_fill(index) # Write the xdr:spPr element. self._write_sp_pr(col_absolute, row_absolute, width, height, shape) self._xml_end_tag('xdr:pic') def _write_nv_pic_pr(self, index, options): # Write the element. self._xml_start_tag('xdr:nvPicPr') # Write the xdr:cNvPr element. self._write_c_nv_pr(index + 1, 'Picture ' + str(index), options) # Write the xdr:cNvPicPr element. self._write_c_nv_pic_pr() self._xml_end_tag('xdr:nvPicPr') def _write_c_nv_pic_pr(self): # Write the element. self._xml_start_tag('xdr:cNvPicPr') # Write the a:picLocks element. self._write_a_pic_locks() self._xml_end_tag('xdr:cNvPicPr') def _write_a_pic_locks(self): # Write the element. attributes = [('noChangeAspect', 1)] self._xml_empty_tag('a:picLocks', attributes) def _write_blip_fill(self, index): # Write the element. self._xml_start_tag('xdr:blipFill') # Write the a:blip element. self._write_a_blip(index) # Write the a:stretch element. self._write_a_stretch() self._xml_end_tag('xdr:blipFill') def _write_a_blip(self, index): # Write the element. schema = 'http://schemas.openxmlformats.org/officeDocument/' xmlns_r = schema + '2006/relationships' r_embed = 'rId' + str(index) attributes = [ ('xmlns:r', xmlns_r), ('r:embed', r_embed)] self._xml_empty_tag('a:blip', attributes) def _write_a_stretch(self): # Write the element. self._xml_start_tag('a:stretch') # Write the a:fillRect element. self._write_a_fill_rect() self._xml_end_tag('a:stretch') def _write_a_fill_rect(self): # Write the element. self._xml_empty_tag('a:fillRect') def _write_sp_pr(self, col_absolute, row_absolute, width, height, shape=None): # Write the element, for charts. self._xml_start_tag('xdr:spPr') # Write the a:xfrm element. self._write_a_xfrm(col_absolute, row_absolute, width, height) # Write the a:prstGeom element. self._write_a_prst_geom(shape) self._xml_end_tag('xdr:spPr') def _write_xdr_sp_pr(self, index, col_absolute, row_absolute, width, height, shape): # Write the element for shapes. attributes = [] # attributes = [('bwMode', 'auto')] self._xml_start_tag('xdr:spPr', attributes) # Write the a:xfrm element. self._write_a_xfrm(col_absolute, row_absolute, width, height, shape) # Write the a:prstGeom element. self._write_a_prst_geom(shape) if shape.fill: if not shape.fill['defined']: # Write the a:solidFill element. self._write_a_solid_fill_scheme('lt1') elif 'none' in shape.fill: # Write the a:noFill element. self._xml_empty_tag('a:noFill') elif 'color' in shape.fill: # Write the a:solidFill element. self._write_a_solid_fill(get_rgb_color(shape.fill['color'])) if shape.gradient: # Write the a:gradFill element. self._write_a_grad_fill(shape.gradient) # Write the a:ln element. self._write_a_ln(shape.line) self._xml_end_tag('xdr:spPr') def _write_a_xfrm(self, col_absolute, row_absolute, width, height, shape=None): # Write the element. attributes = [] if shape: if shape.rotation: rotation = shape.rotation rotation *= 60000 attributes.append(('rot', rotation)) if shape.flip_h: attributes.append(('flipH', 1)) if shape.flip_v: attributes.append(('flipV', 1)) self._xml_start_tag('a:xfrm', attributes) # Write the a:off element. self._write_a_off(col_absolute, row_absolute) # Write the a:ext element. self._write_a_ext(width, height) self._xml_end_tag('a:xfrm') def _write_a_off(self, x, y): # Write the element. attributes = [ ('x', x), ('y', y), ] self._xml_empty_tag('a:off', attributes) def _write_a_ext(self, cx, cy): # Write the element. attributes = [ ('cx', cx), ('cy', cy), ] self._xml_empty_tag('a:ext', attributes) def _write_a_prst_geom(self, shape=None): # Write the element. attributes = [('prst', 'rect')] self._xml_start_tag('a:prstGeom', attributes) # Write the a:avLst element. self._write_a_av_lst(shape) self._xml_end_tag('a:prstGeom') def _write_a_av_lst(self, shape=None): # Write the element. adjustments = [] if shape and shape.adjustments: adjustments = shape.adjustments if adjustments: self._xml_start_tag('a:avLst') i = 0 for adj in adjustments: i += 1 # Only connectors have multiple adjustments. if shape.connect: suffix = i else: suffix = '' # Scale Adjustments: 100,000 = 100%. adj_int = str(int(adj * 1000)) attributes = [('name', 'adj' + suffix), ('fmla', 'val' + adj_int)] self._xml_empty_tag('a:gd', attributes) self._xml_end_tag('a:avLst') else: self._xml_empty_tag('a:avLst') def _write_a_solid_fill(self, rgb): # Write the element. if rgb is None: rgb = 'FFFFFF' self._xml_start_tag('a:solidFill') # Write the a:srgbClr element. self._write_a_srgb_clr(rgb) self._xml_end_tag('a:solidFill') def _write_a_solid_fill_scheme(self, color, shade=None): attributes = [('val', color)] self._xml_start_tag('a:solidFill') if shade: self._xml_start_tag('a:schemeClr', attributes) self._write_a_shade(shade) self._xml_end_tag('a:schemeClr') else: self._xml_empty_tag('a:schemeClr', attributes) self._xml_end_tag('a:solidFill') def _write_a_ln(self, line): # Write the element. width = line.get('width', 0.75) # Round width to nearest 0.25, like Excel. width = int((width + 0.125) * 4) / 4.0 # Convert to internal units. width = int(0.5 + (12700 * width)) attributes = [ ('w', width), ('cmpd', 'sng') ] self._xml_start_tag('a:ln', attributes) if 'none' in line: # Write the a:noFill element. self._xml_empty_tag('a:noFill') elif 'color' in line: # Write the a:solidFill element. self._write_a_solid_fill(get_rgb_color(line['color'])) else: # Write the a:solidFill element. self._write_a_solid_fill_scheme('lt1', '50000') # Write the line/dash type. line_type = line.get('dash_type') if line_type: # Write the a:prstDash element. self._write_a_prst_dash(line_type) self._xml_end_tag('a:ln') def _write_tx_body(self, col_absolute, row_absolute, width, height, shape): # Write the element. attributes = [ ('wrap', "square"), ('rtlCol', "0"), ] if not shape.align['defined']: attributes.append(('anchor', 't')) else: if 'vertical' in shape.align: align = shape.align['vertical'] if align == 'top': attributes.append(('anchor', 't')) elif align == 'middle': attributes.append(('anchor', 'ctr')) elif align == 'bottom': attributes.append(('anchor', 'b')) else: attributes.append(('anchor', 't')) if 'horizontal' in shape.align: align = shape.align['horizontal'] if align == 'center': attributes.append(('anchorCtr', '1')) else: attributes.append(('anchorCtr', '0')) self._xml_start_tag('xdr:txBody') self._xml_empty_tag('a:bodyPr', attributes) self._xml_empty_tag('a:lstStyle') lines = shape.text.split('\n') # Set the font attributes. font = shape.font style_attrs = Shape._get_font_style_attributes(font) latin_attrs = Shape._get_font_latin_attributes(font) style_attrs.insert(0, ('lang', font['lang'])) for line in lines: self._xml_start_tag('a:p') if line == '': self._write_font_run(font, style_attrs, latin_attrs, 'a:endParaRPr') self._xml_end_tag('a:p') continue self._xml_start_tag('a:r') self._write_font_run(font, style_attrs, latin_attrs, 'a:rPr') self._xml_data_element('a:t', line) self._xml_end_tag('a:r') self._xml_end_tag('a:p') self._xml_end_tag('xdr:txBody') def _write_font_run(self, font, style_attrs, latin_attrs, run_type): # Write a:rPr or a:endParaRPr. if font.get('color') is not None: has_color = True else: has_color = False if latin_attrs or has_color: self._xml_start_tag(run_type, style_attrs) if has_color: self._write_a_solid_fill(get_rgb_color(font['color'])) if latin_attrs: self._write_a_latin(latin_attrs) self._write_a_cs(latin_attrs) self._xml_end_tag(run_type) else: self._xml_empty_tag(run_type, style_attrs) def _write_style(self): # Write the element. self._xml_start_tag('xdr:style') # Write the a:lnRef element. self._write_a_ln_ref() # Write the a:fillRef element. self._write_a_fill_ref() # Write the a:effectRef element. self._write_a_effect_ref() # Write the a:fontRef element. self._write_a_font_ref() self._xml_end_tag('xdr:style') def _write_a_ln_ref(self): # Write the element. attributes = [('idx', '0')] self._xml_start_tag('a:lnRef', attributes) # Write the a:scrgbClr element. self._write_a_scrgb_clr() self._xml_end_tag('a:lnRef') def _write_a_fill_ref(self): # Write the element. attributes = [('idx', '0')] self._xml_start_tag('a:fillRef', attributes) # Write the a:scrgbClr element. self._write_a_scrgb_clr() self._xml_end_tag('a:fillRef') def _write_a_effect_ref(self): # Write the element. attributes = [('idx', '0')] self._xml_start_tag('a:effectRef', attributes) # Write the a:scrgbClr element. self._write_a_scrgb_clr() self._xml_end_tag('a:effectRef') def _write_a_scrgb_clr(self): # Write the element. attributes = [ ('r', '0'), ('g', '0'), ('b', '0'), ] self._xml_empty_tag('a:scrgbClr', attributes) def _write_a_font_ref(self): # Write the element. attributes = [('idx', 'minor')] self._xml_start_tag('a:fontRef', attributes) # Write the a:schemeClr element. self._write_a_scheme_clr('dk1') self._xml_end_tag('a:fontRef') def _write_a_scheme_clr(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('a:schemeClr', attributes) def _write_a_shade(self, shade): # Write the element. attributes = [('val', shade)] self._xml_empty_tag('a:shade', attributes) def _write_a_prst_dash(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('a:prstDash', attributes) def _write_a_grad_fill(self, gradient): # Write the element. attributes = [('flip', 'none'), ('rotWithShape', '1')] if gradient['type'] == 'linear': attributes = [] self._xml_start_tag('a:gradFill', attributes) # Write the a:gsLst element. self._write_a_gs_lst(gradient) if gradient['type'] == 'linear': # Write the a:lin element. self._write_a_lin(gradient['angle']) else: # Write the a:path element. self._write_a_path(gradient['type']) # Write the a:tileRect element. self._write_a_tile_rect(gradient['type']) self._xml_end_tag('a:gradFill') def _write_a_gs_lst(self, gradient): # Write the element. positions = gradient['positions'] colors = gradient['colors'] self._xml_start_tag('a:gsLst') for i in range(len(colors)): pos = int(positions[i] * 1000) attributes = [('pos', pos)] self._xml_start_tag('a:gs', attributes) # Write the a:srgbClr element. # TODO: Wait for a feature request to support transparency. color = get_rgb_color(colors[i]) self._write_a_srgb_clr(color) self._xml_end_tag('a:gs') self._xml_end_tag('a:gsLst') def _write_a_lin(self, angle): # Write the element. angle = int(60000 * angle) attributes = [ ('ang', angle), ('scaled', '0'), ] self._xml_empty_tag('a:lin', attributes) def _write_a_path(self, gradient_type): # Write the element. attributes = [('path', gradient_type)] self._xml_start_tag('a:path', attributes) # Write the a:fillToRect element. self._write_a_fill_to_rect(gradient_type) self._xml_end_tag('a:path') def _write_a_fill_to_rect(self, gradient_type): # Write the element. if gradient_type == 'shape': attributes = [ ('l', '50000'), ('t', '50000'), ('r', '50000'), ('b', '50000'), ] else: attributes = [ ('l', '100000'), ('t', '100000'), ] self._xml_empty_tag('a:fillToRect', attributes) def _write_a_tile_rect(self, gradient_type): # Write the element. if gradient_type == 'shape': attributes = [] else: attributes = [ ('r', '-100000'), ('b', '-100000'), ] self._xml_empty_tag('a:tileRect', attributes) def _write_a_srgb_clr(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('a:srgbClr', attributes) def _write_a_latin(self, attributes): # Write the element. self._xml_empty_tag('a:latin', attributes) def _write_a_cs(self, attributes): # Write the element. self._xml_empty_tag('a:cs', attributes) XlsxWriter-1.1.2/xlsxwriter/exceptions.py0000644000076500000240000000202713340606703020660 0ustar Johnstaff00000000000000############################################################################### # # Exceptions - A class for XlsxWriter exceptions. # # Copyright 2013-2018, 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 UndefinedImageSize(XlsxFileError): """No size data found in image file.""" class UnsupportedImageFormat(XlsxFileError): """Unsupported image file format.""" XlsxWriter-1.1.2/xlsxwriter/format.py0000644000076500000240000006315513340606703020000 0ustar Johnstaff00000000000000############################################################################### # # Format - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # # Package imports. from . import xmlwriter from warnings import warn class Format(xmlwriter.XMLwriter): """ A class for writing the Excel XLSX Format file. """ ########################################################################### # # Public API. # ########################################################################### def __init__(self, properties=None, xf_indices=None, dxf_indices=None): """ Constructor. """ if properties is None: properties = {} super(Format, self).__init__() self.xf_format_indices = xf_indices self.dxf_format_indices = dxf_indices self.xf_index = None self.dxf_index = None self.num_format = 0 self.num_format_index = 0 self.font_index = 0 self.has_font = 0 self.has_dxf_font = 0 self.bold = 0 self.underline = 0 self.italic = 0 self.font_name = 'Calibri' self.font_size = 11 self.font_color = 0x0 self.font_strikeout = 0 self.font_outline = 0 self.font_shadow = 0 self.font_script = 0 self.font_family = 2 self.font_charset = 0 self.font_scheme = 'minor' self.font_condense = 0 self.font_extend = 0 self.theme = 0 self.hyperlink = False self.xf_id = 0 self.hidden = 0 self.locked = 1 self.text_h_align = 0 self.text_wrap = 0 self.text_v_align = 0 self.text_justlast = 0 self.rotation = 0 self.fg_color = 0 self.bg_color = 0 self.pattern = 0 self.has_fill = 0 self.has_dxf_fill = 0 self.fill_index = 0 self.fill_count = 0 self.border_index = 0 self.has_border = 0 self.has_dxf_border = 0 self.border_count = 0 self.bottom = 0 self.bottom_color = 0 self.diag_border = 0 self.diag_color = 0 self.diag_type = 0 self.left = 0 self.left_color = 0 self.right = 0 self.right_color = 0 self.top = 0 self.top_color = 0 self.indent = 0 self.shrink = 0 self.merge_range = 0 self.reading_order = 0 self.just_distrib = 0 self.color_indexed = 0 self.font_only = 0 # Convert properties in the constructor to method calls. for key, value in properties.items(): getattr(self, 'set_' + key)(value) self._format_key = None ########################################################################### # # Format properties. # ########################################################################### def set_font_name(self, font_name): """ Set the Format font_name property such as 'Time New Roman'. The default Excel font is 'Calibri'. Args: font_name: String with the font name. No default. Returns: Nothing. """ self.font_name = font_name def set_font_size(self, font_size=11): """ Set the Format font_size property. The default Excel font size is 11. Args: font_size: Int with font size. No default. Returns: Nothing. """ self.font_size = font_size def set_font_color(self, font_color): """ Set the Format font_color property. The Excel default is black. Args: font_color: String with the font color. No default. Returns: Nothing. """ self.font_color = self._get_color(font_color) def set_bold(self, bold=True): """ Set the Format bold property. Args: bold: Default is True, turns property on. Returns: Nothing. """ self.bold = bold def set_italic(self, italic=True): """ Set the Format italic property. Args: italic: Default is True, turns property on. Returns: Nothing. """ self.italic = italic def set_underline(self, underline=1): """ Set the Format underline property. Args: underline: Default is 1, single underline. Returns: Nothing. """ self.underline = underline def set_font_strikeout(self, font_strikeout=True): """ Set the Format font_strikeout property. Args: font_strikeout: Default is True, turns property on. Returns: Nothing. """ self.font_strikeout = font_strikeout def set_font_script(self, font_script=1): """ Set the Format font_script property. Args: font_script: Default is 1, superscript. Returns: Nothing. """ self.font_script = font_script def set_font_outline(self, font_outline=True): """ Set the Format font_outline property. Args: font_outline: Default is True, turns property on. Returns: Nothing. """ self.font_outline = font_outline def set_font_shadow(self, font_shadow=True): """ Set the Format font_shadow property. Args: font_shadow: Default is True, turns property on. Returns: Nothing. """ self.font_shadow = font_shadow def set_num_format(self, num_format): """ Set the Format num_format property such as '#,##0'. Args: num_format: String representing the number format. No default. Returns: Nothing. """ self.num_format = num_format def set_locked(self, locked=True): """ Set the Format locked property. Args: locked: Default is True, turns property on. Returns: Nothing. """ self.locked = locked def set_hidden(self, hidden=True): """ Set the Format hidden property. Args: hidden: Default is True, turns property on. Returns: Nothing. """ self.hidden = hidden def set_align(self, alignment): """ Set the Format cell alignment. Args: alignment: String representing alignment. No default. Returns: Nothing. """ alignment = alignment.lower() # Set horizontal alignment properties. if alignment == 'left': self.set_text_h_align(1) if alignment == 'centre': self.set_text_h_align(2) if alignment == 'center': self.set_text_h_align(2) if alignment == 'right': self.set_text_h_align(3) if alignment == 'fill': self.set_text_h_align(4) if alignment == 'justify': self.set_text_h_align(5) if alignment == 'center_across': self.set_text_h_align(6) if alignment == 'centre_across': self.set_text_h_align(6) if alignment == 'distributed': self.set_text_h_align(7) if alignment == 'justify_distributed': self.set_text_h_align(7) if alignment == 'justify_distributed': self.just_distrib = 1 # Set vertical alignment properties. if alignment == 'top': self.set_text_v_align(1) if alignment == 'vcentre': self.set_text_v_align(2) if alignment == 'vcenter': self.set_text_v_align(2) if alignment == 'bottom': self.set_text_v_align(3) if alignment == 'vjustify': self.set_text_v_align(4) if alignment == 'vdistributed': self.set_text_v_align(5) def set_center_across(self, align_type=None): """ Set the Format center_across property. Returns: Nothing. """ self.set_text_h_align(6) def set_text_wrap(self, text_wrap=True): """ Set the Format text_wrap property. Args: text_wrap: Default is True, turns property on. Returns: Nothing. """ self.text_wrap = text_wrap def set_rotation(self, rotation): """ Set the Format rotation property. Args: rotation: Rotation angle. No default. Returns: Nothing. """ rotation = int(rotation) # Map user angle to Excel angle. if rotation == 270: rotation = 255 elif -90 <= rotation <= 90: if rotation < 0: rotation = -rotation + 90 else: warn("Rotation rotation outside range: -90 <= angle <= 90") return self.rotation = rotation def set_indent(self, indent=1): """ Set the Format indent property. Args: indent: Default is 1, first indentation level. Returns: Nothing. """ self.indent = indent def set_shrink(self, shrink=True): """ Set the Format shrink property. Args: shrink: Default is True, turns property on. Returns: Nothing. """ self.shrink = shrink def set_text_justlast(self, text_justlast=True): """ Set the Format text_justlast property. Args: text_justlast: Default is True, turns property on. Returns: Nothing. """ self.text_justlast = text_justlast def set_pattern(self, pattern=1): """ Set the Format pattern property. Args: pattern: Default is 1, solid fill. Returns: Nothing. """ self.pattern = pattern def set_bg_color(self, bg_color): """ Set the Format bg_color property. Args: bg_color: Background color. No default. Returns: Nothing. """ self.bg_color = self._get_color(bg_color) def set_fg_color(self, fg_color): """ Set the Format fg_color property. Args: fg_color: Foreground color. No default. Returns: Nothing. """ self.fg_color = self._get_color(fg_color) # set_border(style) Set cells borders to the same style def set_border(self, style=1): """ Set the Format bottom property. Args: bottom: Default is 1, border type 1. Returns: Nothing. """ self.set_bottom(style) self.set_top(style) self.set_left(style) self.set_right(style) # set_border_color(color) Set cells border to the same color def set_border_color(self, color): """ Set the Format bottom property. Args: color: Color string. No default. Returns: Nothing. """ self.set_bottom_color(color) self.set_top_color(color) self.set_left_color(color) self.set_right_color(color) def set_bottom(self, bottom=1): """ Set the Format bottom property. Args: bottom: Default is 1, border type 1. Returns: Nothing. """ self.bottom = bottom def set_bottom_color(self, bottom_color): """ Set the Format bottom_color property. Args: bottom_color: Color string. No default. Returns: Nothing. """ self.bottom_color = self._get_color(bottom_color) def set_diag_type(self, diag_type=1): """ Set the Format diag_type property. Args: diag_type: Default is 1, border type 1. Returns: Nothing. """ self.diag_type = diag_type def set_left(self, left=1): """ Set the Format left property. Args: left: Default is 1, border type 1. Returns: Nothing. """ self.left = left def set_left_color(self, left_color): """ Set the Format left_color property. Args: left_color: Color string. No default. Returns: Nothing. """ self.left_color = self._get_color(left_color) def set_right(self, right=1): """ Set the Format right property. Args: right: Default is 1, border type 1. Returns: Nothing. """ self.right = right def set_right_color(self, right_color): """ Set the Format right_color property. Args: right_color: Color string. No default. Returns: Nothing. """ self.right_color = self._get_color(right_color) def set_top(self, top=1): """ Set the Format top property. Args: top: Default is 1, border type 1. Returns: Nothing. """ self.top = top def set_top_color(self, top_color): """ Set the Format top_color property. Args: top_color: Color string. No default. Returns: Nothing. """ self.top_color = self._get_color(top_color) def set_diag_color(self, diag_color): """ Set the Format diag_color property. Args: diag_color: Color string. No default. Returns: Nothing. """ self.diag_color = self._get_color(diag_color) def set_diag_border(self, diag_border=1): """ Set the Format diag_border property. Args: diag_border: Default is 1, border type 1. Returns: Nothing. """ self.diag_border = diag_border ########################################################################### # # Internal Format properties. These aren't documented since they are # either only used internally or else are unlikely to be set by the user. # ########################################################################### def set_has_font(self, has_font=True): # Set the has_font property. self.has_font = has_font def set_has_fill(self, has_fill=True): # Set the has_fill property. self.has_fill = has_fill def set_font_index(self, font_index): # Set the font_index property. self.font_index = font_index def set_xf_index(self, xf_index): # Set the xf_index property. self.xf_index = xf_index def set_dxf_index(self, dxf_index): # Set the xf_index property. self.dxf_index = dxf_index def set_num_format_index(self, num_format_index): # Set the num_format_index property. self.num_format_index = num_format_index def set_text_h_align(self, text_h_align): # Set the text_h_align property. self.text_h_align = text_h_align def set_text_v_align(self, text_v_align): # Set the text_v_align property. self.text_v_align = text_v_align def set_reading_order(self, direction=0): # Set the reading_order property. self.reading_order = direction def set_valign(self, align): # Set vertical cell alignment. This is required by the constructor # properties dict to differentiate between the vertical and horizontal # properties. self.set_align(align) def set_font_family(self, font_family): # Set the Format font_family property. self.font_family = font_family def set_font_charset(self, font_charset): # Set the Format font_charset property. self.font_charset = font_charset def set_font_scheme(self, font_scheme): # Set the Format font_scheme property. self.font_scheme = font_scheme def set_font_condense(self, font_condense): # Set the Format font_condense property. self.font_condense = font_condense def set_font_extend(self, font_extend): # Set the Format font_extend property. self.font_extend = font_extend def set_theme(self, theme): # Set the Format theme property. self.theme = theme def set_hyperlink(self, hyperlink=True): # Set the properties for the hyperlink style. This isn't # currently public. To be fixed when styles are supported. self.xf_id = 1 self.set_underline(1) self.set_theme(10) self.hyperlink = hyperlink def set_color_indexed(self, color_index): # Used in the cell comment format. self.color_indexed = color_index def set_font_only(self, font_only=True): # Used in the cell comment format. self.font_only = font_only # Compatibility methods. def set_font(self, font_name): # For compatibility with Excel::Writer::XLSX. self.font_name = font_name def set_size(self, font_size): # For compatibility with Excel::Writer::XLSX. self.font_size = font_size def set_color(self, font_color): # For compatibility with Excel::Writer::XLSX. self.font_color = self._get_color(font_color) ########################################################################### # # Private API. # ########################################################################### def _get_align_properties(self): # Return properties for an Style xf sub-element. changed = 0 align = [] # Check if any alignment options in the format have been changed. if (self.text_h_align or self.text_v_align or self.indent or self.rotation or self.text_wrap or self.shrink or self.reading_order): changed = 1 else: return changed, align # Indent is only allowed for horizontal left, right and distributed. # If it is defined for any other alignment or no alignment has # been set then default to left alignment. if (self.indent and self.text_h_align != 1 and self.text_h_align != 3 and self.text_h_align != 7): self.text_h_align = 1 # Check for properties that are mutually exclusive. if self.text_wrap: self.shrink = 0 if self.text_h_align == 4: self.shrink = 0 if self.text_h_align == 5: self.shrink = 0 if self.text_h_align == 7: self.shrink = 0 if self.text_h_align != 7: self.just_distrib = 0 if self.indent: self.just_distrib = 0 continuous = 'centerContinuous' if self.text_h_align == 1: align.append(('horizontal', 'left')) if self.text_h_align == 2: align.append(('horizontal', 'center')) if self.text_h_align == 3: align.append(('horizontal', 'right')) if self.text_h_align == 4: align.append(('horizontal', 'fill')) if self.text_h_align == 5: align.append(('horizontal', 'justify')) if self.text_h_align == 6: align.append(('horizontal', continuous)) if self.text_h_align == 7: align.append(('horizontal', 'distributed')) if self.just_distrib: align.append(('justifyLastLine', 1)) # Property 'vertical' => 'bottom' is a default. It sets applyAlignment # without an alignment sub-element. if self.text_v_align == 1: align.append(('vertical', 'top')) if self.text_v_align == 2: align.append(('vertical', 'center')) if self.text_v_align == 4: align.append(('vertical', 'justify')) if self.text_v_align == 5: align.append(('vertical', 'distributed')) if self.indent: align.append(('indent', self.indent)) if self.rotation: align.append(('textRotation', self.rotation)) if self.text_wrap: align.append(('wrapText', 1)) if self.shrink: align.append(('shrinkToFit', 1)) if self.reading_order == 1: align.append(('readingOrder', 1)) if self.reading_order == 2: align.append(('readingOrder', 2)) return changed, align def _get_protection_properties(self): # Return properties for an Excel XML element. attribs = [] if not self.locked: attribs.append(('locked', 0)) if self.hidden: attribs.append(('hidden', 1)) return attribs def _get_format_key(self): # Returns a unique hash key for a font. Used by Workbook. if self._format_key is None: self._format_key = ':'.join(self._to_string(x) for x in ( self._get_font_key(), self._get_border_key(), self._get_fill_key(), self._get_alignment_key(), self.num_format, self.locked, self.hidden)) return self._format_key def _get_font_key(self): # Returns a unique hash key for a font. Used by Workbook. key = ':'.join(self._to_string(x) for x in ( self.bold, self.font_color, self.font_charset, self.font_family, self.font_outline, self.font_script, self.font_shadow, self.font_strikeout, self.font_name, self.italic, self.font_size, self.underline, self.theme)) return key def _get_border_key(self): # Returns a unique hash key for a border style. Used by Workbook. key = ':'.join(self._to_string(x) for x in ( self.bottom, self.bottom_color, self.diag_border, self.diag_color, self.diag_type, self.left, self.left_color, self.right, self.right_color, self.top, self.top_color)) return key def _get_fill_key(self): # Returns a unique hash key for a fill style. Used by Workbook. key = ':'.join(self._to_string(x) for x in ( self.pattern, self.bg_color, self.fg_color)) return key def _get_alignment_key(self): # Returns a unique hash key for alignment formats. key = ':'.join(self._to_string(x) for x in ( self.text_h_align, self.text_v_align, self.indent, self.rotation, self.text_wrap, self.shrink, self.reading_order)) return key def _get_xf_index(self): # Returns the XF index number used by Excel to identify a format. if self.xf_index is not None: # Format already has an index number so return it. return self.xf_index else: # Format doesn't have an index number so assign one. key = self._get_format_key() if key in self.xf_format_indices: # Format matches existing format with an index. return self.xf_format_indices[key] else: # New format requiring an index. Note. +1 since Excel # has an implicit "General" format at index 0. index = 1 + len(self.xf_format_indices) self.xf_format_indices[key] = index self.xf_index = index return index def _get_dxf_index(self): # Returns the DXF index number used by Excel to identify a format. if self.dxf_index is not None: # Format already has an index number so return it. return self.dxf_index else: # Format doesn't have an index number so assign one. key = self._get_format_key() if key in self.dxf_format_indices: # Format matches existing format with an index. return self.dxf_format_indices[key] else: # New format requiring an index. index = len(self.dxf_format_indices) self.dxf_format_indices[key] = index self.dxf_index = index return index def _get_color(self, color): # Used in conjunction with the set_xxx_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] return color def _to_string(self, value): # Convert number to a string but allow for utf-8 strings in Python 2. try: return str(value) except UnicodeEncodeError: return value.encode('utf-8') ########################################################################### # # XML methods. # ########################################################################### XlsxWriter-1.1.2/xlsxwriter/packager.py0000644000076500000240000005505413351301367020264 0ustar Johnstaff00000000000000############################################################################### # # Packager - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # # Standard packages. import os import stat import tempfile from shutil import copy from .compatibility import StringIO from .compatibility import BytesIO # Package imports. from .app import App from .contenttypes import ContentTypes from .core import Core from .custom import Custom 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_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_app_file() self._write_core_file() self._write_custom_file() self._write_content_types_file() self._write_styles_file() self._write_theme_file() self._write_root_rels_file() self._write_workbook_rels_file() self._write_worksheet_rels_files() self._write_chartsheet_rels_files() self._write_drawing_rels_files() self._add_image_files() self._add_vba_project() 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._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_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) 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() 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_format_count = self.workbook.num_format_count border_count = self.workbook.border_count fill_count = self.workbook.fill_count custom_colors = self.workbook.custom_colors dxf_formats = self.workbook.dxf_formats styles = Styles() styles._set_style_properties([ xf_formats, palette, font_count, num_format_count, border_count, fill_count, custom_colors, dxf_formats]) 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() self.table_count += 1 index += 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') 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_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_worksheet_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_worksheet_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)) XlsxWriter-1.1.2/xlsxwriter/relationships.py0000644000076500000240000000653613263123126021371 0ustar Johnstaff00000000000000############################################################################### # # Relationships - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013-2018, 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)) def _add_worksheet_relationship(self, rel_type, target, target_mode=None): # Add worksheet relationship to sheet.rels xml files. rel_type = document_schema + rel_type self.relationships.append((rel_type, target, target_mode)) ########################################################################### # # XML methods. # ########################################################################### def _write_relationships(self): # Write the 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 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) XlsxWriter-1.1.2/xlsxwriter/shape.py0000644000076500000240000003066313263123126017603 0ustar Johnstaff00000000000000############################################################################### # # Shape - A class for to represent Excel XLSX shape objects. # # Copyright 2013-2018, 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.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.textbox = False self.align = None self.fill = None self.font = None self.format = None self.line = 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')) 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 not pattern['pattern'] 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), 'rotation': options.get('rotation'), 'lang': options.get('lang', 'en-US'), } # Convert font size units. if font['size']: font['size'] = int(font['size'] * 100) # Convert rotation into 60,000ths of a degree. if font['rotation']: font['rotation'] = 60000 * int(font['rotation']) 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 XlsxWriter-1.1.2/xlsxwriter/sharedstrings.py0000644000076500000240000001147613340606703021367 0ustar Johnstaff00000000000000############################################################################### # # SharedStrings - A class for writing the Excel XLSX sharedStrings file. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # # Standard packages. import re import sys # Package imports. from . import xmlwriter 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 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._get_strings()): self._write_si(string) def _write_si(self, string): # Write the 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.sub('(_x[0-9a-fA-F]{4}_)', r'_x005F\1', string) # Convert control character to the _xHHHH_ escape. string = re.sub(r'([\x00-\x08\x0B-\x1F])', lambda match: "_x%04X_" % ord(match.group(1)), string) # Escape Unicode non-characters FFFE and FFFF. if sys.version_info[0] == 2: non_char1 = unichr(0xFFFE) non_char2 = unichr(0xFFFF) else: non_char1 = "\uFFFE" non_char2 = "\uFFFF" string = re.sub(non_char1, '_xFFFE_', string) string = re.sub(non_char2, '_xFFFF_', string) # Add attribute to preserve leading or trailing whitespace. if re.search(r'^\s', string) or re.search(r'\s$', string): attributes.append(('xml:space', 'preserve')) # Write any rich strings without further tags. if re.search('^', string) and re.search('$', string): 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 = {} def _get_strings(self): """" Return the sorted string list. """ return self.string_array XlsxWriter-1.1.2/xlsxwriter/styles.py0000644000076500000240000005351313263123126020025 0ustar Johnstaff00000000000000############################################################################### # # Styles - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013-2018, 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_format_count = 0 self.border_count = 0 self.fill_count = 0 self.custom_colors = [] self.dxf_formats = [] self.has_hyperlink = False self.hyperlink_font_id = 0 ########################################################################### # # 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_format_count = properties[3] self.border_count = properties[4] self.fill_count = properties[5] self.custom_colors = properties[6] self.dxf_formats = properties[7] def _get_palette_color(self, color): # Convert the RGB color. if color[0] == '#': color = color[1:] return "FF" + color.upper() ########################################################################### # # XML methods. # ########################################################################### def _write_style_sheet(self): # Write the 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 element. if not self.num_format_count: return attributes = [('count', self.num_format_count)] self._xml_start_tag('numFmts', attributes) # Write the numFmts elements. for xf_format in self.xf_formats: # Ignore built-in number formats, i.e., < 164. if xf_format.num_format_index >= 164: self._write_num_fmt(xf_format.num_format_index, xf_format.num_format) self._xml_end_tag('numFmts') def _write_num_fmt(self, num_fmt_id, format_code): # Write the 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 element. 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) self._xml_end_tag('fonts') def _write_font(self, xf_format, is_dxf_format=False): # Write the 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) 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_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 font sub-element. attributes = [('val', val)] self._xml_empty_tag('vertAlign', attributes) def _write_color(self, name, value): # Write the element. attributes = [(name, value)] self._xml_empty_tag('color', attributes) def _write_fills(self): # Write the 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 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 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', ) 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) self._xml_empty_tag('fgColor', [('rgb', fg_color)]) if bg_color: bg_color = self._get_palette_color(bg_color) self._xml_empty_tag('bgColor', [('rgb', bg_color)]) else: if not is_dxf_format: self._xml_empty_tag('bgColor', [('indexed', 64)]) self._xml_end_tag('patternFill') self._xml_end_tag('fill') def _write_borders(self): # Write the 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 element. attributes = [] # Diagonal borders add attributes to the 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 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 sub elements such as , , 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: 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 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 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 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 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.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 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 element. attributes = [ ('name', name), ('xfId', xf_id), ('builtinId', builtin_id), ] self._xml_empty_tag('cellStyle', attributes) def _write_dxfs(self): # Write the 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 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 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 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 element. attributes = [('val', 0)] self._xml_empty_tag('condense', attributes) def _write_extend(self): # Write the element. attributes = [('val', 0)] self._xml_empty_tag('extend', attributes) XlsxWriter-1.1.2/xlsxwriter/table.py0000644000076500000240000001214513263123126017565 0ustar Johnstaff00000000000000############################################################################### # # Table - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013-2018, 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 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 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 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 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 element. props = self.properties 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'] attributes = [ ('name', name), ('showFirstColumn', show_first_column), ('showLastColumn', show_last_column), ('showRowStripes', show_row_stripes), ('showColumnStripes', show_column_stripes), ] self._xml_empty_tag('tableStyleInfo', attributes) def _write_calculated_column_formula(self, formula): # Write the element. self._xml_data_element('calculatedColumnFormula', formula) XlsxWriter-1.1.2/xlsxwriter/theme.py0000644000076500000240000002156613263123127017610 0ustar Johnstaff00000000000000############################################################################### # # Theme - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # # Standard packages. import codecs import sys # Standard packages in Python 2/3 compatibility mode. from .compatibility import StringIO class Theme(object): """ A class for writing the Excel XLSX Theme file. """ ########################################################################### # # Public API. # ########################################################################### def __init__(self): """ Constructor. """ super(Theme, self).__init__() self.fh = None self.internal_fh = False ########################################################################### # # Private API. # ########################################################################### def _assemble_xml_file(self): # Assemble and write the XML file. self._write_theme_file() if self.internal_fh: self.fh.close() 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 = codecs.open(filename, 'w', 'utf-8') ########################################################################### # # XML methods. # ########################################################################### def _write_theme_file(self): # Write a default theme.xml file. # The theme is encoded to allow Python 2.5/Jython support. default_theme = """\n""" if sys.version_info < (3, 0, 0): default_theme = default_theme.decode('unicode-escape') self.fh.write(default_theme) XlsxWriter-1.1.2/xlsxwriter/utility.py0000644000076500000240000006244513340606703020214 0ustar Johnstaff00000000000000############################################################################### # # Worksheet - A class for writing Excel Worksheets. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # import re import datetime from warnings import warn COL_NAMES = {} 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 = 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 = 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 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 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. """ # TODO. Possibly extend this to quote sheetnames that look like ranges. if not sheetname.isalnum() and not sheetname.startswith("'"): # Double quote any single quotes. sheetname = sheetname.replace("'", "''") # Singe quote the sheet name. sheetname = "'%s'" % sheetname return sheetname 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 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): 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 excel_time > 59: excel_time += 1 return excel_time XlsxWriter-1.1.2/xlsxwriter/vml.py0000644000076500000240000004564513350303502017302 0ustar Johnstaff00000000000000############################################################################### # # Vml - A class for writing the Excel XLSX Vml file. # # Copyright 2013-2018, 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 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 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 element. attributes = [ ('v:ext', 'edit'), ('data', data_id), ] self._xml_empty_tag('o:idmap', attributes) def _write_comment_shapetype(self): # Write the 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 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 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 element. joinstyle = 'miter' attributes = [('joinstyle', joinstyle)] self._xml_empty_tag('v:stroke', attributes) def _write_comment_path(self, gradientshapeok, connecttype): # Write the 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 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 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 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 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 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 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 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), ('style', style), ('o:button', 't'), ('fillcolor', 'buttonFace [67]'), ('strokecolor', 'windowText [64]'), ('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 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] # 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(z_index, name) # Write the o:lock element. self._write_rotation_lock() self._xml_end_tag('v:shape') def _write_comment_fill(self): # Write the element. color_2 = '#ffffe1' attributes = [('color2', color_2)] self._xml_empty_tag('v:fill', attributes) def _write_button_fill(self): # Write the 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 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 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 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
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 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 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 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 element. self._xml_empty_tag('x:MoveWithCells') def _write_size_with_cells(self): # Write the element. self._xml_empty_tag('x:SizeWithCells') def _write_visible(self): # Write the element. self._xml_empty_tag('x:Visible') def _write_anchor(self, vertices): # Write the 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 element. data = 'False' self._xml_data_element('x:AutoFill', data) def _write_row(self, data): # Write the element. self._xml_data_element('x:Row', data) def _write_column(self, data): # Write the element. self._xml_data_element('x:Column', data) def _write_print_object(self): # Write the element. self._xml_data_element('x:PrintObject', 'False') def _write_text_halign(self): # Write the element. self._xml_data_element('x:TextHAlign', 'Center') def _write_text_valign(self): # Write the element. self._xml_data_element('x:TextVAlign', 'Center') def _write_fmla_macro(self, data): # Write the element. self._xml_data_element('x:FmlaMacro', data) def _write_imagedata(self, image_index, o_title): # Write the element. attributes = [ ('o:relid', 'rId' + str(image_index)), ('o:title', o_title), ] self._xml_empty_tag('v:imagedata', attributes) def _write_formulas(self): # Write the 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 element. attributes = [('eqn', eqn)] self._xml_empty_tag('v:f', attributes) XlsxWriter-1.1.2/xlsxwriter/workbook.py0000644000076500000240000016325313362627635020360 0ustar Johnstaff00000000000000############################################################################### # # Workbook - A class for writing the Excel XLSX Workbook file. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # # Standard packages. import sys import re import os import operator import time import warnings from warnings import warn from datetime import datetime from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED from struct import unpack from .compatibility import int_types, num_types, str_types, force_unicode # Package imports. from . import xmlwriter from .worksheet import Worksheet from .chartsheet import Chartsheet from .sharedstrings import SharedStringTable from .format import Format from .packager import Packager from .utility import xl_cell_to_rowcol from .chart_area import ChartArea from .chart_bar import ChartBar from .chart_column import ChartColumn from .chart_doughnut import ChartDoughnut from .chart_line import ChartLine from .chart_pie import ChartPie from .chart_radar import ChartRadar from .chart_scatter import ChartScatter from .chart_stock import ChartStock from .exceptions import InvalidWorksheetName from .exceptions import DuplicateWorksheetName from .exceptions import UndefinedImageSize from .exceptions import UnsupportedImageFormat class Workbook(xmlwriter.XMLwriter): """ A class for writing the Excel XLSX Workbook file. """ ########################################################################### # # Public API. # ########################################################################### chartsheet_class = Chartsheet worksheet_class = Worksheet def __init__(self, filename=None, options=None): """ Constructor. """ if options is None: options = {} super(Workbook, self).__init__() self.filename = filename self.tmpdir = options.get('tmpdir', None) self.date_1904 = options.get('date_1904', False) self.strings_to_numbers = options.get('strings_to_numbers', False) self.strings_to_formulas = options.get('strings_to_formulas', True) self.strings_to_urls = options.get('strings_to_urls', True) self.nan_inf_to_errors = options.get('nan_inf_to_errors', False) self.default_date_format = options.get('default_date_format', None) self.constant_memory = options.get('constant_memory', False) self.in_memory = options.get('in_memory', False) self.excel2003_style = options.get('excel2003_style', False) self.remove_timezone = options.get('remove_timezone', False) self.default_format_properties = \ options.get('default_format_properties', {}) self.worksheet_meta = WorksheetMeta() self.selected = 0 self.fileclosed = 0 self.filehandle = None self.internal_fh = 0 self.sheet_name = 'Sheet' self.chart_name = 'Chart' self.sheetname_count = 0 self.chartname_count = 0 self.worksheets_objs = [] self.charts = [] self.drawings = [] self.sheetnames = {} self.formats = [] self.xf_formats = [] self.xf_format_indices = {} self.dxf_formats = [] self.dxf_format_indices = {} self.palette = [] self.font_count = 0 self.num_format_count = 0 self.defined_names = [] self.named_ranges = [] self.custom_colors = [] self.doc_properties = {} self.custom_properties = [] self.createtime = datetime.utcnow() self.num_vml_files = 0 self.num_comment_files = 0 self.x_window = 240 self.y_window = 15 self.window_width = 16095 self.window_height = 9660 self.tab_ratio = 600 self.str_table = SharedStringTable() self.vba_project = None self.vba_is_stream = False self.vba_codename = None self.image_types = {} self.images = [] self.border_count = 0 self.fill_count = 0 self.drawing_count = 0 self.calc_mode = "auto" self.calc_on_load = True self.allow_zip64 = False self.calc_id = 124519 # We can't do 'constant_memory' mode while doing 'in_memory' mode. if self.in_memory: self.constant_memory = False # Add the default cell format. if self.excel2003_style: self.add_format({'xf_index': 0, 'font_family': 0}) else: self.add_format({'xf_index': 0}) # Add a default URL format. self.default_url_format = self.add_format({'hyperlink': True}) # Add the default date format. if self.default_date_format is not None: self.default_date_format = \ self.add_format({'num_format': self.default_date_format}) def __enter__(self): """Return self object to use with "with" statement.""" return self def __exit__(self, type, value, traceback): """Close workbook when exiting "with" statement.""" self.close() def add_worksheet(self, name=None, worksheet_class=None): """ Add a new worksheet to the Excel workbook. Args: name: The worksheet name. Defaults to 'Sheet1', etc. Returns: Reference to a worksheet object. """ if worksheet_class is None: worksheet_class = self.worksheet_class return self._add_sheet(name, worksheet_class=worksheet_class) def add_chartsheet(self, name=None, chartsheet_class=None): """ Add a new chartsheet to the Excel workbook. Args: name: The chartsheet name. Defaults to 'Sheet1', etc. Returns: Reference to a chartsheet object. """ if chartsheet_class is None: chartsheet_class = self.chartsheet_class return self._add_sheet(name, worksheet_class=chartsheet_class) def add_format(self, properties=None): """ Add a new Format to the Excel Workbook. Args: properties: The format properties. Returns: Reference to a Format object. """ format_properties = self.default_format_properties.copy() if self.excel2003_style: format_properties = {'font_name': 'Arial', 'font_size': 10, 'theme': 1 * -1} if properties: format_properties.update(properties) xf_format = Format(format_properties, self.xf_format_indices, self.dxf_format_indices) # Store the format reference. self.formats.append(xf_format) return xf_format def add_chart(self, options): """ Create a chart object. Args: options: The chart type and subtype options. Returns: Reference to a Chart object. """ # Type must be specified so we can create the required chart instance. chart_type = options.get('type') if chart_type is None: warn("Chart type must be defined in add_chart()") return if chart_type == 'area': chart = ChartArea(options) elif chart_type == 'bar': chart = ChartBar(options) elif chart_type == 'column': chart = ChartColumn(options) elif chart_type == 'doughnut': chart = ChartDoughnut(options) elif chart_type == 'line': chart = ChartLine(options) elif chart_type == 'pie': chart = ChartPie(options) elif chart_type == 'radar': chart = ChartRadar(options) elif chart_type == 'scatter': chart = ChartScatter(options) elif chart_type == 'stock': chart = ChartStock(options) else: warn("Unknown chart type '%s' in add_chart()" % chart_type) return # Set the embedded chart name if present. if 'name' in options: chart.chart_name = options['name'] chart.embedded = True chart.date_1904 = self.date_1904 chart.remove_timezone = self.remove_timezone self.charts.append(chart) return chart def add_vba_project(self, vba_project, is_stream=False): """ Add a vbaProject binary to the Excel workbook. Args: vba_project: The vbaProject binary file name. is_stream: vba_project is an in memory byte stream. Returns: Nothing. """ if not is_stream and not os.path.exists(vba_project): warn("VBA project binary file '%s' not found." % force_unicode(vba_project)) return -1 self.vba_project = vba_project self.vba_is_stream = is_stream def close(self): """ Call finalization code and close file. Args: None. Returns: Nothing. """ if not self.fileclosed: self.fileclosed = 1 self._store_workbook() def set_size(self, width, height): """ Set the size of a workbook window. Args: width: Width of the window in pixels. height: Height of the window in pixels. Returns: Nothing. """ # Convert the width/height to twips at 96 dpi. if width: self.window_width = int(width * 1440 / 96) else: self.window_width = 16095 if height: self.window_height = int(height * 1440 / 96) else: self.window_height = 9660 def set_tab_ratio(self, tab_ratio=None): """ Set the ratio between worksheet tabs and the horizontal slider. Args: tab_ratio: The tab ratio, 0 <= tab_ratio <= 100 Returns: Nothing. """ if tab_ratio is None: return if tab_ratio < 0 or tab_ratio > 100: warn("Tab ratio '%d' outside: 0 <= tab_ratio <= 100" % tab_ratio) tab_ratio = 100 else: self.tab_ratio = int(tab_ratio * 10) def set_properties(self, properties): """ Set the document properties such as Title, Author etc. Args: properties: Dictionary of document properties. Returns: Nothing. """ self.doc_properties = properties def set_custom_property(self, name, value, property_type=None): """ Set a custom document property. Args: name: The name of the custom property. value: The value of the custom property. property_type: The type of the custom property. Optional. Returns: Nothing. """ if name is None or value is None: warn("The name and value parameters must be non-None in " "set_custom_property()") return -1 if property_type is None: # Determine the property type from the Python type. if isinstance(value, bool): property_type = 'bool' elif isinstance(value, datetime): property_type = 'date' elif isinstance(value, int_types): property_type = 'number_int' elif isinstance(value, num_types): property_type = 'number' else: property_type = 'text' if property_type == 'date': value = value.strftime("%Y-%m-%dT%H:%M:%SZ") if property_type == 'text' and len(value) > 255: warn("Length of 'value' parameter exceeds Excel's limit of 255 " "characters in set_custom_property(): '%s'" % force_unicode(value)) if len(name) > 255: warn("Length of 'name' parameter exceeds Excel's limit of 255 " "characters in set_custom_property(): '%s'" % force_unicode(name)) self.custom_properties.append((name, value, property_type)) def set_calc_mode(self, mode, calc_id=None): """ Set the Excel calculation mode for the workbook. Args: mode: String containing one of: * manual * auto_except_tables * auto Returns: Nothing. """ self.calc_mode = mode if mode == 'manual': self.calc_on_load = False elif mode == 'auto_except_tables': self.calc_mode = 'autoNoTable' # Leave undocumented for now. Rarely required. if calc_id: self.calc_id = calc_id def define_name(self, name, formula): # Create a defined name in Excel. We handle global/workbook level # names and local/worksheet names. """ Create a defined name in the workbook. Args: name: The defined name. formula: The cell or range that the defined name refers to. Returns: Nothing. """ sheet_index = None sheetname = '' # Remove the = sign from the formula if it exists. if formula.startswith('='): formula = formula.lstrip('=') # Local defined names are formatted like "Sheet1!name". sheet_parts = re.compile(r'^(.*)!(.*)$') match = sheet_parts.match(name) if match: sheetname = match.group(1) name = match.group(2) sheet_index = self._get_sheet_index(sheetname) # Warn if the sheet index wasn't found. if sheet_index is None: warn("Unknown sheet name '%s' in defined_name()" % force_unicode(sheetname)) return -1 else: # Use -1 to indicate global names. sheet_index = -1 # Warn if the defined name contains invalid chars as defined by Excel. if (not re.match(r'^[\w\\][\w\\.]*$', name, re.UNICODE) or re.match(r'^\d', name)): warn("Invalid Excel characters in defined_name(): '%s'" % force_unicode(name)) return -1 # Warn if the defined name looks like a cell name. if re.match(r'^[a-zA-Z][a-zA-Z]?[a-dA-D]?[0-9]+$', name): warn("Name looks like a cell name in defined_name(): '%s'" % force_unicode(name)) return -1 # Warn if the name looks like a R1C1 cell reference. if (re.match(r'^[rcRC]$', name) or re.match(r'^[rcRC]\d+[rcRC]\d+$', name)): warn("Invalid name '%s' like a RC cell ref in defined_name()" % force_unicode(name)) return -1 self.defined_names.append([name, sheet_index, formula, False]) def worksheets(self): """ Return a list of the worksheet objects in the workbook. Args: None. Returns: A list of worksheet objects. """ return self.worksheets_objs def get_worksheet_by_name(self, name): """ Return a worksheet object in the workbook using the sheetname. Args: name: The name of the worksheet. Returns: A worksheet object or None. """ return self.sheetnames.get(name) def get_default_url_format(self): """ Get the default url format used when a user defined format isn't specified with write_url(). The format is the hyperlink style defined by Excel for the default theme. Args: None. Returns: A format object. """ return self.default_url_format def use_zip64(self): """ Allow ZIP64 extensions when writing xlsx file zip container. Args: None. Returns: Nothing. """ self.allow_zip64 = True def set_vba_name(self, name=None): """ Set the VBA name for the workbook. By default the workbook is referred to as ThisWorkbook in VBA. Args: name: The VBA name for the workbook. Returns: Nothing. """ if name is not None: self.vba_codename = name else: self.vba_codename = 'ThisWorkbook' ########################################################################### # # Private API. # ########################################################################### def _assemble_xml_file(self): # Assemble and write the XML file. # Prepare format object for passing to Style.pm. self._prepare_format_properties() # Write the XML declaration. self._xml_declaration() # Write the workbook element. self._write_workbook() # Write the fileVersion element. self._write_file_version() # Write the workbookPr element. self._write_workbook_pr() # Write the bookViews element. self._write_book_views() # Write the sheets element. self._write_sheets() # Write the workbook defined names. self._write_defined_names() # Write the calcPr element. self._write_calc_pr() # Close the workbook tag. self._xml_end_tag('workbook') # Close the file. self._xml_close() def _store_workbook(self): # Assemble worksheets into a workbook. packager = Packager() # Add a default worksheet if non have been added. if not self.worksheets(): self.add_worksheet() # Ensure that at least one worksheet has been selected. if self.worksheet_meta.activesheet == 0: self.worksheets_objs[0].selected = 1 self.worksheets_objs[0].hidden = 0 # Set the active sheet. for sheet in self.worksheets(): if sheet.index == self.worksheet_meta.activesheet: sheet.active = 1 # Convert the SST strings data structure. self._prepare_sst_string_data() # Prepare the worksheet VML elements such as comments and buttons. self._prepare_vml() # Set the defined names for the worksheets such as Print Titles. self._prepare_defined_names() # Prepare the drawings, charts and images. self._prepare_drawings() # Add cached data to charts. self._add_chart_data() # Prepare the worksheet tables. self._prepare_tables() # Package the workbook. packager._add_workbook(self) packager._set_tmpdir(self.tmpdir) packager._set_in_memory(self.in_memory) xml_files = packager._create_package() # Free up the Packager object. packager = None xlsx_file = ZipFile(self.filename, "w", compression=ZIP_DEFLATED, allowZip64=self.allow_zip64) # Add XML sub-files to the Zip file with their Excel filename. for os_filename, xml_filename, is_binary in xml_files: if self.in_memory: zipinfo = ZipInfo(xml_filename, (1980, 1, 1, 0, 0, 0)) # Copy compression type from parent ZipFile. zipinfo.compress_type = xlsx_file.compression if is_binary: xlsx_file.writestr(zipinfo, os_filename.getvalue()) else: xlsx_file.writestr(zipinfo, os_filename.getvalue().encode('utf-8')) else: # The files are tempfiles. timestamp = time.mktime((1980, 1, 1, 0, 0, 0, 0, 0, 0)) os.utime(os_filename, (timestamp, timestamp)) xlsx_file.write(os_filename, xml_filename) os.remove(os_filename) xlsx_file.close() def _add_sheet(self, name, worksheet_class=None): # Utility for shared code in add_worksheet() and add_chartsheet(). if worksheet_class is None: raise ValueError( "_add_sheet() must define 'worksheet_class'") if worksheet_class: worksheet = worksheet_class() else: worksheet = self.worksheet_class() sheet_index = len(self.worksheets_objs) name = self._check_sheetname(name, isinstance(worksheet, Chartsheet)) # Initialization data to pass to the worksheet. init_data = { 'name': name, 'index': sheet_index, 'str_table': self.str_table, 'worksheet_meta': self.worksheet_meta, 'constant_memory': self.constant_memory, 'tmpdir': self.tmpdir, 'date_1904': self.date_1904, 'strings_to_numbers': self.strings_to_numbers, 'strings_to_formulas': self.strings_to_formulas, 'strings_to_urls': self.strings_to_urls, 'nan_inf_to_errors': self.nan_inf_to_errors, 'default_date_format': self.default_date_format, 'default_url_format': self.default_url_format, 'excel2003_style': self.excel2003_style, 'remove_timezone': self.remove_timezone, } worksheet._initialize(init_data) self.worksheets_objs.append(worksheet) self.sheetnames[name] = worksheet return worksheet def _check_sheetname(self, sheetname, is_chartsheet=False): # Check for valid worksheet names. We check the length, if it contains # any invalid chars and if the sheetname is unique in the workbook. invalid_char = re.compile(r'[\[\]:*?/\\]') # Increment the Sheet/Chart number used for default sheet names below. if is_chartsheet: self.chartname_count += 1 else: self.sheetname_count += 1 # Supply default Sheet/Chart sheetname if none has been defined. if sheetname is None or sheetname == '': if is_chartsheet: sheetname = self.chart_name + str(self.chartname_count) else: sheetname = self.sheet_name + str(self.sheetname_count) # Check that sheet sheetname is <= 31. Excel limit. if len(sheetname) > 31: raise InvalidWorksheetName( "Excel worksheet name '%s' must be <= 31 chars." % sheetname) # Check that sheetname doesn't contain any invalid characters if invalid_char.search(sheetname): raise InvalidWorksheetName( "Invalid Excel character '[]:*?/\\' in sheetname '%s'." % sheetname) # Check that the worksheet name doesn't already exist since this is a # fatal Excel error. The check must be case insensitive like Excel. for worksheet in self.worksheets(): if sheetname.lower() == worksheet.name.lower(): raise DuplicateWorksheetName( "Sheetname '%s', with case ignored, is already in use." % sheetname) return sheetname def _prepare_format_properties(self): # Prepare all Format properties prior to passing them to styles.py. # Separate format objects into XF and DXF formats. self._prepare_formats() # Set the font index for the format objects. self._prepare_fonts() # Set the number format index for the format objects. self._prepare_num_formats() # Set the border index for the format objects. self._prepare_borders() # Set the fill index for the format objects. self._prepare_fills() def _prepare_formats(self): # Iterate through the XF Format objects and separate them into # XF and DXF formats. The XF and DF formats then need to be sorted # back into index order rather than creation order. xf_formats = [] dxf_formats = [] # Sort into XF and DXF formats. for xf_format in self.formats: if xf_format.xf_index is not None: xf_formats.append(xf_format) if xf_format.dxf_index is not None: dxf_formats.append(xf_format) # Pre-extend the format lists. self.xf_formats = [None] * len(xf_formats) self.dxf_formats = [None] * len(dxf_formats) # Rearrange formats into index order. for xf_format in xf_formats: index = xf_format.xf_index self.xf_formats[index] = xf_format for dxf_format in dxf_formats: index = dxf_format.dxf_index self.dxf_formats[index] = dxf_format def _set_default_xf_indices(self): # Set the default index for each format. Only used for testing. formats = list(self.formats) # Delete the default url format. del formats[1] # Skip the default date format if set. if self.default_date_format is not None: del formats[1] # Set the remaining formats. for xf_format in formats: xf_format._get_xf_index() def _prepare_fonts(self): # Iterate through the XF Format objects and give them an index to # non-default font elements. fonts = {} index = 0 for xf_format in self.xf_formats: key = xf_format._get_font_key() if key in fonts: # Font has already been used. xf_format.font_index = fonts[key] xf_format.has_font = 0 else: # This is a new font. fonts[key] = index xf_format.font_index = index xf_format.has_font = 1 index += 1 self.font_count = index # For DXF formats we only need to check if the properties have changed. for xf_format in self.dxf_formats: # The only font properties that can change for a DXF format are: # color, bold, italic, underline and strikethrough. if (xf_format.font_color or xf_format.bold or xf_format.italic or xf_format.underline or xf_format.font_strikeout): xf_format.has_dxf_font = 1 def _prepare_num_formats(self): # User defined records in Excel start from index 0xA4. num_formats = {} index = 164 num_format_count = 0 for xf_format in (self.xf_formats + self.dxf_formats): num_format = xf_format.num_format # Check if num_format is an index to a built-in number format. if not isinstance(num_format, str_types): xf_format.num_format_index = int(num_format) continue if num_format in num_formats: # Number xf_format has already been used. xf_format.num_format_index = num_formats[num_format] else: # Add a new number xf_format. num_formats[num_format] = index xf_format.num_format_index = index index += 1 # Only increase font count for XF formats (not DXF formats). if xf_format.xf_index: num_format_count += 1 self.num_format_count = num_format_count def _prepare_borders(self): # Iterate through the XF Format objects and give them an index to # non-default border elements. borders = {} index = 0 for xf_format in self.xf_formats: key = xf_format._get_border_key() if key in borders: # Border has already been used. xf_format.border_index = borders[key] xf_format.has_border = 0 else: # This is a new border. borders[key] = index xf_format.border_index = index xf_format.has_border = 1 index += 1 self.border_count = index # For DXF formats we only need to check if the properties have changed. has_border = re.compile(r'[^0:]') for xf_format in self.dxf_formats: key = xf_format._get_border_key() if has_border.search(key): xf_format.has_dxf_border = 1 def _prepare_fills(self): # Iterate through the XF Format objects and give them an index to # non-default fill elements. # The user defined fill properties start from 2 since there are 2 # default fills: patternType="none" and patternType="gray125". fills = {} index = 2 # Start from 2. See above. # Add the default fills. fills['0:0:0'] = 0 fills['17:0:0'] = 1 # Store the DXF colors separately since them may be reversed below. for xf_format in self.dxf_formats: if xf_format.pattern or xf_format.bg_color or xf_format.fg_color: xf_format.has_dxf_fill = 1 xf_format.dxf_bg_color = xf_format.bg_color xf_format.dxf_fg_color = xf_format.fg_color for xf_format in self.xf_formats: # The following logical statements jointly take care of special # cases in relation to cell colors and patterns: # 1. For a solid fill (_pattern == 1) Excel reverses the role of # foreground and background colors, and # 2. If the user specifies a foreground or background color # without a pattern they probably wanted a solid fill, so we fill # in the defaults. if (xf_format.pattern == 1 and xf_format.bg_color != 0 and xf_format.fg_color != 0): tmp = xf_format.fg_color xf_format.fg_color = xf_format.bg_color xf_format.bg_color = tmp if (xf_format.pattern <= 1 and xf_format.bg_color != 0 and xf_format.fg_color == 0): xf_format.fg_color = xf_format.bg_color xf_format.bg_color = 0 xf_format.pattern = 1 if (xf_format.pattern <= 1 and xf_format.bg_color == 0 and xf_format.fg_color != 0): xf_format.bg_color = 0 xf_format.pattern = 1 key = xf_format._get_fill_key() if key in fills: # Fill has already been used. xf_format.fill_index = fills[key] xf_format.has_fill = 0 else: # This is a new fill. fills[key] = index xf_format.fill_index = index xf_format.has_fill = 1 index += 1 self.fill_count = index def _prepare_defined_names(self): # Iterate through the worksheets and store any defined names in # addition to any user defined names. Stores the defined names # for the Workbook.xml and the named ranges for App.xml. defined_names = self.defined_names for sheet in self.worksheets(): # Check for Print Area settings. if sheet.autofilter_area: hidden = 1 sheet_range = sheet.autofilter_area # Store the defined names. defined_names.append(['_xlnm._FilterDatabase', sheet.index, sheet_range, hidden]) # Check for Print Area settings. if sheet.print_area_range: hidden = 0 sheet_range = sheet.print_area_range # Store the defined names. defined_names.append(['_xlnm.Print_Area', sheet.index, sheet_range, hidden]) # Check for repeat rows/cols referred to as Print Titles. if sheet.repeat_col_range or sheet.repeat_row_range: hidden = 0 sheet_range = '' if sheet.repeat_col_range and sheet.repeat_row_range: sheet_range = (sheet.repeat_col_range + ',' + sheet.repeat_row_range) else: sheet_range = (sheet.repeat_col_range + sheet.repeat_row_range) # Store the defined names. defined_names.append(['_xlnm.Print_Titles', sheet.index, sheet_range, hidden]) defined_names = self._sort_defined_names(defined_names) self.defined_names = defined_names self.named_ranges = self._extract_named_ranges(defined_names) def _sort_defined_names(self, names): # Sort the list of list of internal and user defined names in # the same order as used by Excel. # Add a normalize name string to each list for sorting. for name_list in names: (defined_name, _, sheet_name, _) = name_list # Normalize the defined name by removing any leading '_xmln.' # from internal names and lowercasing the string. defined_name = defined_name.replace('_xlnm.', '').lower() # Normalize the sheetname by removing the leading quote and # lowercasing the string. sheet_name = sheet_name.lstrip("'").lower() name_list.append(defined_name + "::" + sheet_name) # Sort based on the normalized key. names.sort(key=operator.itemgetter(4)) # Remove the extra key used for sorting. for name_list in names: name_list.pop() return names def _prepare_drawings(self): # Iterate through the worksheets and set up chart and image drawings. chart_ref_id = 0 image_ref_id = 0 drawing_id = 0 x_dpi = 96 y_dpi = 96 for sheet in self.worksheets(): chart_count = len(sheet.charts) image_count = len(sheet.images) shape_count = len(sheet.shapes) header_image_count = len(sheet.header_images) footer_image_count = len(sheet.footer_images) has_drawing = False if not (chart_count or image_count or shape_count or header_image_count or footer_image_count): continue # Don't increase the drawing_id header/footer images. if chart_count or image_count or shape_count: drawing_id += 1 has_drawing = True # Prepare the worksheet charts. for index in range(chart_count): chart_ref_id += 1 sheet._prepare_chart(index, chart_ref_id, drawing_id) # Prepare the worksheet images. for index in range(image_count): filename = sheet.images[index][2] image_data = sheet.images[index][10] (image_type, width, height, name, x_dpi, y_dpi) = \ self._get_image_properties(filename, image_data) image_ref_id += 1 sheet._prepare_image(index, image_ref_id, drawing_id, width, height, name, image_type, x_dpi, y_dpi) # Prepare the worksheet shapes. for index in range(shape_count): sheet._prepare_shape(index, drawing_id) # Prepare the header images. for index in range(header_image_count): filename = sheet.header_images[index][0] image_data = sheet.header_images[index][1] position = sheet.header_images[index][2] (image_type, width, height, name, x_dpi, y_dpi) = \ self._get_image_properties(filename, image_data) image_ref_id += 1 sheet._prepare_header_image(image_ref_id, width, height, name, image_type, position, x_dpi, y_dpi) # Prepare the footer images. for index in range(footer_image_count): filename = sheet.footer_images[index][0] image_data = sheet.footer_images[index][1] position = sheet.footer_images[index][2] (image_type, width, height, name, x_dpi, y_dpi) = \ self._get_image_properties(filename, image_data) image_ref_id += 1 sheet._prepare_header_image(image_ref_id, width, height, name, image_type, position, x_dpi, y_dpi) if has_drawing: drawing = sheet.drawing self.drawings.append(drawing) # Remove charts that were created but not inserted into worksheets. for chart in self.charts[:]: if chart.id == -1: self.charts.remove(chart) # Sort the workbook charts references into the order that the were # written to the worksheets above. self.charts = sorted(self.charts, key=lambda chart: chart.id) self.drawing_count = drawing_id def _get_image_properties(self, filename, image_data): # Extract dimension information from the image file. height = 0 width = 0 x_dpi = 96 y_dpi = 96 if not image_data: # Open the image file and read in the data. fh = open(filename, "rb") data = fh.read() else: # Read the image data from the user supplied byte stream. data = image_data.getvalue() # Get the image filename without the path. image_name = os.path.basename(filename) # Look for some common image file markers. marker1 = unpack('3s', data[1:4])[0] marker2 = unpack('>H', data[:2])[0] marker3 = unpack('2s', data[:2])[0] marker4 = unpack('I', data[offset + 0:offset + 4])[0] marker = unpack('>I', data[offset + 4:offset + 8])[0] # Read the image dimensions. if marker == marker_ihdr: width = unpack('>I', data[offset + 8:offset + 12])[0] height = unpack('>I', data[offset + 12:offset + 16])[0] # Read the image DPI. if marker == marker_phys: x_density = unpack('>I', data[offset + 8:offset + 12])[0] y_density = unpack('>I', data[offset + 12:offset + 16])[0] units = unpack('b', data[offset + 16:offset + 17])[0] if units == 1: x_dpi = x_density * 0.0254 y_dpi = y_density * 0.0254 if marker == marker_iend: end_marker = True continue offset = offset + length + 12 return 'png', width, height, x_dpi, y_dpi def _process_jpg(self, data): # Extract width and height information from a JPEG file. offset = 2 data_length = len(data) end_marker = False width = 0 height = 0 x_dpi = 96 y_dpi = 96 # Search through the image data to read the JPEG markers. while not end_marker and offset < data_length: marker = unpack('>H', data[offset + 0:offset + 2])[0] length = unpack('>H', data[offset + 2:offset + 4])[0] # Read the height and width in the 0xFFCn elements (except C4, C8 # and CC which aren't SOF markers). if ((marker & 0xFFF0) == 0xFFC0 and marker != 0xFFC4 and marker != 0xFFC8 and marker != 0xFFCC): height = unpack('>H', data[offset + 5:offset + 7])[0] width = unpack('>H', data[offset + 7:offset + 9])[0] # Read the DPI in the 0xFFE0 element. if marker == 0xFFE0: units = unpack('b', data[offset + 11:offset + 12])[0] x_density = unpack('>H', data[offset + 12:offset + 14])[0] y_density = unpack('>H', data[offset + 14:offset + 16])[0] if units == 1: x_dpi = x_density y_dpi = y_density if units == 2: x_dpi = x_density * 2.54 y_dpi = y_density * 2.54 # Workaround for incorrect dpi. if x_dpi == 1: x_dpi = 96 if y_dpi == 1: y_dpi = 96 if marker == 0xFFDA: end_marker = True continue offset = offset + length + 2 return 'jpeg', width, height, x_dpi, y_dpi def _process_bmp(self, data): # Extract width and height information from a BMP file. width = unpack(' 0: xf = self.add_format({'font_name': 'Tahoma', 'font_size': 8, 'color_indexed': 81, 'font_only': True}) xf._get_xf_index() # Set the workbook vba_codename if one of the sheets has a button and # the workbook has a vbaProject binary. if has_button and self.vba_project and self.vba_codename is None: self.set_vba_name() def _prepare_tables(self): # Set the table ids for the worksheet tables. table_id = 0 seen = {} for sheet in self.worksheets(): table_count = len(sheet.tables) if not table_count: continue sheet._prepare_tables(table_id + 1, seen) table_id += table_count def _add_chart_data(self): # Add "cached" data to charts to provide the numCache and strCache # data for series and title/axis ranges. worksheets = {} seen_ranges = {} charts = [] # Map worksheet names to worksheet objects. for worksheet in self.worksheets(): worksheets[worksheet.name] = worksheet # Build a list of the worksheet charts including any combined charts. for chart in self.charts: charts.append(chart) if chart.combined: charts.append(chart.combined) for chart in charts: for c_range in chart.formula_ids.keys(): r_id = chart.formula_ids[c_range] # Skip if the series has user defined data. if chart.formula_data[r_id] is not None: if (c_range not in seen_ranges or seen_ranges[c_range] is None): data = chart.formula_data[r_id] seen_ranges[c_range] = data continue # Check to see if the data is already cached locally. if c_range in seen_ranges: chart.formula_data[r_id] = seen_ranges[c_range] continue # Convert the range formula to a sheet name and cell range. (sheetname, cells) = self._get_chart_range(c_range) # Skip if we couldn't parse the formula. if sheetname is None: continue # Handle non-contiguous ranges like: # (Sheet1!$A$1:$A$2,Sheet1!$A$4:$A$5). # We don't try to parse them. We just return an empty list. if sheetname.startswith('('): chart.formula_data[r_id] = [] seen_ranges[c_range] = [] continue # Warn if the name is unknown since it indicates a user error # in a chart series formula. if sheetname not in worksheets: warn("Unknown worksheet reference '%s' in range " "'%s' passed to add_series()" % (force_unicode(sheetname), force_unicode(c_range))) chart.formula_data[r_id] = [] seen_ranges[c_range] = [] continue # Find the worksheet object based on the sheet name. worksheet = worksheets[sheetname] # Get the data from the worksheet table. data = worksheet._get_range_data(*cells) # TODO. Handle SST string ids if required. # Add the data to the chart. chart.formula_data[r_id] = data # Store range data locally to avoid lookup if seen again. seen_ranges[c_range] = data def _get_chart_range(self, c_range): # Convert a range formula such as Sheet1!$B$1:$B$5 into a sheet name # and cell range such as ( 'Sheet1', 0, 1, 4, 1 ). # Split the range formula into sheetname and cells at the last '!'. pos = c_range.rfind('!') if pos > 0: sheetname = c_range[:pos] cells = c_range[pos + 1:] else: return None, None # Split the cell range into 2 cells or else use single cell for both. if cells.find(':') > 0: (cell_1, cell_2) = cells.split(':', 1) else: (cell_1, cell_2) = (cells, cells) # Remove leading/trailing quotes and convert escaped quotes to single. sheetname = sheetname.strip("'") sheetname = sheetname.replace("''", "'") try: # Get the row, col values from the Excel ranges. We do this in a # try block for ranges that can't be parsed such as defined names. (row_start, col_start) = xl_cell_to_rowcol(cell_1) (row_end, col_end) = xl_cell_to_rowcol(cell_2) except AttributeError: return None, None # We only handle 1D ranges. if row_start != row_end and col_start != col_end: return None, None return sheetname, [row_start, col_start, row_end, col_end] def _prepare_sst_string_data(self): # Convert the SST string data from a dict to a list. self.str_table._sort_string_data() ########################################################################### # # XML methods. # ########################################################################### def _write_workbook(self): # Write 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('workbook', attributes) def _write_file_version(self): # Write the element. app_name = 'xl' last_edited = 4 lowest_edited = 4 rup_build = 4505 attributes = [ ('appName', app_name), ('lastEdited', last_edited), ('lowestEdited', lowest_edited), ('rupBuild', rup_build), ] if self.vba_project: attributes.append( ('codeName', '{37E998C4-C9E5-D4B9-71C8-EB1FF731991C}')) self._xml_empty_tag('fileVersion', attributes) def _write_workbook_pr(self): # Write element. default_theme_version = 124226 attributes = [] if self.vba_codename: attributes.append(('codeName', self.vba_codename)) if self.date_1904: attributes.append(('date1904', 1)) attributes.append(('defaultThemeVersion', default_theme_version)) self._xml_empty_tag('workbookPr', attributes) def _write_book_views(self): # Write element. self._xml_start_tag('bookViews') self._write_workbook_view() self._xml_end_tag('bookViews') def _write_workbook_view(self): # Write element. attributes = [ ('xWindow', self.x_window), ('yWindow', self.y_window), ('windowWidth', self.window_width), ('windowHeight', self.window_height), ] # Store the tabRatio attribute when it isn't the default. if self.tab_ratio != 600: attributes.append(('tabRatio', self.tab_ratio)) # Store the firstSheet attribute when it isn't the default. if self.worksheet_meta.firstsheet > 0: firstsheet = self.worksheet_meta.firstsheet + 1 attributes.append(('firstSheet', firstsheet)) # Store the activeTab attribute when it isn't the first sheet. if self.worksheet_meta.activesheet > 0: attributes.append(('activeTab', self.worksheet_meta.activesheet)) self._xml_empty_tag('workbookView', attributes) def _write_sheets(self): # Write element. self._xml_start_tag('sheets') id_num = 1 for worksheet in self.worksheets(): self._write_sheet(worksheet.name, id_num, worksheet.hidden) id_num += 1 self._xml_end_tag('sheets') def _write_sheet(self, name, sheet_id, hidden): # Write element. attributes = [ ('name', name), ('sheetId', sheet_id), ] if hidden: attributes.append(('state', 'hidden')) attributes.append(('r:id', 'rId' + str(sheet_id))) self._xml_empty_tag('sheet', attributes) def _write_calc_pr(self): # Write the element. attributes = [('calcId', self.calc_id)] if self.calc_mode == 'manual': attributes.append(('calcMode', self.calc_mode)) attributes.append(('calcOnSave', "0")) elif self.calc_mode == 'autoNoTable': attributes.append(('calcMode', self.calc_mode)) if self.calc_on_load: attributes.append(('fullCalcOnLoad', '1')) self._xml_empty_tag('calcPr', attributes) def _write_defined_names(self): # Write the element. if not self.defined_names: return self._xml_start_tag('definedNames') for defined_name in self.defined_names: self._write_defined_name(defined_name) self._xml_end_tag('definedNames') def _write_defined_name(self, defined_name): # Write the element. name = defined_name[0] sheet_id = defined_name[1] sheet_range = defined_name[2] hidden = defined_name[3] attributes = [('name', name)] if sheet_id != -1: attributes.append(('localSheetId', sheet_id)) if hidden: attributes.append(('hidden', 1)) self._xml_data_element('definedName', sheet_range, attributes) # A metadata class to share data between worksheets. class WorksheetMeta(object): """ A class to track worksheets data such as the active sheet and the first sheet. """ def __init__(self): self.activesheet = 0 self.firstsheet = 0 XlsxWriter-1.1.2/xlsxwriter/worksheet.py0000644000076500000240000073213513351750661020531 0ustar Johnstaff00000000000000############################################################################### # # Worksheet - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # # Standard packages. import codecs import datetime import os import re import sys import tempfile from warnings import warn # Standard packages in Python 2/3 compatibility mode. from .compatibility import StringIO from .compatibility import defaultdict from .compatibility import namedtuple from .compatibility import force_unicode from .compatibility import num_types, str_types # Package imports. from . import xmlwriter from .format import Format from .drawing import Drawing from .shape import Shape from .xmlwriter import XMLwriter from .utility import xl_rowcol_to_cell from .utility import xl_rowcol_to_cell_fast from .utility import xl_cell_to_rowcol from .utility import xl_col_to_name from .utility import xl_range from .utility import xl_color from .utility import get_sparkline_style from .utility import supported_datetime from .utility import datetime_to_excel_datetime from .utility import quote_sheetname from .exceptions import DuplicateTableName ############################################################################### # # Decorator functions. # ############################################################################### def convert_cell_args(method): """ Decorator function to convert A1 notation in cell method calls to the default row/col notation. """ def cell_wrapper(self, *args, **kwargs): try: # First arg is an int, default to row/col notation. if len(args): first_arg = args[0] int(first_arg) except ValueError: # First arg isn't an int, convert to A1 notation. new_args = xl_cell_to_rowcol(first_arg) args = new_args + args[1:] return method(self, *args, **kwargs) return cell_wrapper def convert_range_args(method): """ Decorator function to convert A1 notation in range method calls to the default row/col notation. """ def cell_wrapper(self, *args, **kwargs): try: # First arg is an int, default to row/col notation. if len(args): int(args[0]) except ValueError: # First arg isn't an int, convert to A1 notation. if ':' in args[0]: cell_1, cell_2 = args[0].split(':') row_1, col_1 = xl_cell_to_rowcol(cell_1) row_2, col_2 = xl_cell_to_rowcol(cell_2) else: row_1, col_1 = xl_cell_to_rowcol(args[0]) row_2, col_2 = row_1, col_1 new_args = [row_1, col_1, row_2, col_2] new_args.extend(args[1:]) args = new_args return method(self, *args, **kwargs) return cell_wrapper def convert_column_args(method): """ Decorator function to convert A1 notation in columns method calls to the default row/col notation. """ def column_wrapper(self, *args, **kwargs): try: # First arg is an int, default to row/col notation. if len(args): int(args[0]) except ValueError: # First arg isn't an int, convert to A1 notation. cell_1, cell_2 = [col + '1' for col in args[0].split(':')] _, col_1 = xl_cell_to_rowcol(cell_1) _, col_2 = xl_cell_to_rowcol(cell_2) new_args = [col_1, col_2] new_args.extend(args[1:]) args = new_args return method(self, *args, **kwargs) return column_wrapper ############################################################################### # # Named tuples used for cell types. # ############################################################################### cell_string_tuple = namedtuple('String', 'string, format') cell_number_tuple = namedtuple('Number', 'number, format') cell_blank_tuple = namedtuple('Blank', 'format') cell_boolean_tuple = namedtuple('Boolean', 'boolean, format') cell_formula_tuple = namedtuple('Formula', 'formula, format, value') cell_arformula_tuple = namedtuple('ArrayFormula', 'formula, format, value, range') ############################################################################### # # Worksheet Class definition. # ############################################################################### class Worksheet(xmlwriter.XMLwriter): """ A class for writing the Excel XLSX Worksheet file. """ ########################################################################### # # Public API. # ########################################################################### def __init__(self): """ Constructor. """ super(Worksheet, self).__init__() self.name = None self.index = None self.str_table = None self.palette = None self.constant_memory = 0 self.tmpdir = None self.is_chartsheet = False self.ext_sheets = [] self.fileclosed = 0 self.excel_version = 2007 self.excel2003_style = False self.xls_rowmax = 1048576 self.xls_colmax = 16384 self.xls_strmax = 32767 self.dim_rowmin = None self.dim_rowmax = None self.dim_colmin = None self.dim_colmax = None self.colinfo = {} self.selections = [] self.hidden = 0 self.active = 0 self.tab_color = 0 self.panes = [] self.active_pane = 3 self.selected = 0 self.page_setup_changed = False self.paper_size = 0 self.orientation = 1 self.print_options_changed = False self.hcenter = False self.vcenter = False self.print_gridlines = False self.screen_gridlines = True self.print_headers = False self.row_col_headers = False self.header_footer_changed = False self.header = '' self.footer = '' self.header_footer_aligns = True self.header_footer_scales = True self.header_images = [] self.footer_images = [] self.header_images_list = [] self.margin_left = 0.7 self.margin_right = 0.7 self.margin_top = 0.75 self.margin_bottom = 0.75 self.margin_header = 0.3 self.margin_footer = 0.3 self.repeat_row_range = '' self.repeat_col_range = '' self.print_area_range = '' self.page_order = 0 self.black_white = 0 self.draft_quality = 0 self.print_comments = 0 self.page_start = 0 self.fit_page = 0 self.fit_width = 0 self.fit_height = 0 self.hbreaks = [] self.vbreaks = [] self.protect_options = {} self.set_cols = {} self.set_rows = defaultdict(dict) self.zoom = 100 self.zoom_scale_normal = 1 self.print_scale = 100 self.is_right_to_left = 0 self.show_zeros = 1 self.leading_zeros = 0 self.outline_row_level = 0 self.outline_col_level = 0 self.outline_style = 0 self.outline_below = 1 self.outline_right = 1 self.outline_on = 1 self.outline_changed = False self.original_row_height = 15 self.default_row_height = 15 self.default_row_pixels = 20 self.default_col_pixels = 64 self.default_row_zeroed = 0 self.names = {} self.write_match = [] self.table = defaultdict(dict) self.merge = [] self.row_spans = {} self.has_vml = False self.has_header_vml = False self.has_comments = False self.comments = defaultdict(dict) self.comments_list = [] self.comments_author = '' self.comments_visible = 0 self.vml_shape_id = 1024 self.buttons_list = [] self.vml_header_id = 0 self.autofilter_area = '' self.autofilter_ref = None self.filter_range = [] self.filter_on = 0 self.filter_cols = {} self.filter_type = {} self.col_sizes = {} self.row_sizes = {} self.col_formats = {} self.col_size_changed = False self.row_size_changed = False self.last_shape_id = 1 self.rel_count = 0 self.hlink_count = 0 self.hlink_refs = [] self.external_hyper_links = [] self.external_drawing_links = [] self.external_comment_links = [] self.external_vml_links = [] self.external_table_links = [] self.drawing_links = [] self.vml_drawing_links = [] self.charts = [] self.images = [] self.tables = [] self.sparklines = [] self.shapes = [] self.shape_hash = {} self.drawing = 0 self.rstring = '' self.previous_row = 0 self.validations = [] self.cond_formats = {} self.data_bars_2010 = [] self.use_data_bars_2010 = False self.dxf_priority = 1 self.page_view = 0 self.vba_codename = None self.date_1904 = False self.hyperlinks = defaultdict(dict) self.strings_to_numbers = False self.strings_to_urls = True self.nan_inf_to_errors = False self.strings_to_formulas = True self.default_date_format = None self.default_url_format = None self.remove_timezone = False self.row_data_filename = None self.row_data_fh = None self.worksheet_meta = None self.vml_data_id = None self.vml_shape_id = None self.row_data_filename = None self.row_data_fh = None self.row_data_fh_closed = False self.vertical_dpi = 0 self.horizontal_dpi = 0 # Utility function for writing different types of strings. def _write_token_as_string(self, token, row, col, *args): # Map the data to the appropriate write_*() method. if token is '': return self._write_blank(row, col, *args) if self.strings_to_formulas and token.startswith('='): return self._write_formula(row, col, *args) if token.startswith('{=') and token.endswith('}'): return self._write_formula(row, col, *args) if ':' in token: if self.strings_to_urls and re.match('(ftp|http)s?://', token): return self._write_url(row, col, *args) elif self.strings_to_urls and re.match('mailto:', token): return self._write_url(row, col, *args) elif self.strings_to_urls and re.match('(in|ex)ternal:', token): return self._write_url(row, col, *args) if self.strings_to_numbers: try: f = float(token) if (self.nan_inf_to_errors or (not self._isnan(f) and not self._isinf(f))): return self._write_number(row, col, f, *args[1:]) except ValueError: # Not a number, write as a string. pass return self._write_string(row, col, *args) else: # We have a plain string. return self._write_string(row, col, *args) @convert_cell_args def write(self, row, col, *args): """ Write data to a worksheet cell by calling the appropriate write_*() method based on the type of data being passed. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). *args: Args to pass to sub functions. Returns: 0: Success. -1: Row or column is out of worksheet bounds. other: Return value of called method. """ return self._write(row, col, *args) # Undecorated version of write(). def _write(self, row, col, *args): # Check the number of args passed. if not len(args): raise TypeError("write() takes at least 4 arguments (3 given)") # The first arg should be the token for all write calls. token = args[0] # Write None as a blank cell. if token is None: return self._write_blank(row, col, *args) # Avoid isinstance() for better performance. token_type = type(token) if token_type is bool: return self._write_boolean(row, col, *args) if token_type in num_types: return self._write_number(row, col, *args) if token_type is str: return self._write_token_as_string(token, row, col, *args) if token_type in (datetime.datetime, datetime.date, datetime.time, datetime.timedelta): return self._write_datetime(row, col, *args) if sys.version_info < (3, 0, 0): if token_type is unicode: try: return self._write_token_as_string(str(token), row, col, *args) except (UnicodeEncodeError, NameError): pass # Resort to isinstance() for subclassed primitives. # Write number types. if isinstance(token, num_types): return self._write_number(row, col, *args) # Write string types. if isinstance(token, str_types): return self._write_token_as_string(token, row, col, *args) # Write boolean types. if isinstance(token, bool): return self._write_boolean(row, col, *args) # Write datetime objects. if supported_datetime(token): return self._write_datetime(row, col, *args) # We haven't matched a supported type. Try float. try: f = float(token) return self._write_number(row, col, f, *args[1:]) except ValueError: pass except TypeError: raise TypeError("Unsupported type %s in write()" % type(token)) # Finally try string. try: str(token) return self._write_string(row, col, *args) except ValueError: raise TypeError("Unsupported type %s in write()" % type(token)) @convert_cell_args def write_string(self, row, col, string, cell_format=None): """ Write a string to a worksheet cell. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). string: Cell data. Str. format: An optional cell Format object. Returns: 0: Success. -1: Row or column is out of worksheet bounds. -2: String truncated to 32k characters. """ return self._write_string(row, col, string, cell_format) # Undecorated version of write_string(). def _write_string(self, row, col, string, cell_format=None): str_error = 0 # Check that row and col are valid and store max and min values. if self._check_dimensions(row, col): return -1 # Check that the string is < 32767 chars. if len(string) > self.xls_strmax: string = string[:self.xls_strmax] str_error = -2 # Write a shared string or an in-line string in constant_memory mode. if not self.constant_memory: string_index = self.str_table._get_shared_string_index(string) else: string_index = string # Write previous row if in in-line string constant_memory mode. if self.constant_memory and row > self.previous_row: self._write_single_row(row) # Store the cell data in the worksheet data table. self.table[row][col] = cell_string_tuple(string_index, cell_format) return str_error @convert_cell_args def write_number(self, row, col, number, cell_format=None): """ Write a number to a worksheet cell. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). number: Cell data. Int or float. cell_format: An optional cell Format object. Returns: 0: Success. -1: Row or column is out of worksheet bounds. """ return self._write_number(row, col, number, cell_format) # Undecorated version of write_number(). def _write_number(self, row, col, number, cell_format=None): if self._isnan(number) or self._isinf(number): if self.nan_inf_to_errors: if self._isnan(number): return self._write_formula(row, col, '#NUM!', cell_format, '#NUM!') elif self._isinf(number): return self._write_formula(row, col, '1/0', cell_format, '#DIV/0!') else: raise TypeError( "NAN/INF not supported in write_number() " "without 'nan_inf_to_errors' Workbook() option") # Check that row and col are valid and store max and min values. if self._check_dimensions(row, col): return -1 # Write previous row if in in-line string constant_memory mode. if self.constant_memory and row > self.previous_row: self._write_single_row(row) # Store the cell data in the worksheet data table. self.table[row][col] = cell_number_tuple(number, cell_format) return 0 @convert_cell_args def write_blank(self, row, col, blank, cell_format=None): """ Write a blank cell with formatting to a worksheet cell. The blank token is ignored and the format only is written to the cell. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). blank: Any value. It is ignored. cell_format: An optional cell Format object. Returns: 0: Success. -1: Row or column is out of worksheet bounds. """ return self._write_blank(row, col, blank, cell_format) # Undecorated version of write_blank(). def _write_blank(self, row, col, blank, cell_format=None): # Don't write a blank cell unless it has a format. if cell_format is None: return 0 # Check that row and col are valid and store max and min values. if self._check_dimensions(row, col): return -1 # Write previous row if in in-line string constant_memory mode. if self.constant_memory and row > self.previous_row: self._write_single_row(row) # Store the cell data in the worksheet data table. self.table[row][col] = cell_blank_tuple(cell_format) return 0 @convert_cell_args def write_formula(self, row, col, formula, cell_format=None, value=0): """ Write a formula to a worksheet cell. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). formula: Cell formula. cell_format: An optional cell Format object. value: An optional value for the formula. Default is 0. Returns: 0: Success. -1: Row or column is out of worksheet bounds. """ # Check that row and col are valid and store max and min values. return self._write_formula(row, col, formula, cell_format, value) # Undecorated version of write_formula(). def _write_formula(self, row, col, formula, cell_format=None, value=0): if self._check_dimensions(row, col): return -1 # Hand off array formulas. if formula.startswith('{') and formula.endswith('}'): return self._write_array_formula(row, col, row, col, formula, cell_format, value) # Remove the formula '=' sign if it exists. if formula.startswith('='): formula = formula.lstrip('=') # Write previous row if in in-line string constant_memory mode. if self.constant_memory and row > self.previous_row: self._write_single_row(row) # Store the cell data in the worksheet data table. self.table[row][col] = cell_formula_tuple(formula, cell_format, value) return 0 @convert_range_args def write_array_formula(self, first_row, first_col, last_row, last_col, formula, cell_format=None, value=0): """ Write a formula to a worksheet cell. Args: first_row: The first row of the cell range. (zero indexed). first_col: The first column of the cell range. last_row: The last row of the cell range. (zero indexed). last_col: The last column of the cell range. formula: Cell formula. cell_format: An optional cell Format object. value: An optional value for the formula. Default is 0. Returns: 0: Success. -1: Row or column is out of worksheet bounds. """ return self._write_array_formula(first_row, first_col, last_row, last_col, formula, cell_format, value) # Undecorated version of write_array_formula(). def _write_array_formula(self, first_row, first_col, last_row, last_col, formula, cell_format=None, value=0): # Swap last row/col with first row/col as necessary. if first_row > last_row: first_row, last_row = last_row, first_row if first_col > last_col: first_col, last_col = last_col, first_col # Check that row and col are valid and store max and min values if self._check_dimensions(last_row, last_col): return -1 # Define array range if first_row == last_row and first_col == last_col: cell_range = xl_rowcol_to_cell(first_row, first_col) else: cell_range = (xl_rowcol_to_cell(first_row, first_col) + ':' + xl_rowcol_to_cell(last_row, last_col)) # Remove array formula braces and the leading =. if formula[0] == '{': formula = formula[1:] if formula[0] == '=': formula = formula[1:] if formula[-1] == '}': formula = formula[:-1] # Write previous row if in in-line string constant_memory mode. if self.constant_memory and first_row > self.previous_row: self._write_single_row(first_row) # Store the cell data in the worksheet data table. self.table[first_row][first_col] = cell_arformula_tuple(formula, cell_format, value, cell_range) # Pad out the rest of the area with formatted zeroes. if not self.constant_memory: for row in range(first_row, last_row + 1): for col in range(first_col, last_col + 1): if row != first_row or col != first_col: self._write_number(row, col, 0, cell_format) return 0 @convert_cell_args def write_datetime(self, row, col, date, cell_format=None): """ Write a date or time to a worksheet cell. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). date: Date and/or time as a datetime object. cell_format: A cell Format object. Returns: 0: Success. -1: Row or column is out of worksheet bounds. """ return self._write_datetime(row, col, date, cell_format) # Undecorated version of write_datetime(). def _write_datetime(self, row, col, date, cell_format=None): # Check that row and col are valid and store max and min values. if self._check_dimensions(row, col): return -1 # Write previous row if in in-line string constant_memory mode. if self.constant_memory and row > self.previous_row: self._write_single_row(row) # Convert datetime to an Excel date. number = self._convert_date_time(date) # Add the default date format. if cell_format is None: cell_format = self.default_date_format # Store the cell data in the worksheet data table. self.table[row][col] = cell_number_tuple(number, cell_format) return 0 @convert_cell_args def write_boolean(self, row, col, boolean, cell_format=None): """ Write a boolean value to a worksheet cell. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). boolean: Cell data. bool type. cell_format: An optional cell Format object. Returns: 0: Success. -1: Row or column is out of worksheet bounds. """ return self._write_boolean(row, col, boolean, cell_format) # Undecorated version of write_boolean(). def _write_boolean(self, row, col, boolean, cell_format=None): # Check that row and col are valid and store max and min values. if self._check_dimensions(row, col): return -1 # Write previous row if in in-line string constant_memory mode. if self.constant_memory and row > self.previous_row: self._write_single_row(row) if boolean: value = 1 else: value = 0 # Store the cell data in the worksheet data table. self.table[row][col] = cell_boolean_tuple(value, cell_format) return 0 # Write a hyperlink. This is comprised of two elements: the displayed # string and the non-displayed link. The displayed string is the same as # the link unless an alternative string is specified. The display string # is written using the write_string() method. Therefore the max characters # string limit applies. # # The hyperlink can be to a http, ftp, mail, internal sheet, or external # directory urls. @convert_cell_args def write_url(self, row, col, url, cell_format=None, string=None, tip=None): """ Write a hyperlink to a worksheet cell. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). url: Hyperlink url. format: An optional cell Format object. string: An optional display string for the hyperlink. tip: An optional tooltip. Returns: 0: Success. -1: Row or column is out of worksheet bounds. -2: String longer than 32767 characters. -3: URL longer than Excel limit of 255 characters. -4: Exceeds Excel limit of 65,530 urls per worksheet. """ return self._write_url(row, col, url, cell_format, string, tip) # Undecorated version of write_url(). def _write_url(self, row, col, url, cell_format=None, string=None, tip=None): # Check that row and col are valid and store max and min values if self._check_dimensions(row, col): return -1 # Set the displayed string to the URL unless defined by the user. if string is None: string = url # Default to external link type such as 'http://' or 'external:'. link_type = 1 # Remove the URI scheme from internal links. if url.startswith('internal:'): url = url.replace('internal:', '') string = string.replace('internal:', '') link_type = 2 # Remove the URI scheme from external links and change the directory # separator from Unix to Dos. external = False if url.startswith('external:'): url = url.replace('external:', '') url = url.replace('/', '\\') string = string.replace('external:', '') string = string.replace('/', '\\') external = True # Strip the mailto header. string = string.replace('mailto:', '') # Check that the string is < 32767 chars str_error = 0 if len(string) > self.xls_strmax: warn("Ignoring URL since it exceeds Excel's string limit of " "32767 characters") return -2 # Copy string for use in hyperlink elements. url_str = string # External links to URLs and to other Excel workbooks have slightly # different characteristics that we have to account for. if link_type == 1: # Split url into the link and optional anchor/location. if '#' in url: url, url_str = url.split('#', 1) else: url_str = None url = self._escape_url(url) if url_str is not None and not external: url_str = self._escape_url(url_str) # Add the file:/// URI to the url for Windows style "C:/" link and # Network shares. if re.match(r'\w:', url) or re.match(r'\\', url): url = 'file:///' + url # Convert a .\dir\file.xlsx link to dir\file.xlsx. url = re.sub(r'^\.\\', '', url) # Excel limits the escaped URL and location/anchor to 255 characters. tmp_url_str = url_str or '' if len(url) > 255 or len(tmp_url_str) > 255: warn("Ignoring URL '%s' with link or location/anchor > 255 " "characters since it exceeds Excel's limit for URLS" % force_unicode(url)) return -3 # Check the limit of URLS per worksheet. self.hlink_count += 1 if self.hlink_count > 65530: warn("Ignoring URL '%s' since it exceeds Excel's limit of " "65,530 URLS per worksheet." % force_unicode(url)) return -4 # Write previous row if in in-line string constant_memory mode. if self.constant_memory and row > self.previous_row: self._write_single_row(row) # Add the default URL format. if cell_format is None: cell_format = self.default_url_format # Write the hyperlink string. self._write_string(row, col, string, cell_format) # Store the hyperlink data in a separate structure. self.hyperlinks[row][col] = { 'link_type': link_type, 'url': url, 'str': url_str, 'tip': tip} return str_error @convert_cell_args def write_rich_string(self, row, col, *args): """ Write a "rich" string with multiple formats to a worksheet cell. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). string_parts: String and format pairs. cell_format: Optional Format object. Returns: 0: Success. -1: Row or column is out of worksheet bounds. -2: String truncated to 32k characters. -3: 2 consecutive formats used. -4: Empty string used. -5: Insufficient parameters. """ return self._write_rich_string(row, col, *args) # Undecorated version of write_rich_string(). def _write_rich_string(self, row, col, *args): tokens = list(args) cell_format = None str_length = 0 string_index = 0 # Check that row and col are valid and store max and min values if self._check_dimensions(row, col): return -1 # If the last arg is a format we use it as the cell format. if isinstance(tokens[-1], Format): cell_format = tokens.pop() # Create a temp XMLWriter object and use it to write the rich string # XML to a string. fh = StringIO() self.rstring = XMLwriter() self.rstring._set_filehandle(fh) # Create a temp format with the default font for unformatted fragments. default = Format() # Convert list of format, string tokens to pairs of (format, string) # except for the first string fragment which doesn't require a default # formatting run. Use the default for strings without a leading format. fragments = [] previous = 'format' pos = 0 if len(tokens) <= 2: warn("You must specify more then 2 format/fragments for rich " "strings. Ignoring input in write_rich_string().") return -5 for token in tokens: if not isinstance(token, Format): # Token is a string. if previous != 'format': # If previous token wasn't a format add one before string. fragments.append(default) fragments.append(token) else: # If previous token was a format just add the string. fragments.append(token) if token == '': warn("Excel doesn't allow empty strings in rich strings. " "Ignoring input in write_rich_string().") return -4 # Keep track of actual string str_length. str_length += len(token) previous = 'string' else: # Can't allow 2 formats in a row. if previous == 'format' and pos > 0: warn("Excel doesn't allow 2 consecutive formats in rich " "strings. Ignoring input in write_rich_string().") return -3 # Token is a format object. Add it to the fragment list. fragments.append(token) previous = 'format' pos += 1 # If the first token is a string start the element. if not isinstance(fragments[0], Format): self.rstring._xml_start_tag('r') # Write the XML elements for the $format $string fragments. for token in fragments: if isinstance(token, Format): # Write the font run. self.rstring._xml_start_tag('r') self._write_font(token) else: # Write the string fragment part, with whitespace handling. attributes = [] if re.search(r'^\s', token) or re.search(r'\s$', token): attributes.append(('xml:space', 'preserve')) self.rstring._xml_data_element('t', token, attributes) self.rstring._xml_end_tag('r') # Read the in-memory string. string = self.rstring.fh.getvalue() # Check that the string is < 32767 chars. if str_length > self.xls_strmax: return -2 # Write a shared string or an in-line string in constant_memory mode. if not self.constant_memory: string_index = self.str_table._get_shared_string_index(string) else: string_index = string # Write previous row if in in-line string constant_memory mode. if self.constant_memory and row > self.previous_row: self._write_single_row(row) # Store the cell data in the worksheet data table. self.table[row][col] = cell_string_tuple(string_index, cell_format) return 0 @convert_cell_args def write_row(self, row, col, data, cell_format=None): """ Write a row of data starting from (row, col). Args: row: The cell row (zero indexed). col: The cell column (zero indexed). data: A list of tokens to be written with write(). format: An optional cell Format object. Returns: 0: Success. other: Return value of write() method. """ for token in data: error = self._write(row, col, token, cell_format) if error: return error col += 1 return 0 @convert_cell_args def write_column(self, row, col, data, cell_format=None): """ Write a column of data starting from (row, col). Args: row: The cell row (zero indexed). col: The cell column (zero indexed). data: A list of tokens to be written with write(). format: An optional cell Format object. Returns: 0: Success. other: Return value of write() method. """ for token in data: error = self._write(row, col, token, cell_format) if error: return error row += 1 return 0 @convert_cell_args def insert_image(self, row, col, filename, options=None): """ Insert an image with its top-left corner in a worksheet cell. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). filename: Path and filename for image in PNG, JPG or BMP format. options: Position, scale, url and data stream of the image. Returns: 0: Success. -1: Row or column is out of worksheet bounds. """ # Check insert (row, col) without storing. if self._check_dimensions(row, col, True, True): warn('Cannot insert image at (%d, %d).' % (row, col)) return -1 if options is None: options = {} x_offset = options.get('x_offset', 0) y_offset = options.get('y_offset', 0) x_scale = options.get('x_scale', 1) y_scale = options.get('y_scale', 1) url = options.get('url', None) tip = options.get('tip', None) anchor = options.get('positioning', None) image_data = options.get('image_data', None) if not image_data and not os.path.exists(filename): warn("Image file '%s' not found." % force_unicode(filename)) return -1 self.images.append([row, col, filename, x_offset, y_offset, x_scale, y_scale, url, tip, anchor, image_data]) @convert_cell_args def insert_textbox(self, row, col, text, options=None): """ Insert an textbox with its top-left corner in a worksheet cell. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). text: The text for the textbox. options: Textbox options. Returns: 0: Success. -1: Row or column is out of worksheet bounds. """ # Check insert (row, col) without storing. if self._check_dimensions(row, col, True, True): warn('Cannot insert textbox at (%d, %d).' % (row, col)) return -1 if options is None: options = {} x_offset = options.get('x_offset', 0) y_offset = options.get('y_offset', 0) x_scale = options.get('x_scale', 1) y_scale = options.get('y_scale', 1) self.shapes.append([row, col, x_offset, y_offset, x_scale, y_scale, text, options]) @convert_cell_args def insert_chart(self, row, col, chart, options=None): """ Insert an chart with its top-left corner in a worksheet cell. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). chart: Chart object. options: Position and scale of the chart. Returns: 0: Success. -1: Row or column is out of worksheet bounds. """ # Check insert (row, col) without storing. if self._check_dimensions(row, col, True, True): warn('Cannot insert chart at (%d, %d).' % (row, col)) return -1 if options is None: options = {} # Ensure a chart isn't inserted more than once. if (chart.already_inserted or chart.combined and chart.combined.already_inserted): warn('Chart cannot be inserted in a worksheet more than once.') return else: chart.already_inserted = True if chart.combined: chart.combined.already_inserted = True x_offset = options.get('x_offset', 0) y_offset = options.get('y_offset', 0) x_scale = options.get('x_scale', 1) y_scale = options.get('y_scale', 1) # Allow Chart to override the scale and offset. if chart.x_scale != 1: x_scale = chart.x_scale if chart.y_scale != 1: y_scale = chart.y_scale if chart.x_offset: x_offset = chart.x_offset if chart.y_offset: y_offset = chart.y_offset self.charts.append([row, col, chart, x_offset, y_offset, x_scale, y_scale]) @convert_cell_args def write_comment(self, row, col, comment, options=None): """ Write a comment to a worksheet cell. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). comment: Cell comment. Str. options: Comment formatting options. Returns: 0: Success. -1: Row or column is out of worksheet bounds. -2: String longer than 32k characters. """ if options is None: options = {} # Check that row and col are valid and store max and min values if self._check_dimensions(row, col): return -1 # Check that the comment string is < 32767 chars. if len(comment) > self.xls_strmax: return -2 self.has_vml = 1 self.has_comments = 1 # Process the properties of the cell comment. self.comments[row][col] = \ self._comment_params(row, col, comment, options) def show_comments(self): """ Make any comments in the worksheet visible. Args: None. Returns: Nothing. """ self.comments_visible = 1 def set_comments_author(self, author): """ Set the default author of the cell comments. Args: author: Comment author name. String. Returns: Nothing. """ self.comments_author = author def get_name(self): """ Retrieve the worksheet name. Args: None. Returns: Nothing. """ # There is no set_name() method. Name must be set in add_worksheet(). return self.name def activate(self): """ Set this worksheet as the active worksheet, i.e. the worksheet that is displayed when the workbook is opened. Also set it as selected. Note: An active worksheet cannot be hidden. Args: None. Returns: Nothing. """ self.hidden = 0 self.selected = 1 self.worksheet_meta.activesheet = self.index def select(self): """ Set current worksheet as a selected worksheet, i.e. the worksheet has its tab highlighted. Note: A selected worksheet cannot be hidden. Args: None. Returns: Nothing. """ self.selected = 1 self.hidden = 0 def hide(self): """ Hide the current worksheet. Args: None. Returns: Nothing. """ self.hidden = 1 # A hidden worksheet shouldn't be active or selected. self.selected = 0 # TODO. Should add a check to see if the sheet is the global # activesheet or firstsheet and reset them. def set_first_sheet(self): """ Set current worksheet as the first visible sheet. This is necessary when there are a large number of worksheets and the activated worksheet is not visible on the screen. Note: A selected worksheet cannot be hidden. Args: None. Returns: Nothing. """ self.hidden = 0 # Active worksheet can't be hidden. self.worksheet_meta.firstsheet = self.index @convert_column_args def set_column(self, first_col, last_col, width=None, cell_format=None, options=None): """ Set the width, and other properties of a single column or a range of columns. Args: first_col: First column (zero-indexed). last_col: Last column (zero-indexed). Can be same as first_col. width: Column width. (optional). cell_format: Column cell_format. (optional). options: Dict of options such as hidden and level. Returns: 0: Success. -1: Column number is out of worksheet bounds. """ if options is None: options = {} # Ensure 2nd col is larger than first. if first_col > last_col: (first_col, last_col) = (last_col, first_col) # Don't modify the row dimensions when checking the columns. ignore_row = True # Set optional column values. hidden = options.get('hidden', False) collapsed = options.get('collapsed', False) level = options.get('level', 0) # Store the column dimension only in some conditions. if cell_format or (width and hidden): ignore_col = False else: ignore_col = True # Check that each column is valid and store the max and min values. if self._check_dimensions(0, last_col, ignore_row, ignore_col): return -1 if self._check_dimensions(0, first_col, ignore_row, ignore_col): return -1 # Set the limits for the outline levels (0 <= x <= 7). if level < 0: level = 0 if level > 7: level = 7 if level > self.outline_col_level: self.outline_col_level = level # Store the column data. Padded for sorting. self.colinfo["%05d" % first_col] = [first_col, last_col, width, cell_format, hidden, level, collapsed] # Store the column change to allow optimizations. self.col_size_changed = True # Store the col sizes for use when calculating image vertices taking # hidden columns into account. Also store the column formats. # Set width to zero if col is hidden if hidden: width = 0 for col in range(first_col, last_col + 1): self.col_sizes[col] = width if cell_format: self.col_formats[col] = cell_format return 0 def set_row(self, row, height=None, cell_format=None, options=None): """ Set the width, and other properties of a row. Args: row: Row number (zero-indexed). height: Row width. (optional). cell_format: Row cell_format. (optional). options: Dict of options such as hidden, level and collapsed. Returns: 0: Success. -1: Row number is out of worksheet bounds. """ if options is None: options = {} # Use minimum col in _check_dimensions(). if self.dim_colmin is not None: min_col = self.dim_colmin else: min_col = 0 # Check that row is valid. if self._check_dimensions(row, min_col): return -1 if height is None: height = self.default_row_height # Set optional row values. hidden = options.get('hidden', False) collapsed = options.get('collapsed', False) level = options.get('level', 0) # If the height is 0 the row is hidden and the height is the default. if height == 0: hidden = 1 height = self.default_row_height # Set the limits for the outline levels (0 <= x <= 7). if level < 0: level = 0 if level > 7: level = 7 if level > self.outline_row_level: self.outline_row_level = level # Store the row properties. self.set_rows[row] = [height, cell_format, hidden, level, collapsed] # Store the row change to allow optimizations. self.row_size_changed = True if hidden: height = 0 # Store the row sizes for use when calculating image vertices. self.row_sizes[row] = height def set_default_row(self, height=None, hide_unused_rows=False): """ Set the default row properties. Args: height: Default height. Optional, defaults to 15. hide_unused_rows: Hide unused rows. Optional, defaults to False. Returns: Nothing. """ if height is None: height = self.default_row_height if height != self.original_row_height: # Store the row change to allow optimizations. self.row_size_changed = True self.default_row_height = height if hide_unused_rows: self.default_row_zeroed = 1 @convert_range_args def merge_range(self, first_row, first_col, last_row, last_col, data, cell_format=None): """ Merge a range of cells. Args: first_row: The first row of the cell range. (zero indexed). first_col: The first column of the cell range. last_row: The last row of the cell range. (zero indexed). last_col: The last column of the cell range. data: Cell data. cell_format: Cell Format object. Returns: 0: Success. -1: Row or column is out of worksheet bounds. other: Return value of write(). """ # Merge a range of cells. The first cell should contain the data and # the others should be blank. All cells should have the same format. # Excel doesn't allow a single cell to be merged if first_row == last_row and first_col == last_col: warn("Can't merge single cell") return # Swap last row/col with first row/col as necessary if first_row > last_row: (first_row, last_row) = (last_row, first_row) if first_col > last_col: (first_col, last_col) = (last_col, first_col) # Check that column number is valid and store the max value if self._check_dimensions(last_row, last_col) == -1: return # Store the merge range. self.merge.append([first_row, first_col, last_row, last_col]) # Write the first cell self._write(first_row, first_col, data, cell_format) # Pad out the rest of the area with formatted blank cells. for row in range(first_row, last_row + 1): for col in range(first_col, last_col + 1): if row == first_row and col == first_col: continue self._write_blank(row, col, '', cell_format) @convert_range_args def autofilter(self, first_row, first_col, last_row, last_col): """ Set the autofilter area in the worksheet. Args: first_row: The first row of the cell range. (zero indexed). first_col: The first column of the cell range. last_row: The last row of the cell range. (zero indexed). last_col: The last column of the cell range. Returns: Nothing. """ # Reverse max and min values if necessary. if last_row < first_row: (first_row, last_row) = (last_row, first_row) if last_col < first_col: (first_col, last_col) = (last_col, first_col) # Build up the print area range "Sheet1!$A$1:$C$13". area = self._convert_name_area(first_row, first_col, last_row, last_col) ref = xl_range(first_row, first_col, last_row, last_col) self.autofilter_area = area self.autofilter_ref = ref self.filter_range = [first_col, last_col] def filter_column(self, col, criteria): """ Set the column filter criteria. Args: col: Filter column (zero-indexed). criteria: Filter criteria. Returns: Nothing. """ if not self.autofilter_area: warn("Must call autofilter() before filter_column()") return # Check for a column reference in A1 notation and substitute. try: int(col) except ValueError: # Convert col ref to a cell ref and then to a col number. col_letter = col (_, col) = xl_cell_to_rowcol(col + '1') if col >= self.xls_colmax: warn("Invalid column '%s'" % col_letter) return (col_first, col_last) = self.filter_range # Reject column if it is outside filter range. if col < col_first or col > col_last: warn("Column '%d' outside autofilter() column range (%d, %d)" % (col, col_first, col_last)) return tokens = self._extract_filter_tokens(criteria) if not (len(tokens) == 3 or len(tokens) == 7): warn("Incorrect number of tokens in criteria '%s'" % criteria) tokens = self._parse_filter_expression(criteria, tokens) # Excel handles single or double custom filters as default filters. # We need to check for them and handle them accordingly. if len(tokens) == 2 and tokens[0] == 2: # Single equality. self.filter_column_list(col, [tokens[1]]) elif (len(tokens) == 5 and tokens[0] == 2 and tokens[2] == 1 and tokens[3] == 2): # Double equality with "or" operator. self.filter_column_list(col, [tokens[1], tokens[4]]) else: # Non default custom filter. self.filter_cols[col] = tokens self.filter_type[col] = 0 self.filter_on = 1 def filter_column_list(self, col, filters): """ Set the column filter criteria in Excel 2007 list style. Args: col: Filter column (zero-indexed). filters: List of filter criteria to match. Returns: Nothing. """ if not self.autofilter_area: warn("Must call autofilter() before filter_column()") return # Check for a column reference in A1 notation and substitute. try: int(col) except ValueError: # Convert col ref to a cell ref and then to a col number. col_letter = col (_, col) = xl_cell_to_rowcol(col + '1') if col >= self.xls_colmax: warn("Invalid column '%s'" % col_letter) return (col_first, col_last) = self.filter_range # Reject column if it is outside filter range. if col < col_first or col > col_last: warn("Column '%d' outside autofilter() column range " "(%d,%d)" % (col, col_first, col_last)) return self.filter_cols[col] = filters self.filter_type[col] = 1 self.filter_on = 1 @convert_range_args def data_validation(self, first_row, first_col, last_row, last_col, options=None): """ Add a data validation to a worksheet. Args: first_row: The first row of the cell range. (zero indexed). first_col: The first column of the cell range. last_row: The last row of the cell range. (zero indexed). last_col: The last column of the cell range. options: Data validation options. Returns: 0: Success. -1: Row or column is out of worksheet bounds. -2: Incorrect parameter or option. """ # Check that row and col are valid without storing the values. if self._check_dimensions(first_row, first_col, True, True): return -1 if self._check_dimensions(last_row, last_col, True, True): return -1 if options is None: options = {} else: # Copy the user defined options so they aren't modified. options = options.copy() # Valid input parameters. valid_parameters = { 'validate': True, 'criteria': True, 'value': True, 'source': True, 'minimum': True, 'maximum': True, 'ignore_blank': True, 'dropdown': True, 'show_input': True, 'input_title': True, 'input_message': True, 'show_error': True, 'error_title': True, 'error_message': True, 'error_type': True, 'other_cells': True, } # Check for valid input parameters. for param_key in options.keys(): if param_key not in valid_parameters: warn("Unknown parameter '%s' in data_validation()" % param_key) return -2 # Map alternative parameter names 'source' or 'minimum' to 'value'. if 'source' in options: options['value'] = options['source'] if 'minimum' in options: options['value'] = options['minimum'] # 'validate' is a required parameter. if 'validate' not in options: warn("Parameter 'validate' is required in data_validation()") return -2 # List of valid validation types. valid_types = { 'any': 'none', 'any value': 'none', 'whole number': 'whole', 'whole': 'whole', 'integer': 'whole', 'decimal': 'decimal', 'list': 'list', 'date': 'date', 'time': 'time', 'text length': 'textLength', 'length': 'textLength', 'custom': 'custom', } # Check for valid validation types. if not options['validate'] in valid_types: warn("Unknown validation type '%s' for parameter " "'validate' in data_validation()" % options['validate']) return -2 else: options['validate'] = valid_types[options['validate']] # No action is required for validation type 'any' if there are no # input messages to display. if (options['validate'] == 'none' and options.get('input_title') is None and options.get('input_message') is None): return -2 # The any, list and custom validations don't have a criteria so we use # a default of 'between'. if (options['validate'] == 'none' or options['validate'] == 'list' or options['validate'] == 'custom'): options['criteria'] = 'between' options['maximum'] = None # 'criteria' is a required parameter. if 'criteria' not in options: warn("Parameter 'criteria' is required in data_validation()") return -2 # Valid criteria types. criteria_types = { 'between': 'between', 'not between': 'notBetween', 'equal to': 'equal', '=': 'equal', '==': 'equal', 'not equal to': 'notEqual', '!=': 'notEqual', '<>': 'notEqual', 'greater than': 'greaterThan', '>': 'greaterThan', 'less than': 'lessThan', '<': 'lessThan', 'greater than or equal to': 'greaterThanOrEqual', '>=': 'greaterThanOrEqual', 'less than or equal to': 'lessThanOrEqual', '<=': 'lessThanOrEqual', } # Check for valid criteria types. if not options['criteria'] in criteria_types: warn("Unknown criteria type '%s' for parameter " "'criteria' in data_validation()" % options['criteria']) return -2 else: options['criteria'] = criteria_types[options['criteria']] # 'Between' and 'Not between' criteria require 2 values. if (options['criteria'] == 'between' or options['criteria'] == 'notBetween'): if 'maximum' not in options: warn("Parameter 'maximum' is required in data_validation() " "when using 'between' or 'not between' criteria") return -2 else: options['maximum'] = None # Valid error dialog types. error_types = { 'stop': 0, 'warning': 1, 'information': 2, } # Check for valid error dialog types. if 'error_type' not in options: options['error_type'] = 0 elif not options['error_type'] in error_types: warn("Unknown criteria type '%s' for parameter 'error_type' " "in data_validation()" % options['error_type']) return -2 else: options['error_type'] = error_types[options['error_type']] # Convert date/times value if required. if options['validate'] == 'date' or options['validate'] == 'time': if options['value']: if supported_datetime(options['value']): date_time = self._convert_date_time(options['value']) # Format date number to the same precision as Excel. options['value'] = "%.16g" % date_time if options['maximum']: if supported_datetime(options['maximum']): date_time = self._convert_date_time(options['maximum']) options['maximum'] = "%.16g" % date_time # Check that the input title doesn't exceed the maximum length. if options.get('input_title') and len(options['input_title']) > 32: warn("Length of input title '%s' exceeds Excel's limit of 32" % force_unicode(options['input_title'])) return -2 # Check that the error title doesn't exceed the maximum length. if options.get('error_title') and len(options['error_title']) > 32: warn("Length of error title '%s' exceeds Excel's limit of 32" % force_unicode(options['error_title'])) return -2 # Check that the input message doesn't exceed the maximum length. if (options.get('input_message') and len(options['input_message']) > 255): warn("Length of input message '%s' exceeds Excel's limit of 255" % force_unicode(options['input_message'])) return -2 # Check that the error message doesn't exceed the maximum length. if (options.get('error_message') and len(options['error_message']) > 255): warn("Length of error message '%s' exceeds Excel's limit of 255" % force_unicode(options['error_message'])) return -2 # Check that the input list doesn't exceed the maximum length. if options['validate'] == 'list' and type(options['value']) is list: formula = self._csv_join(*options['value']) if len(formula) > 255: warn("Length of list items '%s' exceeds Excel's limit of " "255, use a formula range instead" % force_unicode(formula)) return -2 # Set some defaults if they haven't been defined by the user. if 'ignore_blank' not in options: options['ignore_blank'] = 1 if 'dropdown' not in options: options['dropdown'] = 1 if 'show_input' not in options: options['show_input'] = 1 if 'show_error' not in options: options['show_error'] = 1 # These are the cells to which the validation is applied. options['cells'] = [[first_row, first_col, last_row, last_col]] # A (for now) undocumented parameter to pass additional cell ranges. if 'other_cells' in options: options['cells'].extend(options['other_cells']) # Store the validation information until we close the worksheet. self.validations.append(options) @convert_range_args def conditional_format(self, first_row, first_col, last_row, last_col, options=None): """ Add a conditional format to a worksheet. Args: first_row: The first row of the cell range. (zero indexed). first_col: The first column of the cell range. last_row: The last row of the cell range. (zero indexed). last_col: The last column of the cell range. options: Conditional format options. Returns: 0: Success. -1: Row or column is out of worksheet bounds. -2: Incorrect parameter or option. """ # Check that row and col are valid without storing the values. if self._check_dimensions(first_row, first_col, True, True): return -1 if self._check_dimensions(last_row, last_col, True, True): return -1 if options is None: options = {} else: # Copy the user defined options so they aren't modified. options = options.copy() # Valid input parameters. valid_parameter = { 'type': True, 'format': True, 'criteria': True, 'value': True, 'minimum': True, 'maximum': True, 'stop_if_true': True, 'min_type': True, 'mid_type': True, 'max_type': True, 'min_value': True, 'mid_value': True, 'max_value': True, 'min_color': True, 'mid_color': True, 'max_color': True, 'min_length': True, 'max_length': True, 'multi_range': True, 'bar_color': True, 'bar_negative_color': True, 'bar_negative_color_same': True, 'bar_solid': True, 'bar_border_color': True, 'bar_negative_border_color': True, 'bar_negative_border_color_same': True, 'bar_no_border': True, 'bar_direction': True, 'bar_axis_position': True, 'bar_axis_color': True, 'bar_only': True, 'data_bar_2010': True, 'icon_style': True, 'reverse_icons': True, 'icons_only': True, 'icons': True} # Check for valid input parameters. for param_key in options.keys(): if param_key not in valid_parameter: warn("Unknown parameter '%s' in conditional_format()" % param_key) return -2 # 'type' is a required parameter. if 'type' not in options: warn("Parameter 'type' is required in conditional_format()") return -2 # Valid types. valid_type = { 'cell': 'cellIs', 'date': 'date', 'time': 'time', 'average': 'aboveAverage', 'duplicate': 'duplicateValues', 'unique': 'uniqueValues', 'top': 'top10', 'bottom': 'top10', 'text': 'text', 'time_period': 'timePeriod', 'blanks': 'containsBlanks', 'no_blanks': 'notContainsBlanks', 'errors': 'containsErrors', 'no_errors': 'notContainsErrors', '2_color_scale': '2_color_scale', '3_color_scale': '3_color_scale', 'data_bar': 'dataBar', 'formula': 'expression', 'icon_set': 'iconSet'} # Check for valid types. if options['type'] not in valid_type: warn("Unknown value '%s' for parameter 'type' " "in conditional_format()" % options['type']) return -2 else: if options['type'] == 'bottom': options['direction'] = 'bottom' options['type'] = valid_type[options['type']] # Valid criteria types. criteria_type = { 'between': 'between', 'not between': 'notBetween', 'equal to': 'equal', '=': 'equal', '==': 'equal', 'not equal to': 'notEqual', '!=': 'notEqual', '<>': 'notEqual', 'greater than': 'greaterThan', '>': 'greaterThan', 'less than': 'lessThan', '<': 'lessThan', 'greater than or equal to': 'greaterThanOrEqual', '>=': 'greaterThanOrEqual', 'less than or equal to': 'lessThanOrEqual', '<=': 'lessThanOrEqual', 'containing': 'containsText', 'not containing': 'notContains', 'begins with': 'beginsWith', 'ends with': 'endsWith', 'yesterday': 'yesterday', 'today': 'today', 'last 7 days': 'last7Days', 'last week': 'lastWeek', 'this week': 'thisWeek', 'next week': 'nextWeek', 'last month': 'lastMonth', 'this month': 'thisMonth', 'next month': 'nextMonth', # For legacy, but incorrect, support. 'continue week': 'nextWeek', 'continue month': 'nextMonth'} # Check for valid criteria types. if 'criteria' in options and options['criteria'] in criteria_type: options['criteria'] = criteria_type[options['criteria']] # Convert date/times value if required. if options['type'] == 'date' or options['type'] == 'time': options['type'] = 'cellIs' if 'value' in options: if not supported_datetime(options['value']): warn("Conditional format 'value' must be a " "datetime object.") return -2 else: date_time = self._convert_date_time(options['value']) # Format date number to the same precision as Excel. options['value'] = "%.16g" % date_time if 'minimum' in options: if not supported_datetime(options['minimum']): warn("Conditional format 'minimum' must be a " "datetime object.") return -2 else: date_time = self._convert_date_time(options['minimum']) options['minimum'] = "%.16g" % date_time if 'maximum' in options: if not supported_datetime(options['maximum']): warn("Conditional format 'maximum' must be a " "datetime object.") return -2 else: date_time = self._convert_date_time(options['maximum']) options['maximum'] = "%.16g" % date_time # Valid icon styles. valid_icons = { "3_arrows": "3Arrows", # 1 "3_flags": "3Flags", # 2 "3_traffic_lights_rimmed": "3TrafficLights2", # 3 "3_symbols_circled": "3Symbols", # 4 "4_arrows": "4Arrows", # 5 "4_red_to_black": "4RedToBlack", # 6 "4_traffic_lights": "4TrafficLights", # 7 "5_arrows_gray": "5ArrowsGray", # 8 "5_quarters": "5Quarters", # 9 "3_arrows_gray": "3ArrowsGray", # 10 "3_traffic_lights": "3TrafficLights", # 11 "3_signs": "3Signs", # 12 "3_symbols": "3Symbols2", # 13 "4_arrows_gray": "4ArrowsGray", # 14 "4_ratings": "4Rating", # 15 "5_arrows": "5Arrows", # 16 "5_ratings": "5Rating"} # 17 # Set the icon set properties. if options['type'] == 'iconSet': # An icon_set must have an icon style. if not options.get('icon_style'): warn("The 'icon_style' parameter must be specified when " "'type' == 'icon_set' in conditional_format()") return -3 # Check for valid icon styles. if options['icon_style'] not in valid_icons: warn("Unknown icon_style '%s' in conditional_format()" % options['icon_style']) return -2 else: options['icon_style'] = valid_icons[options['icon_style']] # Set the number of icons for the icon style. options['total_icons'] = 3 if options['icon_style'].startswith('4'): options['total_icons'] = 4 elif options['icon_style'].startswith('5'): options['total_icons'] = 5 options['icons'] = self._set_icon_props(options.get('total_icons'), options.get('icons')) # Swap last row/col for first row/col as necessary if first_row > last_row: first_row, last_row = last_row, first_row if first_col > last_col: first_col, last_col = last_col, first_col # Set the formatting range. # If the first and last cell are the same write a single cell. if first_row == last_row and first_col == last_col: cell_range = xl_rowcol_to_cell(first_row, first_col) start_cell = cell_range else: cell_range = xl_range(first_row, first_col, last_row, last_col) start_cell = xl_rowcol_to_cell(first_row, first_col) # Override with user defined multiple range if provided. if 'multi_range' in options: cell_range = options['multi_range'] cell_range = cell_range.replace('$', '') # Get the dxf format index. if 'format' in options and options['format']: options['format'] = options['format']._get_dxf_index() # Set the priority based on the order of adding. options['priority'] = self.dxf_priority self.dxf_priority += 1 # Check for 2010 style data_bar parameters. if (self.use_data_bars_2010 or options.get('data_bar_2010') or options.get('bar_solid') or options.get('bar_border_color') or options.get('bar_negative_color') or options.get('bar_negative_color_same') or options.get('bar_negative_border_color') or options.get('bar_negative_border_color_same') or options.get('bar_no_border') or options.get('bar_axis_position') or options.get('bar_axis_color') or options.get('bar_direction')): options['is_data_bar_2010'] = True # Special handling of text criteria. if options['type'] == 'text': if options['criteria'] == 'containsText': options['type'] = 'containsText' options['formula'] = ('NOT(ISERROR(SEARCH("%s",%s)))' % (options['value'], start_cell)) elif options['criteria'] == 'notContains': options['type'] = 'notContainsText' options['formula'] = ('ISERROR(SEARCH("%s",%s))' % (options['value'], start_cell)) elif options['criteria'] == 'beginsWith': options['type'] = 'beginsWith' options['formula'] = ('LEFT(%s,%d)="%s"' % (start_cell, len(options['value']), options['value'])) elif options['criteria'] == 'endsWith': options['type'] = 'endsWith' options['formula'] = ('RIGHT(%s,%d)="%s"' % (start_cell, len(options['value']), options['value'])) else: warn("Invalid text criteria '%s' " "in conditional_format()" % options['criteria']) # Special handling of time time_period criteria. if options['type'] == 'timePeriod': if options['criteria'] == 'yesterday': options['formula'] = 'FLOOR(%s,1)=TODAY()-1' % start_cell elif options['criteria'] == 'today': options['formula'] = 'FLOOR(%s,1)=TODAY()' % start_cell elif options['criteria'] == 'tomorrow': options['formula'] = 'FLOOR(%s,1)=TODAY()+1' % start_cell elif options['criteria'] == 'last7Days': options['formula'] = \ ('AND(TODAY()-FLOOR(%s,1)<=6,FLOOR(%s,1)<=TODAY())' % (start_cell, start_cell)) elif options['criteria'] == 'lastWeek': options['formula'] = \ ('AND(TODAY()-ROUNDDOWN(%s,0)>=(WEEKDAY(TODAY())),' 'TODAY()-ROUNDDOWN(%s,0)<(WEEKDAY(TODAY())+7))' % (start_cell, start_cell)) elif options['criteria'] == 'thisWeek': options['formula'] = \ ('AND(TODAY()-ROUNDDOWN(%s,0)<=WEEKDAY(TODAY())-1,' 'ROUNDDOWN(%s,0)-TODAY()<=7-WEEKDAY(TODAY()))' % (start_cell, start_cell)) elif options['criteria'] == 'nextWeek': options['formula'] = \ ('AND(ROUNDDOWN(%s,0)-TODAY()>(7-WEEKDAY(TODAY())),' 'ROUNDDOWN(%s,0)-TODAY()<(15-WEEKDAY(TODAY())))' % (start_cell, start_cell)) elif options['criteria'] == 'lastMonth': options['formula'] = \ ('AND(MONTH(%s)=MONTH(TODAY())-1,OR(YEAR(%s)=YEAR(' 'TODAY()),AND(MONTH(%s)=1,YEAR(A1)=YEAR(TODAY())-1)))' % (start_cell, start_cell, start_cell)) elif options['criteria'] == 'thisMonth': options['formula'] = \ ('AND(MONTH(%s)=MONTH(TODAY()),YEAR(%s)=YEAR(TODAY()))' % (start_cell, start_cell)) elif options['criteria'] == 'nextMonth': options['formula'] = \ ('AND(MONTH(%s)=MONTH(TODAY())+1,OR(YEAR(%s)=YEAR(' 'TODAY()),AND(MONTH(%s)=12,YEAR(%s)=YEAR(TODAY())+1)))' % (start_cell, start_cell, start_cell, start_cell)) else: warn("Invalid time_period criteria '%s' " "in conditional_format()" % options['criteria']) # Special handling of blanks/error types. if options['type'] == 'containsBlanks': options['formula'] = 'LEN(TRIM(%s))=0' % start_cell if options['type'] == 'notContainsBlanks': options['formula'] = 'LEN(TRIM(%s))>0' % start_cell if options['type'] == 'containsErrors': options['formula'] = 'ISERROR(%s)' % start_cell if options['type'] == 'notContainsErrors': options['formula'] = 'NOT(ISERROR(%s))' % start_cell # Special handling for 2 color scale. if options['type'] == '2_color_scale': options['type'] = 'colorScale' # Color scales don't use any additional formatting. options['format'] = None # Turn off 3 color parameters. options['mid_type'] = None options['mid_color'] = None options.setdefault('min_type', 'min') options.setdefault('max_type', 'max') options.setdefault('min_value', 0) options.setdefault('max_value', 0) options.setdefault('min_color', '#FF7128') options.setdefault('max_color', '#FFEF9C') options['min_color'] = xl_color(options['min_color']) options['max_color'] = xl_color(options['max_color']) # Special handling for 3 color scale. if options['type'] == '3_color_scale': options['type'] = 'colorScale' # Color scales don't use any additional formatting. options['format'] = None options.setdefault('min_type', 'min') options.setdefault('mid_type', 'percentile') options.setdefault('max_type', 'max') options.setdefault('min_value', 0) options.setdefault('max_value', 0) options.setdefault('min_color', '#F8696B') options.setdefault('mid_color', '#FFEB84') options.setdefault('max_color', '#63BE7B') options['min_color'] = xl_color(options['min_color']) options['mid_color'] = xl_color(options['mid_color']) options['max_color'] = xl_color(options['max_color']) # Set a default mid value. if 'mid_value' not in options: options['mid_value'] = 50 # Special handling for data bar. if options['type'] == 'dataBar': # Color scales don't use any additional formatting. options['format'] = None if not options.get('min_type'): options['min_type'] = 'min' options['x14_min_type'] = 'autoMin' else: options['x14_min_type'] = options['min_type'] if not options.get('max_type'): options['max_type'] = 'max' options['x14_max_type'] = 'autoMax' else: options['x14_max_type'] = options['max_type'] options.setdefault('min_value', 0) options.setdefault('max_value', 0) options.setdefault('bar_color', '#638EC6') options.setdefault('bar_border_color', options['bar_color']) options.setdefault('bar_only', False) options.setdefault('bar_no_border', False) options.setdefault('bar_solid', False) options.setdefault('bar_direction', '') options.setdefault('bar_negative_color', '#FF0000') options.setdefault('bar_negative_border_color', '#FF0000') options.setdefault('bar_negative_color_same', False) options.setdefault('bar_negative_border_color_same', False) options.setdefault('bar_axis_position', '') options.setdefault('bar_axis_color', '#000000') options['bar_color'] = xl_color(options['bar_color']) options['bar_border_color'] = xl_color(options['bar_border_color']) options['bar_axis_color'] = xl_color(options['bar_axis_color']) options['bar_negative_color'] = \ xl_color(options['bar_negative_color']) options['bar_negative_border_color'] = \ xl_color(options['bar_negative_border_color']) # Adjust for 2010 style data_bar parameters. if options.get('is_data_bar_2010'): self.excel_version = 2010 if options['min_type'] is 'min' and options['min_value'] == 0: options['min_value'] = None if options['max_type'] is 'max' and options['max_value'] == 0: options['max_value'] = None options['range'] = cell_range # Strip the leading = from formulas. try: options['min_value'] = options['min_value'].lstrip('=') except (KeyError, AttributeError): pass try: options['mid_value'] = options['mid_value'].lstrip('=') except (KeyError, AttributeError): pass try: options['max_value'] = options['max_value'].lstrip('=') except (KeyError, AttributeError): pass # Store the conditional format until we close the worksheet. if cell_range in self.cond_formats: self.cond_formats[cell_range].append(options) else: self.cond_formats[cell_range] = [options] @convert_range_args def add_table(self, first_row, first_col, last_row, last_col, options=None): """ Add an Excel table to a worksheet. Args: first_row: The first row of the cell range. (zero indexed). first_col: The first column of the cell range. last_row: The last row of the cell range. (zero indexed). last_col: The last column of the cell range. options: Table format options. (Optional) Returns: 0: Success. -1: Not supported in constant_memory mode. -2: Row or column is out of worksheet bounds. -3: Incorrect parameter or option. """ table = {} col_formats = {} if options is None: options = {} else: # Copy the user defined options so they aren't modified. options = options.copy() if self.constant_memory: warn("add_table() isn't supported in 'constant_memory' mode") return -1 # Check that row and col are valid without storing the values. if self._check_dimensions(first_row, first_col, True, True): return -2 if self._check_dimensions(last_row, last_col, True, True): return -2 # Valid input parameters. valid_parameter = { 'autofilter': True, 'banded_columns': True, 'banded_rows': True, 'columns': True, 'data': True, 'first_column': True, 'header_row': True, 'last_column': True, 'name': True, 'style': True, 'total_row': True, } # Check for valid input parameters. for param_key in options.keys(): if param_key not in valid_parameter: warn("Unknown parameter '%s' in add_table()" % param_key) return -3 # Turn on Excel's defaults. options['banded_rows'] = options.get('banded_rows', True) options['header_row'] = options.get('header_row', True) options['autofilter'] = options.get('autofilter', True) # Set the table options. table['show_first_col'] = options.get('first_column', False) table['show_last_col'] = options.get('last_column', False) table['show_row_stripes'] = options.get('banded_rows', False) table['show_col_stripes'] = options.get('banded_columns', False) table['header_row_count'] = options.get('header_row', 0) table['totals_row_shown'] = options.get('total_row', False) # Set the table name. if 'name' in options: name = options['name'] table['name'] = name if ' ' in name: warn("Name '%s' in add_table() cannot contain spaces" % force_unicode(name)) return -3 # Warn if the name contains invalid chars as defined by Excel. if (not re.match(r'^[\w\\][\w\\.]*$', name, re.UNICODE) or re.match(r'^\d', name)): warn("Invalid Excel characters in add_table(): '%s'" % force_unicode(name)) return -1 # Warn if the name looks like a cell name. if re.match(r'^[a-zA-Z][a-zA-Z]?[a-dA-D]?[0-9]+$', name): warn("Name looks like a cell name in add_table(): '%s'" % force_unicode(name)) return -1 # Warn if the name looks like a R1C1 cell reference. if (re.match(r'^[rcRC]$', name) or re.match(r'^[rcRC]\d+[rcRC]\d+$', name)): warn("Invalid name '%s' like a RC cell ref in add_table()" % force_unicode(name)) return -1 # Set the table style. if 'style' in options: table['style'] = options['style'] # Remove whitespace from style name. table['style'] = table['style'].replace(' ', '') else: table['style'] = "TableStyleMedium9" # Swap last row/col for first row/col as necessary. if first_row > last_row: (first_row, last_row) = (last_row, first_row) if first_col > last_col: (first_col, last_col) = (last_col, first_col) # Set the data range rows (without the header and footer). first_data_row = first_row last_data_row = last_row if options.get('header_row'): first_data_row += 1 if options.get('total_row'): last_data_row -= 1 # Set the table and autofilter ranges. table['range'] = xl_range(first_row, first_col, last_row, last_col) table['a_range'] = xl_range(first_row, first_col, last_data_row, last_col) # If the header row if off the default is to turn autofilter off. if not options['header_row']: options['autofilter'] = 0 # Set the autofilter range. if options['autofilter']: table['autofilter'] = table['a_range'] # Add the table columns. col_id = 1 table['columns'] = [] seen_names = {} for col_num in range(first_col, last_col + 1): # Set up the default column data. col_data = { 'id': col_id, 'name': 'Column' + str(col_id), 'total_string': '', 'total_function': '', 'total_value': 0, 'formula': '', 'format': None, 'name_format': None, } # Overwrite the defaults with any user defined values. if 'columns' in options: # Check if there are user defined values for this column. if col_id <= len(options['columns']): user_data = options['columns'][col_id - 1] else: user_data = None if user_data: # Get the column format. xformat = user_data.get('format', None) # Map user defined values to internal values. if user_data.get('header'): col_data['name'] = user_data['header'] # Excel requires unique case insensitive header names. header_name = col_data['name'] name = header_name.lower() if name in seen_names: warn("Duplicate header name in add_table(): '%s'" % force_unicode(name)) return -1 else: seen_names[name] = True col_data['name_format'] = user_data.get('header_format') # Handle the column formula. if 'formula' in user_data and user_data['formula']: formula = user_data['formula'] # Remove the formula '=' sign if it exists. if formula.startswith('='): formula = formula.lstrip('=') # Covert Excel 2010 "@" ref to 2007 "#This Row". formula = formula.replace('@', '[#This Row],') col_data['formula'] = formula for row in range(first_data_row, last_data_row + 1): self._write_formula(row, col_num, formula, xformat) # Handle the function for the total row. if user_data.get('total_function'): function = user_data['total_function'] # Massage the function name. function = function.lower() function = function.replace('_', '') function = function.replace(' ', '') if function == 'countnums': function = 'countNums' if function == 'stddev': function = 'stdDev' col_data['total_function'] = function formula = \ self._table_function_to_formula(function, col_data['name']) value = user_data.get('total_value', 0) self._write_formula(last_row, col_num, formula, xformat, value) elif user_data.get('total_string'): # Total label only (not a function). total_string = user_data['total_string'] col_data['total_string'] = total_string self._write_string(last_row, col_num, total_string, user_data.get('format')) # Get the dxf format index. if xformat is not None: col_data['format'] = xformat._get_dxf_index() # Store the column format for writing the cell data. # It doesn't matter if it is undefined. col_formats[col_id - 1] = xformat # Store the column data. table['columns'].append(col_data) # Write the column headers to the worksheet. if options['header_row']: self._write_string(first_row, col_num, col_data['name'], col_data['name_format']) col_id += 1 # Write the cell data if supplied. if 'data' in options: data = options['data'] i = 0 # For indexing the row data. for row in range(first_data_row, last_data_row + 1): j = 0 # For indexing the col data. for col in range(first_col, last_col + 1): if i < len(data) and j < len(data[i]): token = data[i][j] if j in col_formats: self._write(row, col, token, col_formats[j]) else: self._write(row, col, token, None) j += 1 i += 1 # Store the table data. self.tables.append(table) return table @convert_cell_args def add_sparkline(self, row, col, options=None): """ Add sparklines to the worksheet. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). options: Sparkline formatting options. Returns: 0: Success. -1: Row or column is out of worksheet bounds. -2: Incorrect parameter or option. """ # Check that row and col are valid without storing the values. if self._check_dimensions(row, col, True, True): return -1 sparkline = {'locations': [xl_rowcol_to_cell(row, col)]} if options is None: options = {} # Valid input parameters. valid_parameters = { 'location': True, 'range': True, 'type': True, 'high_point': True, 'low_point': True, 'negative_points': True, 'first_point': True, 'last_point': True, 'markers': True, 'style': True, 'series_color': True, 'negative_color': True, 'markers_color': True, 'first_color': True, 'last_color': True, 'high_color': True, 'low_color': True, 'max': True, 'min': True, 'axis': True, 'reverse': True, 'empty_cells': True, 'show_hidden': True, 'plot_hidden': True, 'date_axis': True, 'weight': True, } # Check for valid input parameters. for param_key in options.keys(): if param_key not in valid_parameters: warn("Unknown parameter '%s' in add_sparkline()" % param_key) return -1 # 'range' is a required parameter. if 'range' not in options: warn("Parameter 'range' is required in add_sparkline()") return -2 # Handle the sparkline type. spark_type = options.get('type', 'line') if spark_type not in ('line', 'column', 'win_loss'): warn("Parameter 'type' must be 'line', 'column' " "or 'win_loss' in add_sparkline()") return -2 if spark_type == 'win_loss': spark_type = 'stacked' sparkline['type'] = spark_type # We handle single location/range values or list of values. if 'location' in options: if type(options['location']) is list: sparkline['locations'] = options['location'] else: sparkline['locations'] = [options['location']] if type(options['range']) is list: sparkline['ranges'] = options['range'] else: sparkline['ranges'] = [options['range']] range_count = len(sparkline['ranges']) location_count = len(sparkline['locations']) # The ranges and locations must match. if range_count != location_count: warn("Must have the same number of location and range " "parameters in add_sparkline()") return -2 # Store the count. sparkline['count'] = len(sparkline['locations']) # Get the worksheet name for the range conversion below. sheetname = quote_sheetname(self.name) # Cleanup the input ranges. new_ranges = [] for spark_range in sparkline['ranges']: # Remove the absolute reference $ symbols. spark_range = spark_range.replace('$', '') # Remove the = from formula. spark_range = spark_range.lstrip('=') # Convert a simple range into a full Sheet1!A1:D1 range. if '!' not in spark_range: spark_range = sheetname + "!" + spark_range new_ranges.append(spark_range) sparkline['ranges'] = new_ranges # Cleanup the input locations. new_locations = [] for location in sparkline['locations']: location = location.replace('$', '') new_locations.append(location) sparkline['locations'] = new_locations # Map options. sparkline['high'] = options.get('high_point') sparkline['low'] = options.get('low_point') sparkline['negative'] = options.get('negative_points') sparkline['first'] = options.get('first_point') sparkline['last'] = options.get('last_point') sparkline['markers'] = options.get('markers') sparkline['min'] = options.get('min') sparkline['max'] = options.get('max') sparkline['axis'] = options.get('axis') sparkline['reverse'] = options.get('reverse') sparkline['hidden'] = options.get('show_hidden') sparkline['weight'] = options.get('weight') # Map empty cells options. empty = options.get('empty_cells', '') if empty == 'zero': sparkline['empty'] = 0 elif empty == 'connect': sparkline['empty'] = 'span' else: sparkline['empty'] = 'gap' # Map the date axis range. date_range = options.get('date_axis') if date_range and '!' not in date_range: date_range = sheetname + "!" + date_range sparkline['date_axis'] = date_range # Set the sparkline styles. style_id = options.get('style', 0) style = get_sparkline_style(style_id) sparkline['series_color'] = style['series'] sparkline['negative_color'] = style['negative'] sparkline['markers_color'] = style['markers'] sparkline['first_color'] = style['first'] sparkline['last_color'] = style['last'] sparkline['high_color'] = style['high'] sparkline['low_color'] = style['low'] # Override the style colors with user defined colors. self._set_spark_color(sparkline, options, 'series_color') self._set_spark_color(sparkline, options, 'negative_color') self._set_spark_color(sparkline, options, 'markers_color') self._set_spark_color(sparkline, options, 'first_color') self._set_spark_color(sparkline, options, 'last_color') self._set_spark_color(sparkline, options, 'high_color') self._set_spark_color(sparkline, options, 'low_color') self.sparklines.append(sparkline) @convert_range_args def set_selection(self, first_row, first_col, last_row, last_col): """ Set the selected cell or cells in a worksheet Args: first_row: The first row of the cell range. (zero indexed). first_col: The first column of the cell range. last_row: The last row of the cell range. (zero indexed). last_col: The last column of the cell range. Returns: 0: Nothing. """ pane = None # Range selection. Do this before swapping max/min to allow the # selection direction to be reversed. active_cell = xl_rowcol_to_cell(first_row, first_col) # Swap last row/col for first row/col if necessary if first_row > last_row: (first_row, last_row) = (last_row, first_row) if first_col > last_col: (first_col, last_col) = (last_col, first_col) # If the first and last cell are the same write a single cell. if (first_row == last_row) and (first_col == last_col): sqref = active_cell else: sqref = xl_range(first_row, first_col, last_row, last_col) # Selection isn't set for cell A1. if sqref == 'A1': return self.selections = [[pane, active_cell, sqref]] def outline_settings(self, visible=1, symbols_below=1, symbols_right=1, auto_style=0): """ Control outline settings. Args: visible: Outlines are visible. Optional, defaults to True. symbols_below: Show row outline symbols below the outline bar. Optional, defaults to True. symbols_right: Show column outline symbols to the right of the outline bar. Optional, defaults to True. auto_style: Use Automatic style. Optional, defaults to False. Returns: 0: Nothing. """ self.outline_on = visible self.outline_below = symbols_below self.outline_right = symbols_right self.outline_style = auto_style self.outline_changed = True @convert_cell_args def freeze_panes(self, row, col, top_row=None, left_col=None, pane_type=0): """ Create worksheet panes and mark them as frozen. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). top_row: Topmost visible row in scrolling region of pane. left_col: Leftmost visible row in scrolling region of pane. Returns: 0: Nothing. """ if top_row is None: top_row = row if left_col is None: left_col = col self.panes = [row, col, top_row, left_col, pane_type] @convert_cell_args def split_panes(self, x, y, top_row=None, left_col=None): """ Create worksheet panes and mark them as split. Args: x: The position for the vertical split. y: The position for the horizontal split. top_row: Topmost visible row in scrolling region of pane. left_col: Leftmost visible row in scrolling region of pane. Returns: 0: Nothing. """ # Same as freeze panes with a different pane type. self.freeze_panes(x, y, top_row, left_col, 2) def set_zoom(self, zoom=100): """ Set the worksheet zoom factor. Args: zoom: Scale factor: 10 <= zoom <= 400. Returns: Nothing. """ # Ensure the zoom scale is in Excel's range. if zoom < 10 or zoom > 400: warn("Zoom factor %d outside range: 10 <= zoom <= 400" % zoom) zoom = 100 self.zoom = int(zoom) def right_to_left(self): """ Display the worksheet right to left for some versions of Excel. Args: None. Returns: Nothing. """ self.is_right_to_left = 1 def hide_zero(self): """ Hide zero values in worksheet cells. Args: None. Returns: Nothing. """ self.show_zeros = 0 def set_tab_color(self, color): """ Set the color of the worksheet tab. Args: color: A #RGB color index. Returns: Nothing. """ self.tab_color = xl_color(color) 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. """ if password != '': password = self._encode_password(password) if not options: options = {} # Default values for objects that can be protected. defaults = { 'sheet': True, 'content': False, 'objects': False, 'scenarios': False, 'format_cells': False, 'format_columns': False, 'format_rows': False, 'insert_columns': False, 'insert_rows': False, 'insert_hyperlinks': False, 'delete_columns': False, 'delete_rows': False, 'select_locked_cells': True, 'sort': False, 'autofilter': False, 'pivot_tables': False, 'select_unlocked_cells': True} # Overwrite the defaults with user specified values. for key in (options.keys()): if key in defaults: defaults[key] = options[key] else: warn("Unknown protection object: '%s'" % key) # Set the password after the user defined values. defaults['password'] = password self.protect_options = defaults @convert_cell_args def insert_button(self, row, col, options=None): """ Insert a button form object into the worksheet. Args: row: The cell row (zero indexed). col: The cell column (zero indexed). options: Button formatting options. Returns: 0: Success. -1: Row or column is out of worksheet bounds. """ # Check insert (row, col) without storing. if self._check_dimensions(row, col, True, True): warn('Cannot insert button at (%d, %d).' % (row, col)) return -1 if options is None: options = {} button = self._button_params(row, col, options) self.buttons_list.append(button) self.has_vml = 1 ########################################################################### # # Public API. Page Setup methods. # ########################################################################### def set_landscape(self): """ Set the page orientation as landscape. Args: None. Returns: Nothing. """ self.orientation = 0 self.page_setup_changed = True def set_portrait(self): """ Set the page orientation as portrait. Args: None. Returns: Nothing. """ self.orientation = 1 self.page_setup_changed = True def set_page_view(self): """ Set the page view mode. Args: None. Returns: Nothing. """ self.page_view = 1 def set_paper(self, paper_size): """ Set the paper type. US Letter = 1, A4 = 9. Args: paper_size: Paper index. Returns: Nothing. """ if paper_size: self.paper_size = paper_size self.page_setup_changed = True def center_horizontally(self): """ Center the page horizontally. Args: None. Returns: Nothing. """ self.print_options_changed = True self.hcenter = 1 def center_vertically(self): """ Center the page vertically. Args: None. Returns: Nothing. """ self.print_options_changed = True self.vcenter = 1 def set_margins(self, left=0.7, right=0.7, top=0.75, bottom=0.75): """ Set all the page margins in inches. Args: left: Left margin. right: Right margin. top: Top margin. bottom: Bottom margin. Returns: Nothing. """ self.margin_left = left self.margin_right = right self.margin_top = top self.margin_bottom = bottom def set_header(self, header='', options=None, margin=None): """ Set the page header caption and optional margin. Args: header: Header string. margin: Header margin. options: Header options, mainly for images. Returns: Nothing. """ header_orig = header header = header.replace('&[Picture]', '&G') if len(header) >= 255: warn('Header string must be less than 255 characters') return if options is not None: # For backward compatibility allow options to be the margin. if not isinstance(options, dict): options = {'margin': options} else: options = {} # Copy the user defined options so they aren't modified. options = options.copy() # For backward compatibility. if margin is not None: options['margin'] = margin # Reset the list in case the function is called more than once. self.header_images = [] if options.get('image_left'): self.header_images.append([options.get('image_left'), options.get('image_data_left'), 'LH']) if options.get('image_center'): self.header_images.append([options.get('image_center'), options.get('image_data_center'), 'CH']) if options.get('image_right'): self.header_images.append([options.get('image_right'), options.get('image_data_right'), 'RH']) placeholder_count = header.count('&G') image_count = len(self.header_images) if placeholder_count != image_count: warn("Number of header images (%s) doesn't match placeholder " "count (%s) in string: %s" % (image_count, placeholder_count, header_orig)) self.header_images = [] return if 'align_with_margins' in options: self.header_footer_aligns = options['align_with_margins'] if 'scale_with_doc' in options: self.header_footer_scales = options['scale_with_doc'] self.header = header self.margin_header = options.get('margin', 0.3) self.header_footer_changed = True if image_count: self.has_header_vml = True def set_footer(self, footer='', options=None, margin=None): """ Set the page footer caption and optional margin. Args: footer: Footer string. margin: Footer margin. options: Footer options, mainly for images. Returns: Nothing. """ footer_orig = footer footer = footer.replace('&[Picture]', '&G') if len(footer) >= 255: warn('Footer string must be less than 255 characters') return if options is not None: # For backward compatibility allow options to be the margin. if not isinstance(options, dict): options = {'margin': options} else: options = {} # Copy the user defined options so they aren't modified. options = options.copy() # For backward compatibility. if margin is not None: options['margin'] = margin # Reset the list in case the function is called more than once. self.footer_images = [] if options.get('image_left'): self.footer_images.append([options.get('image_left'), options.get('image_data_left'), 'LF']) if options.get('image_center'): self.footer_images.append([options.get('image_center'), options.get('image_data_center'), 'CF']) if options.get('image_right'): self.footer_images.append([options.get('image_right'), options.get('image_data_right'), 'RF']) placeholder_count = footer.count('&G') image_count = len(self.footer_images) if placeholder_count != image_count: warn("Number of footer images (%s) doesn't match placeholder " "count (%s) in string: %s" % (image_count, placeholder_count, footer_orig)) self.footer_images = [] return if 'align_with_margins' in options: self.header_footer_aligns = options['align_with_margins'] if 'scale_with_doc' in options: self.header_footer_scales = options['scale_with_doc'] self.footer = footer self.margin_footer = options.get('margin', 0.3) self.header_footer_changed = True if image_count: self.has_header_vml = True def repeat_rows(self, first_row, last_row=None): """ Set the rows to repeat at the top of each printed page. Args: first_row: Start row for range. last_row: End row for range. Returns: Nothing. """ if last_row is None: last_row = first_row # Convert rows to 1 based. first_row += 1 last_row += 1 # Create the row range area like: $1:$2. area = '$%d:$%d' % (first_row, last_row) # Build up the print titles area "Sheet1!$1:$2" sheetname = quote_sheetname(self.name) self.repeat_row_range = sheetname + '!' + area @convert_column_args def repeat_columns(self, first_col, last_col=None): """ Set the columns to repeat at the left hand side of each printed page. Args: first_col: Start column for range. last_col: End column for range. Returns: Nothing. """ if last_col is None: last_col = first_col # Convert to A notation. first_col = xl_col_to_name(first_col, 1) last_col = xl_col_to_name(last_col, 1) # Create a column range like $C:$D. area = first_col + ':' + last_col # Build up the print area range "=Sheet2!$C:$D" sheetname = quote_sheetname(self.name) self.repeat_col_range = sheetname + "!" + area def hide_gridlines(self, option=1): """ Set the option to hide gridlines on the screen and the printed page. Args: option: 0 : Don't hide gridlines 1 : Hide printed gridlines only 2 : Hide screen and printed gridlines Returns: Nothing. """ if option == 0: self.print_gridlines = 1 self.screen_gridlines = 1 self.print_options_changed = True elif option == 1: self.print_gridlines = 0 self.screen_gridlines = 1 else: self.print_gridlines = 0 self.screen_gridlines = 0 def print_row_col_headers(self): """ Set the option to print the row and column headers on the printed page. Args: None. Returns: Nothing. """ self.print_headers = True self.print_options_changed = True def hide_row_col_headers(self): """ Set the option to hide the row and column headers on the worksheet. Args: None. Returns: Nothing. """ self.row_col_headers = True @convert_range_args def print_area(self, first_row, first_col, last_row, last_col): """ Set the print area in the current worksheet. Args: first_row: The first row of the cell range. (zero indexed). first_col: The first column of the cell range. last_row: The last row of the cell range. (zero indexed). last_col: The last column of the cell range. Returns: 0: Success. -1: Row or column is out of worksheet bounds. """ # Set the print area in the current worksheet. # Ignore max print area since it is the same as no area for Excel. if (first_row == 0 and first_col == 0 and last_row == self.xls_rowmax - 1 and last_col == self.xls_colmax - 1): return # Build up the print area range "Sheet1!$A$1:$C$13". area = self._convert_name_area(first_row, first_col, last_row, last_col) self.print_area_range = area def print_across(self): """ Set the order in which pages are printed. Args: None. Returns: Nothing. """ self.page_order = 1 self.page_setup_changed = True def fit_to_pages(self, width, height): """ Fit the printed area to a specific number of pages both vertically and horizontally. Args: width: Number of pages horizontally. height: Number of pages vertically. Returns: Nothing. """ self.fit_page = 1 self.fit_width = width self.fit_height = height self.page_setup_changed = True def set_start_page(self, start_page): """ Set the start page number when printing. Args: start_page: Start page number. Returns: Nothing. """ self.page_start = start_page def set_print_scale(self, scale): """ Set the scale factor for the printed page. Args: scale: Print scale. 10 <= scale <= 400. Returns: Nothing. """ # Confine the scale to Excel's range. if scale < 10 or scale > 400: warn("Print scale '%d' outside range: 10 <= scale <= 400" % scale) return # Turn off "fit to page" option when print scale is on. self.fit_page = 0 self.print_scale = int(scale) self.page_setup_changed = True def set_h_pagebreaks(self, breaks): """ Set the horizontal page breaks on a worksheet. Args: breaks: List of rows where the page breaks should be added. Returns: Nothing. """ self.hbreaks = breaks def set_v_pagebreaks(self, breaks): """ Set the horizontal page breaks on a worksheet. Args: breaks: List of columns where the page breaks should be added. Returns: Nothing. """ self.vbreaks = breaks def set_vba_name(self, name=None): """ Set the VBA name for the worksheet. By default this is the same as the sheet name: i.e., Sheet1 etc. Args: name: The VBA name for the worksheet. Returns: Nothing. """ if name is not None: self.vba_codename = name else: self.vba_codename = self.name ########################################################################### # # Private API. # ########################################################################### def _initialize(self, init_data): self.name = init_data['name'] self.index = init_data['index'] self.str_table = init_data['str_table'] self.worksheet_meta = init_data['worksheet_meta'] self.constant_memory = init_data['constant_memory'] self.tmpdir = init_data['tmpdir'] self.date_1904 = init_data['date_1904'] self.strings_to_numbers = init_data['strings_to_numbers'] self.strings_to_formulas = init_data['strings_to_formulas'] self.strings_to_urls = init_data['strings_to_urls'] self.nan_inf_to_errors = init_data['nan_inf_to_errors'] self.default_date_format = init_data['default_date_format'] self.default_url_format = init_data['default_url_format'] self.excel2003_style = init_data['excel2003_style'] self.remove_timezone = init_data['remove_timezone'] if self.excel2003_style: self.original_row_height = 12.75 self.default_row_height = 12.75 self.default_row_pixels = 17 self.margin_left = 0.75 self.margin_right = 0.75 self.margin_top = 1 self.margin_bottom = 1 self.margin_header = 0.5 self.margin_footer = 0.5 self.header_footer_aligns = False # Open a temp filehandle to store row data in constant_memory mode. if self.constant_memory: # This is sub-optimal but we need to create a temp file # with utf8 encoding in Python < 3. (fd, filename) = tempfile.mkstemp(dir=self.tmpdir) os.close(fd) self.row_data_filename = filename self.row_data_fh = codecs.open(filename, 'w+', 'utf-8') # Set as the worksheet filehandle until the file is assembled. self.fh = self.row_data_fh 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_worksheet() # Write the worksheet properties. self._write_sheet_pr() # Write the worksheet dimensions. self._write_dimension() # Write the sheet view properties. self._write_sheet_views() # Write the sheet format properties. self._write_sheet_format_pr() # Write the sheet column info. self._write_cols() # Write the worksheet data such as rows columns and cells. if not self.constant_memory: self._write_sheet_data() else: self._write_optimized_sheet_data() # Write the sheetProtection element. self._write_sheet_protection() # Write the phoneticPr element. if self.excel2003_style: self._write_phonetic_pr() # Write the autoFilter element. self._write_auto_filter() # Write the mergeCells element. self._write_merge_cells() # Write the conditional formats. self._write_conditional_formats() # Write the dataValidations element. self._write_data_validations() # Write the hyperlink element. self._write_hyperlinks() # 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 rowBreaks element. self._write_row_breaks() # Write the colBreaks element. self._write_col_breaks() # Write the drawing element. self._write_drawings() # Write the legacyDrawing element. self._write_legacy_drawing() # Write the legacyDrawingHF element. self._write_legacy_drawing_hf() # Write the tableParts element. self._write_table_parts() # Write the extLst elements. self._write_ext_list() # Close the worksheet tag. self._xml_end_tag('worksheet') # Close the file. self._xml_close() def _check_dimensions(self, row, col, ignore_row=False, ignore_col=False): # Check that row and col are valid and store the max and min # values for use in other methods/elements. The ignore_row / # ignore_col flags is used to indicate that we wish to perform # the dimension check without storing the value. The ignore # flags are use by set_row() and data_validate. # Check that the row/col are within the worksheet bounds. if row < 0 or col < 0: return -1 if row >= self.xls_rowmax or col >= self.xls_colmax: return -1 # In constant_memory mode we don't change dimensions for rows # that are already written. if not ignore_row and not ignore_col and self.constant_memory: if row < self.previous_row: return -2 if not ignore_row: if self.dim_rowmin is None or row < self.dim_rowmin: self.dim_rowmin = row if self.dim_rowmax is None or row > self.dim_rowmax: self.dim_rowmax = row if not ignore_col: if self.dim_colmin is None or col < self.dim_colmin: self.dim_colmin = col if self.dim_colmax is None or col > self.dim_colmax: self.dim_colmax = col return 0 def _convert_date_time(self, dt_obj): # Convert a datetime object to an Excel serial date and time. return datetime_to_excel_datetime(dt_obj, self.date_1904, self.remove_timezone) def _convert_name_area(self, row_num_1, col_num_1, row_num_2, col_num_2): # Convert zero indexed rows and columns to the format required by # worksheet named ranges, eg, "Sheet1!$A$1:$C$13". range1 = '' range2 = '' area = '' row_col_only = 0 # Convert to A1 notation. col_char_1 = xl_col_to_name(col_num_1, 1) col_char_2 = xl_col_to_name(col_num_2, 1) row_char_1 = '$' + str(row_num_1 + 1) row_char_2 = '$' + str(row_num_2 + 1) # We need to handle special cases that refer to rows or columns only. if row_num_1 == 0 and row_num_2 == self.xls_rowmax - 1: range1 = col_char_1 range2 = col_char_2 row_col_only = 1 elif col_num_1 == 0 and col_num_2 == self.xls_colmax - 1: range1 = row_char_1 range2 = row_char_2 row_col_only = 1 else: range1 = col_char_1 + row_char_1 range2 = col_char_2 + row_char_2 # A repeated range is only written once (if it isn't a special case). if range1 == range2 and not row_col_only: area = range1 else: area = range1 + ':' + range2 # Build up the print area range "Sheet1!$A$1:$C$13". sheetname = quote_sheetname(self.name) area = sheetname + "!" + area return area def _sort_pagebreaks(self, breaks): # This is an internal method used to filter elements of a list of # pagebreaks used in the _store_hbreak() and _store_vbreak() methods. # It: # 1. Removes duplicate entries from the list. # 2. Sorts the list. # 3. Removes 0 from the list if present. if not breaks: return breaks_set = set(breaks) if 0 in breaks_set: breaks_set.remove(0) breaks_list = list(breaks_set) breaks_list.sort() # The Excel 2007 specification says that the maximum number of page # breaks is 1026. However, in practice it is actually 1023. max_num_breaks = 1023 if len(breaks_list) > max_num_breaks: breaks_list = breaks_list[:max_num_breaks] return breaks_list def _extract_filter_tokens(self, expression): # Extract the tokens from the filter expression. The tokens are mainly # non-whitespace groups. The only tricky part is to extract string # tokens that contain whitespace and/or quoted double quotes (Excel's # escaped quotes). # # Examples: 'x < 2000' # 'x > 2000 and x < 5000' # 'x = "foo"' # 'x = "foo bar"' # 'x = "foo "" bar"' # if not expression: return [] token_re = re.compile(r'"(?:[^"]|"")*"|\S+') tokens = token_re.findall(expression) new_tokens = [] # Remove single leading and trailing quotes and un-escape other quotes. for token in tokens: if token.startswith('"'): token = token[1:] if token.endswith('"'): token = token[:-1] token = token.replace('""', '"') new_tokens.append(token) return new_tokens def _parse_filter_expression(self, expression, tokens): # Converts the tokens of a possibly conditional expression into 1 or 2 # sub expressions for further parsing. # # Examples: # ('x', '==', 2000) -> exp1 # ('x', '>', 2000, 'and', 'x', '<', 5000) -> exp1 and exp2 if len(tokens) == 7: # The number of tokens will be either 3 (for 1 expression) # or 7 (for 2 expressions). conditional = tokens[3] if re.match('(and|&&)', conditional): conditional = 0 elif re.match(r'(or|\|\|)', conditional): conditional = 1 else: warn("Token '%s' is not a valid conditional " "in filter expression '%s'" % (conditional, expression)) expression_1 = self._parse_filter_tokens(expression, tokens[0:3]) expression_2 = self._parse_filter_tokens(expression, tokens[4:7]) return expression_1 + [conditional] + expression_2 else: return self._parse_filter_tokens(expression, tokens) def _parse_filter_tokens(self, expression, tokens): # Parse the 3 tokens of a filter expression and return the operator # and token. The use of numbers instead of operators is a legacy of # Spreadsheet::WriteExcel. operators = { '==': 2, '=': 2, '=~': 2, 'eq': 2, '!=': 5, '!~': 5, 'ne': 5, '<>': 5, '<': 1, '<=': 3, '>': 4, '>=': 6, } operator = operators.get(tokens[1], None) token = tokens[2] # Special handling of "Top" filter expressions. if re.match('top|bottom', tokens[0].lower()): value = int(tokens[1]) if value < 1 or value > 500: warn("The value '%d' in expression '%s' " "must be in the range 1 to 500" % (value, expression)) token = token.lower() if token != 'items' and token != '%': warn("The type '%s' in expression '%s' " "must be either 'items' or '%'" % (token, expression)) if tokens[0].lower() == 'top': operator = 30 else: operator = 32 if tokens[2] == '%': operator += 1 token = str(value) if not operator and tokens[0]: warn("Token '%s' is not a valid operator " "in filter expression '%s'" % (token[0], expression)) # Special handling for Blanks/NonBlanks. if re.match('blanks|nonblanks', token.lower()): # Only allow Equals or NotEqual in this context. if operator != 2 and operator != 5: warn("The operator '%s' in expression '%s' " "is not valid in relation to Blanks/NonBlanks'" % (tokens[1], expression)) token = token.lower() # The operator should always be 2 (=) to flag a "simple" equality # in the binary record. Therefore we convert <> to =. if token == 'blanks': if operator == 5: token = ' ' else: if operator == 5: operator = 2 token = 'blanks' else: operator = 5 token = ' ' # if the string token contains an Excel match character then change the # operator type to indicate a non "simple" equality. if operator == 2 and re.search('[*?]', token): operator = 22 return [operator, token] def _encode_password(self, plaintext): # Encode the worksheet protection "password" as a simple hash. # Based on the algorithm by Daniel Rentz of OpenOffice. i = 0 count = len(plaintext) digits = [] for char in plaintext: i += 1 char = ord(char) << i low_15 = char & 0x7fff high_15 = char & 0x7fff << 15 high_15 >>= 15 char = low_15 | high_15 digits.append(char) password_hash = 0x0000 for digit in digits: password_hash ^= digit password_hash ^= count password_hash ^= 0xCE4B return "%X" % password_hash def _prepare_image(self, index, image_id, drawing_id, width, height, name, image_type, x_dpi, y_dpi): # Set up images/drawings. drawing_type = 2 (row, col, _, x_offset, y_offset, x_scale, y_scale, url, tip, anchor, _) = self.images[index] width *= x_scale height *= y_scale # Scale by non 96dpi resolutions. width *= 96.0 / x_dpi height *= 96.0 / y_dpi dimensions = self._position_object_emus(col, row, x_offset, y_offset, width, height) # Convert from pixels to emus. width = int(0.5 + (width * 9525)) height = int(0.5 + (height * 9525)) # Create a Drawing obj to use with worksheet unless one already exists. if not self.drawing: drawing = Drawing() drawing.embedded = 1 self.drawing = drawing self.external_drawing_links.append(['/drawing', '../drawings/drawing' + str(drawing_id) + '.xml', None]) else: drawing = self.drawing drawing_object = [drawing_type] drawing_object.extend(dimensions) drawing_object.extend([width, height, name, None, url, tip, anchor]) drawing._add_drawing_object(drawing_object) if url: rel_type = "/hyperlink" target_mode = "External" if re.match('(ftp|http)s?://', url): target = url if re.match('external:', url): target = url.replace('external:', '') if re.match("internal:", url): target = url.replace('internal:', '#') target_mode = None self.drawing_links.append([rel_type, target, target_mode]) self.drawing_links.append(['/image', '../media/image' + str(image_id) + '.' + image_type]) def _prepare_shape(self, index, drawing_id): # Set up shapes/drawings. drawing_type = 3 (row, col, x_offset, y_offset, x_scale, y_scale, text, options) = self.shapes[index] width = options.get('width', self.default_col_pixels * 3) height = options.get('height', self.default_row_pixels * 6) width *= x_scale height *= y_scale dimensions = self._position_object_emus(col, row, x_offset, y_offset, width, height) # Convert from pixels to emus. width = int(0.5 + (width * 9525)) height = int(0.5 + (height * 9525)) # Create a Drawing obj to use with worksheet unless one already exists. if not self.drawing: drawing = Drawing() drawing.embedded = 1 self.drawing = drawing self.external_drawing_links.append(['/drawing', '../drawings/drawing' + str(drawing_id) + '.xml', None]) else: drawing = self.drawing shape = Shape('rect', 'TextBox', options) shape.text = text drawing_object = [drawing_type] drawing_object.extend(dimensions) drawing_object.extend([width, height, None, shape, None, None, None]) drawing._add_drawing_object(drawing_object) def _prepare_header_image(self, image_id, width, height, name, image_type, position, x_dpi, y_dpi): # Set up an image without a drawing object for header/footer images. # Strip the extension from the filename. name = re.sub(r'\..*$', '', name) self.header_images_list.append([width, height, name, position, x_dpi, y_dpi]) self.vml_drawing_links.append(['/image', '../media/image' + str(image_id) + '.' + image_type]) def _prepare_chart(self, index, chart_id, drawing_id): # Set up chart/drawings. drawing_type = 1 (row, col, chart, x_offset, y_offset, x_scale, y_scale) = \ self.charts[index] chart.id = chart_id - 1 # Use user specified dimensions, if any. width = int(0.5 + (chart.width * x_scale)) height = int(0.5 + (chart.height * y_scale)) dimensions = self._position_object_emus(col, row, x_offset, y_offset, width, height) # Set the chart name for the embedded object if it has been specified. name = chart.chart_name # Create a Drawing obj to use with worksheet unless one already exists. if not self.drawing: drawing = Drawing() drawing.embedded = 1 self.drawing = drawing self.external_drawing_links.append(['/drawing', '../drawings/drawing' + str(drawing_id) + '.xml']) else: drawing = self.drawing drawing_object = [drawing_type] drawing_object.extend(dimensions) drawing_object.extend([width, height, name, None]) drawing._add_drawing_object(drawing_object) self.drawing_links.append(['/chart', '../charts/chart' + str(chart_id) + '.xml']) def _position_object_emus(self, col_start, row_start, x1, y1, width, height): # Calculate the vertices that define the position of a graphical # object within the worksheet in EMUs. # # The vertices are expressed as English Metric Units (EMUs). There are # 12,700 EMUs per point. Therefore, 12,700 * 3 /4 = 9,525 EMUs per # pixel (col_start, row_start, x1, y1, col_end, row_end, x2, y2, x_abs, y_abs) = \ self._position_object_pixels(col_start, row_start, x1, y1, width, height) # Convert the pixel values to EMUs. See above. x1 = int(0.5 + 9525 * x1) y1 = int(0.5 + 9525 * y1) x2 = int(0.5 + 9525 * x2) y2 = int(0.5 + 9525 * y2) x_abs = int(0.5 + 9525 * x_abs) y_abs = int(0.5 + 9525 * y_abs) return (col_start, row_start, x1, y1, col_end, row_end, x2, y2, x_abs, y_abs) # Calculate the vertices that define the position of a graphical object # within the worksheet in pixels. # # +------------+------------+ # | A | B | # +-----+------------+------------+ # | |(x1,y1) | | # | 1 |(A1)._______|______ | # | | | | | # | | | | | # +-----+----| OBJECT |-----+ # | | | | | # | 2 | |______________. | # | | | (B2)| # | | | (x2,y2)| # +---- +------------+------------+ # # Example of an object that covers some of the area from cell A1 to B2. # # Based on the width and height of the object we need to calculate 8 vars: # # col_start, row_start, col_end, row_end, x1, y1, x2, y2. # # We also calculate the absolute x and y position of the top left vertex of # the object. This is required for images. # # The width and height of the cells that the object occupies can be # variable and have to be taken into account. # # The values of col_start and row_start are passed in from the calling # function. The values of col_end and row_end are calculated by # subtracting the width and height of the object from the width and # height of the underlying cells. # def _position_object_pixels(self, col_start, row_start, x1, y1, width, height): # col_start # Col containing upper left corner of object. # x1 # Distance to left side of object. # # row_start # Row containing top left corner of object. # y1 # Distance to top of object. # # col_end # Col containing lower right corner of object. # x2 # Distance to right side of object. # # row_end # Row containing bottom right corner of object. # y2 # Distance to bottom of object. # # width # Width of object frame. # height # Height of object frame. # # x_abs # Absolute distance to left side of object. # y_abs # Absolute distance to top side of object. x_abs = 0 y_abs = 0 # Adjust start column for negative offsets. while x1 < 0 and col_start > 0: x1 += self._size_col(col_start - 1) col_start -= 1 # Adjust start row for negative offsets. while y1 < 0 and row_start > 0: y1 += self._size_row(row_start - 1) row_start -= 1 # Ensure that the image isn't shifted off the page at top left. if x1 < 0: x1 = 0 if y1 < 0: y1 = 0 # Calculate the absolute x offset of the top-left vertex. if self.col_size_changed: for col_id in range(col_start): x_abs += self._size_col(col_id) else: # Optimization for when the column widths haven't changed. x_abs += self.default_col_pixels * col_start x_abs += x1 # Calculate the absolute y offset of the top-left vertex. if self.row_size_changed: for row_id in range(row_start): y_abs += self._size_row(row_id) else: # Optimization for when the row heights haven't changed. y_abs += self.default_row_pixels * row_start y_abs += y1 # Adjust start column for offsets that are greater than the col width. while x1 >= self._size_col(col_start): x1 -= self._size_col(col_start) col_start += 1 # Adjust start row for offsets that are greater than the row height. while y1 >= self._size_row(row_start): y1 -= self._size_row(row_start) row_start += 1 # Initialize end cell to the same as the start cell. col_end = col_start row_end = row_start width = width + x1 height = height + y1 # Subtract the underlying cell widths to find end cell of the object. while width >= self._size_col(col_end): width -= self._size_col(col_end) col_end += 1 # Subtract the underlying cell heights to find end cell of the object. while height >= self._size_row(row_end): height -= self._size_row(row_end) row_end += 1 # The end vertices are whatever is left from the width and height. x2 = width y2 = height return ([col_start, row_start, x1, y1, col_end, row_end, x2, y2, x_abs, y_abs]) def _size_col(self, col): # Convert the width of a cell from user's units to pixels. Excel rounds # the column width to the nearest pixel. If the width hasn't been set # by the user we use the default value. If the column is hidden it # has a value of zero. max_digit_width = 7 # For Calabri 11. padding = 5 pixels = 0 # Look up the cell value to see if it has been changed. if col in self.col_sizes and self.col_sizes[col] is not None: width = self.col_sizes[col] # Convert to pixels. if width == 0: pixels = 0 elif width < 1: pixels = int(width * (max_digit_width + padding) + 0.5) else: pixels = int(width * max_digit_width + 0.5) + padding else: pixels = self.default_col_pixels return pixels def _size_row(self, row): # Convert the height of a cell from user's units to pixels. If the # height hasn't been set by the user we use the default value. If # the row is hidden it has a value of zero. pixels = 0 # Look up the cell value to see if it has been changed if row in self.row_sizes: height = self.row_sizes[row] if height == 0: pixels = 0 else: pixels = int(4.0 / 3.0 * height) else: pixels = int(4.0 / 3.0 * self.default_row_height) return pixels def _comment_params(self, row, col, string, options): # This method handles the additional optional parameters to # write_comment() as well as calculating the comment object # position and vertices. default_width = 128 default_height = 74 params = { 'author': None, 'color': '#ffffe1', 'start_cell': None, 'start_col': None, 'start_row': None, 'visible': None, 'width': default_width, 'height': default_height, 'x_offset': None, 'x_scale': 1, 'y_offset': None, 'y_scale': 1, 'font_name': 'Tahoma', 'font_size': 8, 'font_family': 2, } # Overwrite the defaults with any user supplied values. Incorrect or # misspelled parameters are silently ignored. for key in options.keys(): params[key] = options[key] # Ensure that a width and height have been set. if not params['width']: params['width'] = default_width if not params['height']: params['height'] = default_height # Set the comment background color. params['color'] = xl_color(params['color']).lower() # Convert from Excel XML style color to XML html style color. params['color'] = params['color'].replace('ff', '#', 1) # Convert a cell reference to a row and column. if params['start_cell'] is not None: (start_row, start_col) = xl_cell_to_rowcol(params['start_cell']) params['start_row'] = start_row params['start_col'] = start_col # Set the default start cell and offsets for the comment. These are # generally fixed in relation to the parent cell. However there are # some edge cases for cells at the, er, edges. row_max = self.xls_rowmax col_max = self.xls_colmax if params['start_row'] is None: if row == 0: params['start_row'] = 0 elif row == row_max - 3: params['start_row'] = row_max - 7 elif row == row_max - 2: params['start_row'] = row_max - 6 elif row == row_max - 1: params['start_row'] = row_max - 5 else: params['start_row'] = row - 1 if params['y_offset'] is None: if row == 0: params['y_offset'] = 2 elif row == row_max - 3: params['y_offset'] = 16 elif row == row_max - 2: params['y_offset'] = 16 elif row == row_max - 1: params['y_offset'] = 14 else: params['y_offset'] = 10 if params['start_col'] is None: if col == col_max - 3: params['start_col'] = col_max - 6 elif col == col_max - 2: params['start_col'] = col_max - 5 elif col == col_max - 1: params['start_col'] = col_max - 4 else: params['start_col'] = col + 1 if params['x_offset'] is None: if col == col_max - 3: params['x_offset'] = 49 elif col == col_max - 2: params['x_offset'] = 49 elif col == col_max - 1: params['x_offset'] = 49 else: params['x_offset'] = 15 # Scale the size of the comment box if required. if params['x_scale']: params['width'] = params['width'] * params['x_scale'] if params['y_scale']: params['height'] = params['height'] * params['y_scale'] # Round the dimensions to the nearest pixel. params['width'] = int(0.5 + params['width']) params['height'] = int(0.5 + params['height']) # Calculate the positions of the comment object. vertices = self._position_object_pixels( params['start_col'], params['start_row'], params['x_offset'], params['y_offset'], params['width'], params['height']) # Add the width and height for VML. vertices.append(params['width']) vertices.append(params['height']) return ([row, col, string, params['author'], params['visible'], params['color'], params['font_name'], params['font_size'], params['font_family']] + [vertices]) def _button_params(self, row, col, options): # This method handles the parameters passed to insert_button() as well # as calculating the comment object position and vertices. default_height = self.default_row_pixels default_width = self.default_col_pixels button_number = 1 + len(self.buttons_list) button = {'row': row, 'col': col, 'font': {}} params = {} # Overwrite the defaults with any user supplied values. Incorrect or # misspelled parameters are silently ignored. for key in options.keys(): params[key] = options[key] # Set the button caption. caption = params.get('caption') # Set a default caption if none was specified by user. if caption is None: caption = 'Button %d' % button_number button['font']['caption'] = caption # Set the macro name. if params.get('macro'): button['macro'] = '[0]!' + params['macro'] else: button['macro'] = '[0]!Button%d_Click' % button_number # Ensure that a width and height have been set. params['width'] = params.get('width', default_width) params['height'] = params.get('height', default_height) # Set the x/y offsets. params['x_offset'] = params.get('x_offset', 0) params['y_offset'] = params.get('y_offset', 0) # Scale the size of the button if required. params['width'] = params['width'] * params.get('x_scale', 1) params['height'] = params['height'] * params.get('y_scale', 1) # Round the dimensions to the nearest pixel. params['width'] = int(0.5 + params['width']) params['height'] = int(0.5 + params['height']) params['start_row'] = row params['start_col'] = col # Calculate the positions of the button object. vertices = self._position_object_pixels( params['start_col'], params['start_row'], params['x_offset'], params['y_offset'], params['width'], params['height']) # Add the width and height for VML. vertices.append(params['width']) vertices.append(params['height']) button['vertices'] = vertices return button def _prepare_vml_objects(self, vml_data_id, vml_shape_id, vml_drawing_id, comment_id): comments = [] # Sort the comments into row/column order for easier comparison # testing and set the external links for comments and buttons. row_nums = sorted(self.comments.keys()) for row in row_nums: col_nums = sorted(self.comments[row].keys()) for col in col_nums: # Set comment visibility if required and not user defined. if self.comments_visible: if self.comments[row][col][4] is None: self.comments[row][col][4] = 1 # Set comment author if not already user defined. if self.comments[row][col][3] is None: self.comments[row][col][3] = self.comments_author comments.append(self.comments[row][col]) self.external_vml_links.append(['/vmlDrawing', '../drawings/vmlDrawing' + str(vml_drawing_id) + '.vml']) if self.has_comments: self.comments_list = comments self.external_comment_links.append(['/comments', '../comments' + str(comment_id) + '.xml']) count = len(comments) start_data_id = vml_data_id # The VML o:idmap data id contains a comma separated range when there # is more than one 1024 block of comments, like this: data="1,2". for i in range(int(count / 1024)): vml_data_id = '%s,%d' % (vml_data_id, start_data_id + i + 1) self.vml_data_id = vml_data_id self.vml_shape_id = vml_shape_id return count def _prepare_header_vml_objects(self, vml_header_id, vml_drawing_id): # Set up external linkage for VML header/footer images. self.vml_header_id = vml_header_id self.external_vml_links.append(['/vmlDrawing', '../drawings/vmlDrawing' + str(vml_drawing_id) + '.vml']) def _prepare_tables(self, table_id, seen): # Set the table ids for the worksheet tables. for table in self.tables: table['id'] = table_id if table.get('name') is None: # Set a default name. table['name'] = 'Table' + str(table_id) # Check for duplicate table names. name = table['name'].lower() if name in seen: raise DuplicateTableName( "Duplicate name '%s' used in worksheet.add_table()." % table['name']) else: seen[name] = True # Store the link used for the rels file. self.external_table_links.append(['/table', '../tables/table' + str(table_id) + '.xml']) table_id += 1 def _table_function_to_formula(self, function, col_name): # Convert a table total function to a worksheet formula. formula = '' # Escape special characters, as required by Excel. col_name = re.sub(r"'", "''", col_name) col_name = re.sub(r"#", "'#", col_name) col_name = re.sub(r"]", "']", col_name) col_name = re.sub(r"\[", "'[", col_name) subtotals = { 'average': 101, 'countNums': 102, 'count': 103, 'max': 104, 'min': 105, 'stdDev': 107, 'sum': 109, 'var': 110, } if function in subtotals: func_num = subtotals[function] formula = "SUBTOTAL(%s,[%s])" % (func_num, col_name) else: warn("Unsupported function '%s' in add_table()" % function) return formula def _set_spark_color(self, sparkline, options, user_color): # Set the sparkline color. if user_color not in options: return sparkline[user_color] = {'rgb': xl_color(options[user_color])} def _get_range_data(self, row_start, col_start, row_end, col_end): # Returns a range of data from the worksheet _table to be used in # chart cached data. Strings are returned as SST ids and decoded # in the workbook. Return None for data that doesn't exist since # Excel can chart series with data missing. if self.constant_memory: return () data = [] # Iterate through the table data. for row_num in range(row_start, row_end + 1): # Store None if row doesn't exist. if row_num not in self.table: data.append(None) continue for col_num in range(col_start, col_end + 1): if col_num in self.table[row_num]: cell = self.table[row_num][col_num] type_cell_name = type(cell).__name__ if type_cell_name == 'Number': # Return a number with Excel's precision. data.append("%.16g" % cell.number) elif type_cell_name == 'String': # Return a string from it's shared string index. index = cell.string string = self.str_table._get_shared_string(index) data.append(string) elif (type_cell_name == 'Formula' or type_cell_name == 'ArrayFormula'): # Return the formula value. value = cell.value if value is None: value = 0 data.append(value) elif type_cell_name == 'Blank': # Return a empty cell. data.append('') else: # Store None if column doesn't exist. data.append(None) return data def _csv_join(self, *items): # Create a csv string for use with data validation formulas and lists. # Convert non string types to string. items = [str(item) if not isinstance(item, str_types) else item for item in items] return ','.join(items) def _escape_url(self, url): # Don't escape URL if it looks already escaped. if re.search('%[0-9a-fA-F]{2}', url): return url # Can't use url.quote() here because it doesn't match Excel. url = url.replace('%', '%25') url = url.replace('"', '%22') url = url.replace(' ', '%20') url = url.replace('<', '%3c') url = url.replace('>', '%3e') url = url.replace('[', '%5b') url = url.replace(']', '%5d') url = url.replace('^', '%5e') url = url.replace('`', '%60') url = url.replace('{', '%7b') url = url.replace('}', '%7d') return url ########################################################################### # # The following font methods are, more or less, duplicated from the # Styles class. Not the cleanest version of reuse but works for now. # ########################################################################### def _write_font(self, xf_format): # Write the element. xml_writer = self.rstring xml_writer._xml_start_tag('rPr') # Handle the main font properties. if xf_format.bold: xml_writer._xml_empty_tag('b') if xf_format.italic: xml_writer._xml_empty_tag('i') if xf_format.font_strikeout: xml_writer._xml_empty_tag('strike') if xf_format.font_outline: xml_writer._xml_empty_tag('outline') if xf_format.font_shadow: xml_writer._xml_empty_tag('shadow') # Handle the underline variants. if xf_format.underline: self._write_underline(xf_format.underline) # Handle super/subscript. if xf_format.font_script == 1: self._write_vert_align('superscript') if xf_format.font_script == 2: self._write_vert_align('subscript') # Write the font size xml_writer._xml_empty_tag('sz', [('val', xf_format.font_size)]) # Handle colors. if 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) self._write_rstring_color('rgb', color) else: self._write_rstring_color('theme', 1) # Write some other font properties related to font families. xml_writer._xml_empty_tag('rFont', [('val', xf_format.font_name)]) xml_writer._xml_empty_tag('family', [('val', xf_format.font_family)]) if xf_format.font_name == 'Calibri' and not xf_format.hyperlink: xml_writer._xml_empty_tag('scheme', [('val', xf_format.font_scheme)]) xml_writer._xml_end_tag('rPr') def _write_underline(self, underline): # Write the underline font element. attributes = [] # Handle the underline variants. if underline == 2: attributes = [('val', 'double')] elif underline == 33: attributes = [('val', 'singleAccounting')] elif underline == 34: attributes = [('val', 'doubleAccounting')] self.rstring._xml_empty_tag('u', attributes) def _write_vert_align(self, val): # Write the font sub-element. attributes = [('val', val)] self.rstring._xml_empty_tag('vertAlign', attributes) def _write_rstring_color(self, name, value): # Write the element. attributes = [(name, value)] self.rstring._xml_empty_tag('color', attributes) def _get_palette_color(self, color): # Convert the RGB color. if color[0] == '#': color = color[1:] return "FF" + color.upper() def _isnan(self, x): # Workaround for lack of math.isnan in Python 2.5/Jython. return x != x def _isinf(self, x): # Workaround for lack of math.isinf in Python 2.5/Jython. return (x - x) != 0 def _opt_close(self): # Close the row data filehandle in constant_memory mode. if not self.row_data_fh_closed: self.row_data_fh.close() self.row_data_fh_closed = True def _opt_reopen(self): # Reopen the row data filehandle in constant_memory mode. if self.row_data_fh_closed: filename = self.row_data_filename self.row_data_fh = codecs.open(filename, 'a+', 'utf-8') self.row_data_fh_closed = False self.fh = self.row_data_fh def _set_icon_props(self, total_icons, user_props=None): # Set the sub-properties for icons. props = [] # Set the defaults. for _ in range(total_icons): props.append({'criteria': False, 'value': 0, 'type': 'percent'}) # Set the default icon values based on the number of icons. if total_icons == 3: props[0]['value'] = 67 props[1]['value'] = 33 if total_icons == 4: props[0]['value'] = 75 props[1]['value'] = 50 props[2]['value'] = 25 if total_icons == 5: props[0]['value'] = 80 props[1]['value'] = 60 props[2]['value'] = 40 props[3]['value'] = 20 # Overwrite default properties with user defined properties. if user_props: # Ensure we don't set user properties for lowest icon. max_data = len(user_props) if max_data >= total_icons: max_data = total_icons - 1 for i in range(max_data): # Set the user defined 'value' property. if user_props[i].get('value') is not None: props[i]['value'] = user_props[i]['value'] # Remove the formula '=' sign if it exists. tmp = props[i]['value'] if isinstance(tmp, str_types) and tmp.startswith('='): props[i]['value'] = tmp.lstrip('=') # Set the user defined 'type' property. if user_props[i].get('type'): valid_types = ('percent', 'percentile', 'number', 'formula') if user_props[i]['type'] not in valid_types: warn("Unknown icon property type '%s' for sub-" "property 'type' in conditional_format()" % user_props[i]['type']) else: props[i]['type'] = user_props[i]['type'] if props[i]['type'] is 'number': props[i]['type'] = 'num' # Set the user defined 'criteria' property. criteria = user_props[i].get('criteria') if criteria and criteria == '>': props[i]['criteria'] = True return props ########################################################################### # # XML methods. # ########################################################################### def _write_worksheet(self): # Write the element. This is the root element. schema = 'http://schemas.openxmlformats.org/' xmlns = schema + 'spreadsheetml/2006/main' xmlns_r = schema + 'officeDocument/2006/relationships' xmlns_mc = schema + 'markup-compatibility/2006' ms_schema = 'http://schemas.microsoft.com/' xmlns_x14ac = ms_schema + 'office/spreadsheetml/2009/9/ac' attributes = [ ('xmlns', xmlns), ('xmlns:r', xmlns_r)] # Add some extra attributes for Excel 2010. Mainly for sparklines. if self.excel_version == 2010: attributes.append(('xmlns:mc', xmlns_mc)) attributes.append(('xmlns:x14ac', xmlns_x14ac)) attributes.append(('mc:Ignorable', 'x14ac')) self._xml_start_tag('worksheet', attributes) def _write_dimension(self): # Write the element. This specifies the range of # cells in the worksheet. As a special case, empty # spreadsheets use 'A1' as a range. if self.dim_rowmin is None and self.dim_colmin is None: # If the min dimensions are not defined then no dimensions # have been set and we use the default 'A1'. ref = 'A1' elif self.dim_rowmin is None and self.dim_colmin is not None: # If the row dimensions aren't set but the column # dimensions are set then they have been changed via # set_column(). if self.dim_colmin == self.dim_colmax: # The dimensions are a single cell and not a range. ref = xl_rowcol_to_cell(0, self.dim_colmin) else: # The dimensions are a cell range. cell_1 = xl_rowcol_to_cell(0, self.dim_colmin) cell_2 = xl_rowcol_to_cell(0, self.dim_colmax) ref = cell_1 + ':' + cell_2 elif (self.dim_rowmin == self.dim_rowmax and self.dim_colmin == self.dim_colmax): # The dimensions are a single cell and not a range. ref = xl_rowcol_to_cell(self.dim_rowmin, self.dim_colmin) else: # The dimensions are a cell range. cell_1 = xl_rowcol_to_cell(self.dim_rowmin, self.dim_colmin) cell_2 = xl_rowcol_to_cell(self.dim_rowmax, self.dim_colmax) ref = cell_1 + ':' + cell_2 self._xml_empty_tag('dimension', [('ref', ref)]) def _write_sheet_views(self): # Write the element. self._xml_start_tag('sheetViews') # Write the sheetView element. self._write_sheet_view() self._xml_end_tag('sheetViews') def _write_sheet_view(self): # Write the element. attributes = [] # Hide screen gridlines if required. if not self.screen_gridlines: attributes.append(('showGridLines', 0)) # Hide screen row/column headers. if self.row_col_headers: attributes.append(('showRowColHeaders', 0)) # Hide zeroes in cells. if not self.show_zeros: attributes.append(('showZeros', 0)) # Display worksheet right to left for Hebrew, Arabic and others. if self.is_right_to_left: attributes.append(('rightToLeft', 1)) # Show that the sheet tab is selected. if self.selected: attributes.append(('tabSelected', 1)) # Turn outlines off. Also required in the outlinePr element. if not self.outline_on: attributes.append(("showOutlineSymbols", 0)) # Set the page view/layout mode if required. if self.page_view: attributes.append(('view', 'pageLayout')) # Set the zoom level. if self.zoom != 100: if not self.page_view: attributes.append(('zoomScale', self.zoom)) if self.zoom_scale_normal: attributes.append(('zoomScaleNormal', self.zoom)) attributes.append(('workbookViewId', 0)) if self.panes or len(self.selections): self._xml_start_tag('sheetView', attributes) self._write_panes() self._write_selections() self._xml_end_tag('sheetView') else: self._xml_empty_tag('sheetView', attributes) def _write_sheet_format_pr(self): # Write the element. default_row_height = self.default_row_height row_level = self.outline_row_level col_level = self.outline_col_level attributes = [('defaultRowHeight', default_row_height)] if self.default_row_height != self.original_row_height: attributes.append(('customHeight', 1)) if self.default_row_zeroed: attributes.append(('zeroHeight', 1)) if row_level: attributes.append(('outlineLevelRow', row_level)) if col_level: attributes.append(('outlineLevelCol', col_level)) if self.excel_version == 2010: attributes.append(('x14ac:dyDescent', '0.25')) self._xml_empty_tag('sheetFormatPr', attributes) def _write_cols(self): # Write the element and
sub elements. # Exit unless some column have been formatted. if not self.colinfo: return self._xml_start_tag('cols') for col in sorted(self.colinfo.keys()): self._write_col_info(self.colinfo[col]) self._xml_end_tag('cols') def _write_col_info(self, col_info): # Write the element. (col_min, col_max, width, cell_format, hidden, level, collapsed) = col_info custom_width = 1 xf_index = 0 # Get the cell_format index. if cell_format: xf_index = cell_format._get_xf_index() # Set the Excel default column width. if width is None: if not hidden: width = 8.43 custom_width = 0 else: width = 0 elif width == 8.43: # Width is defined but same as default. custom_width = 0 # Convert column width from user units to character width. if width > 0: # For Calabri 11. max_digit_width = 7 padding = 5 if width < 1: width = int((int(width * (max_digit_width + padding) + 0.5)) / float(max_digit_width) * 256.0) / 256.0 else: width = int((int(width * max_digit_width + 0.5) + padding) / float(max_digit_width) * 256.0) / 256.0 attributes = [ ('min', col_min + 1), ('max', col_max + 1), ('width', "%.16g" % width)] if xf_index: attributes.append(('style', xf_index)) if hidden: attributes.append(('hidden', '1')) if custom_width: attributes.append(('customWidth', '1')) if level: attributes.append(('outlineLevel', level)) if collapsed: attributes.append(('collapsed', '1')) self._xml_empty_tag('col', attributes) def _write_sheet_data(self): # Write the element. if self.dim_rowmin is None: # If the dimensions aren't defined there is no data to write. self._xml_empty_tag('sheetData') else: self._xml_start_tag('sheetData') self._write_rows() self._xml_end_tag('sheetData') def _write_optimized_sheet_data(self): # Write the element when constant_memory is on. In this # case we read the data stored in the temp file and rewrite it to the # XML sheet file. if self.dim_rowmin is None: # If the dimensions aren't defined then there is no data to write. self._xml_empty_tag('sheetData') else: self._xml_start_tag('sheetData') # Rewind the filehandle that was used for temp row data. buff_size = 65536 self.row_data_fh.seek(0) data = self.row_data_fh.read(buff_size) while data: self.fh.write(data) data = self.row_data_fh.read(buff_size) self.row_data_fh.close() os.unlink(self.row_data_filename) self._xml_end_tag('sheetData') def _write_page_margins(self): # Write the element. attributes = [ ('left', self.margin_left), ('right', self.margin_right), ('top', self.margin_top), ('bottom', self.margin_bottom), ('header', self.margin_header), ('footer', self.margin_footer)] self._xml_empty_tag('pageMargins', attributes) def _write_page_setup(self): # Write the element. # # The following is an example taken from Excel. # # # attributes = [] # Skip this element if no page setup has changed. if not self.page_setup_changed: return # Set paper size. if self.paper_size: attributes.append(('paperSize', self.paper_size)) # Set the print_scale. if self.print_scale != 100: attributes.append(('scale', self.print_scale)) # Set the "Fit to page" properties. if self.fit_page and self.fit_width != 1: attributes.append(('fitToWidth', self.fit_width)) if self.fit_page and self.fit_height != 1: attributes.append(('fitToHeight', self.fit_height)) # Set the page print direction. if self.page_order: attributes.append(('pageOrder', "overThenDown")) # Set start page for printing. if self.page_start > 1: attributes.append(('firstPageNumber', self.page_start)) # Set page orientation. if self.orientation: attributes.append(('orientation', 'portrait')) else: attributes.append(('orientation', 'landscape')) # Set start page for printing. if self.page_start != 0: attributes.append(('useFirstPageNumber', '1')) # Set the DPI. Mainly only for testing. if self.is_chartsheet: if self.horizontal_dpi: attributes.append(('horizontalDpi', self.horizontal_dpi)) if self.vertical_dpi: attributes.append(('verticalDpi', self.vertical_dpi)) else: if self.vertical_dpi: attributes.append(('verticalDpi', self.vertical_dpi)) if self.horizontal_dpi: attributes.append(('horizontalDpi', self.horizontal_dpi)) self._xml_empty_tag('pageSetup', attributes) def _write_print_options(self): # Write the element. attributes = [] if not self.print_options_changed: return # Set horizontal centering. if self.hcenter: attributes.append(('horizontalCentered', 1)) # Set vertical centering. if self.vcenter: attributes.append(('verticalCentered', 1)) # Enable row and column headers. if self.print_headers: attributes.append(('headings', 1)) # Set printed gridlines. if self.print_gridlines: attributes.append(('gridLines', 1)) self._xml_empty_tag('printOptions', attributes) def _write_header_footer(self): # Write the element. attributes = [] if not self.header_footer_scales: attributes.append(('scaleWithDoc', 0)) if not self.header_footer_aligns: attributes.append(('alignWithMargins', 0)) if self.header_footer_changed: self._xml_start_tag('headerFooter', attributes) if self.header: self._write_odd_header() if self.footer: self._write_odd_footer() self._xml_end_tag('headerFooter') elif self.excel2003_style: self._xml_empty_tag('headerFooter', attributes) def _write_odd_header(self): # Write the element. self._xml_data_element('oddHeader', self.header) def _write_odd_footer(self): # Write the element. self._xml_data_element('oddFooter', self.footer) def _write_rows(self): # Write out the worksheet data as a series of rows and cells. self._calculate_spans() for row_num in range(self.dim_rowmin, self.dim_rowmax + 1): if (row_num in self.set_rows or row_num in self.comments or self.table[row_num]): # Only process rows with formatting, cell data and/or comments. span_index = int(row_num / 16) if span_index in self.row_spans: span = self.row_spans[span_index] else: span = None if self.table[row_num]: # Write the cells if the row contains data. if row_num not in self.set_rows: self._write_row(row_num, span) else: self._write_row(row_num, span, self.set_rows[row_num]) for col_num in range(self.dim_colmin, self.dim_colmax + 1): if col_num in self.table[row_num]: col_ref = self.table[row_num][col_num] self._write_cell(row_num, col_num, col_ref) self._xml_end_tag('row') elif row_num in self.comments: # Row with comments in cells. self._write_empty_row(row_num, span, self.set_rows[row_num]) else: # Blank row with attributes only. self._write_empty_row(row_num, span, self.set_rows[row_num]) def _write_single_row(self, current_row_num=0): # Write out the worksheet data as a single row with cells. # This method is used when constant_memory is on. A single # row is written and the data table is reset. That way only # one row of data is kept in memory at any one time. We don't # write span data in the optimized case since it is optional. # Set the new previous row as the current row. row_num = self.previous_row self.previous_row = current_row_num if (row_num in self.set_rows or row_num in self.comments or self.table[row_num]): # Only process rows with formatting, cell data and/or comments. # No span data in optimized mode. span = None if self.table[row_num]: # Write the cells if the row contains data. if row_num not in self.set_rows: self._write_row(row_num, span) else: self._write_row(row_num, span, self.set_rows[row_num]) for col_num in range(self.dim_colmin, self.dim_colmax + 1): if col_num in self.table[row_num]: col_ref = self.table[row_num][col_num] self._write_cell(row_num, col_num, col_ref) self._xml_end_tag('row') else: # Row attributes or comments only. self._write_empty_row(row_num, span, self.set_rows[row_num]) # Reset table. self.table.clear() def _calculate_spans(self): # Calculate the "spans" attribute of the tag. This is an # XLSX optimization and isn't strictly required. However, it # makes comparing files easier. The span is the same for each # block of 16 rows. spans = {} span_min = None span_max = None for row_num in range(self.dim_rowmin, self.dim_rowmax + 1): if row_num in self.table: # Calculate spans for cell data. for col_num in range(self.dim_colmin, self.dim_colmax + 1): if col_num in self.table[row_num]: if span_min is None: span_min = col_num span_max = col_num else: if col_num < span_min: span_min = col_num if col_num > span_max: span_max = col_num if row_num in self.comments: # Calculate spans for comments. for col_num in range(self.dim_colmin, self.dim_colmax + 1): if (row_num in self.comments and col_num in self.comments[row_num]): if span_min is None: span_min = col_num span_max = col_num else: if col_num < span_min: span_min = col_num if col_num > span_max: span_max = col_num if ((row_num + 1) % 16 == 0) or row_num == self.dim_rowmax: span_index = int(row_num / 16) if span_min is not None: span_min += 1 span_max += 1 spans[span_index] = "%s:%s" % (span_min, span_max) span_min = None self.row_spans = spans def _write_row(self, row, spans, properties=None, empty_row=False): # Write the element. xf_index = 0 if properties: height, cell_format, hidden, level, collapsed = properties else: height, cell_format, hidden, level, collapsed = None, None, 0, 0, 0 if height is None: height = self.default_row_height attributes = [('r', row + 1)] # Get the cell_format index. if cell_format: xf_index = cell_format._get_xf_index() # Add row attributes where applicable. if spans: attributes.append(('spans', spans)) if xf_index: attributes.append(('s', xf_index)) if cell_format: attributes.append(('customFormat', 1)) if height != self.original_row_height: attributes.append(('ht', height)) if hidden: attributes.append(('hidden', 1)) if height != self.original_row_height: attributes.append(('customHeight', 1)) if level: attributes.append(('outlineLevel', level)) if collapsed: attributes.append(('collapsed', 1)) if self.excel_version == 2010: attributes.append(('x14ac:dyDescent', '0.25')) if empty_row: self._xml_empty_tag_unencoded('row', attributes) else: self._xml_start_tag_unencoded('row', attributes) def _write_empty_row(self, row, spans, properties=None): # Write and empty element. self._write_row(row, spans, properties, empty_row=True) def _write_cell(self, row, col, cell): # Write the element. # Note. This is the innermost loop so efficiency is important. cell_range = xl_rowcol_to_cell_fast(row, col) attributes = [('r', cell_range)] if cell.format: # Add the cell format index. xf_index = cell.format._get_xf_index() attributes.append(('s', xf_index)) elif row in self.set_rows and self.set_rows[row][1]: # Add the row format. row_xf = self.set_rows[row][1] attributes.append(('s', row_xf._get_xf_index())) elif col in self.col_formats: # Add the column format. col_xf = self.col_formats[col] attributes.append(('s', col_xf._get_xf_index())) type_cell_name = type(cell).__name__ # Write the various cell types. if type_cell_name == 'Number': # Write a number. self._xml_number_element(cell.number, attributes) elif type_cell_name == 'String': # Write a string. string = cell.string if not self.constant_memory: # Write a shared string. self._xml_string_element(string, attributes) else: # Write an optimized in-line string. # Escape control characters. See SharedString.pm for details. string = re.sub('(_x[0-9a-fA-F]{4}_)', r'_x005F\1', string) string = re.sub(r'([\x00-\x08\x0B-\x1F])', lambda match: "_x%04X_" % ord(match.group(1)), string) # Escape non characters. if sys.version_info[0] == 2: non_char1 = unichr(0xFFFE) non_char2 = unichr(0xFFFF) else: non_char1 = "\uFFFE" non_char2 = "\uFFFF" string = re.sub(non_char1, '_xFFFE_', string) string = re.sub(non_char2, '_xFFFF_', string) # Write any rich strings without further tags. if re.search('^', string) and re.search('$', string): self._xml_rich_inline_string(string, attributes) else: # Add attribute to preserve leading or trailing whitespace. preserve = 0 if re.search(r'^\s', string) or re.search(r'\s$', string): preserve = 1 self._xml_inline_string(string, preserve, attributes) elif type_cell_name == 'Formula': # Write a formula. First check the formula value type. value = cell.value if type(cell.value) == bool: attributes.append(('t', 'b')) if cell.value: value = 1 else: value = 0 elif isinstance(cell.value, str_types): error_codes = ('#DIV/0!', '#N/A', '#NAME?', '#NULL!', '#NUM!', '#REF!', '#VALUE!') if cell.value in error_codes: attributes.append(('t', 'e')) else: attributes.append(('t', 'str')) self._xml_formula_element(cell.formula, value, attributes) elif type_cell_name == 'ArrayFormula': # Write a array formula. # First check if the formula value is a string. try: float(cell.value) except ValueError: attributes.append(('t', 'str')) # Write an array formula. self._xml_start_tag('c', attributes) self._write_cell_array_formula(cell.formula, cell.range) self._write_cell_value(cell.value) self._xml_end_tag('c') elif type_cell_name == 'Blank': # Write a empty cell. self._xml_empty_tag('c', attributes) elif type_cell_name == 'Boolean': # Write a boolean cell. attributes.append(('t', 'b')) self._xml_start_tag('c', attributes) self._write_cell_value(cell.boolean) self._xml_end_tag('c') def _write_cell_value(self, value): # Write the cell value element. if value is None: value = '' self._xml_data_element('v', value) def _write_cell_array_formula(self, formula, cell_range): # Write the cell array formula element. attributes = [ ('t', 'array'), ('ref', cell_range) ] self._xml_data_element('f', formula, attributes) def _write_sheet_pr(self): # Write the element for Sheet level properties. attributes = [] if (not self.fit_page and not self.filter_on and not self.tab_color and not self.outline_changed and not self.vba_codename): return if self.vba_codename: attributes.append(('codeName', self.vba_codename)) if self.filter_on: attributes.append(('filterMode', 1)) if (self.fit_page or self.tab_color or self.outline_changed): self._xml_start_tag('sheetPr', attributes) self._write_tab_color() self._write_outline_pr() self._write_page_set_up_pr() self._xml_end_tag('sheetPr') else: self._xml_empty_tag('sheetPr', attributes) def _write_page_set_up_pr(self): # Write the element. if not self.fit_page: return attributes = [('fitToPage', 1)] self._xml_empty_tag('pageSetUpPr', attributes) def _write_tab_color(self): # Write the element. color = self.tab_color if not color: return attributes = [('rgb', color)] self._xml_empty_tag('tabColor', attributes) def _write_outline_pr(self): # Write the element. attributes = [] if not self.outline_changed: return if self.outline_style: attributes.append(("applyStyles", 1)) if not self.outline_below: attributes.append(("summaryBelow", 0)) if not self.outline_right: attributes.append(("summaryRight", 0)) if not self.outline_on: attributes.append(("showOutlineSymbols", 0)) self._xml_empty_tag('outlinePr', attributes) def _write_row_breaks(self): # Write the element. page_breaks = self._sort_pagebreaks(self.hbreaks) if not page_breaks: return count = len(page_breaks) attributes = [ ('count', count), ('manualBreakCount', count), ] self._xml_start_tag('rowBreaks', attributes) for row_num in page_breaks: self._write_brk(row_num, 16383) self._xml_end_tag('rowBreaks') def _write_col_breaks(self): # Write the element. page_breaks = self._sort_pagebreaks(self.vbreaks) if not page_breaks: return count = len(page_breaks) attributes = [ ('count', count), ('manualBreakCount', count), ] self._xml_start_tag('colBreaks', attributes) for col_num in page_breaks: self._write_brk(col_num, 1048575) self._xml_end_tag('colBreaks') def _write_brk(self, brk_id, brk_max): # Write the element. attributes = [ ('id', brk_id), ('max', brk_max), ('man', 1)] self._xml_empty_tag('brk', attributes) def _write_merge_cells(self): # Write the element. merged_cells = self.merge count = len(merged_cells) if not count: return attributes = [('count', count)] self._xml_start_tag('mergeCells', attributes) for merged_range in merged_cells: # Write the mergeCell element. self._write_merge_cell(merged_range) self._xml_end_tag('mergeCells') def _write_merge_cell(self, merged_range): # Write the element. (row_min, col_min, row_max, col_max) = merged_range # Convert the merge dimensions to a cell range. cell_1 = xl_rowcol_to_cell(row_min, col_min) cell_2 = xl_rowcol_to_cell(row_max, col_max) ref = cell_1 + ':' + cell_2 attributes = [('ref', ref)] self._xml_empty_tag('mergeCell', attributes) def _write_hyperlinks(self): # Process any stored hyperlinks in row/col order and write the # element. The attributes are different for internal # and external links. hlink_refs = [] display = None # Sort the hyperlinks into row order. row_nums = sorted(self.hyperlinks.keys()) # Exit if there are no hyperlinks to process. if not row_nums: return # Iterate over the rows. for row_num in row_nums: # Sort the hyperlinks into column order. col_nums = sorted(self.hyperlinks[row_num].keys()) # Iterate over the columns. for col_num in col_nums: # Get the link data for this cell. link = self.hyperlinks[row_num][col_num] link_type = link["link_type"] # If the cell isn't a string then we have to add the url as # the string to display. if (self.table and self.table[row_num] and self.table[row_num][col_num]): cell = self.table[row_num][col_num] if type(cell).__name__ != 'String': display = link["url"] if link_type == 1: # External link with rel file relationship. self.rel_count += 1 hlink_refs.append([link_type, row_num, col_num, self.rel_count, link["str"], display, link["tip"]]) # Links for use by the packager. self.external_hyper_links.append(['/hyperlink', link["url"], 'External']) else: # Internal link with rel file relationship. hlink_refs.append([link_type, row_num, col_num, link["url"], link["str"], link["tip"]]) # Write the hyperlink elements. self._xml_start_tag('hyperlinks') for args in hlink_refs: link_type = args.pop(0) if link_type == 1: self._write_hyperlink_external(*args) elif link_type == 2: self._write_hyperlink_internal(*args) self._xml_end_tag('hyperlinks') def _write_hyperlink_external(self, row, col, id_num, location=None, display=None, tooltip=None): # Write the element for external links. ref = xl_rowcol_to_cell(row, col) r_id = 'rId' + str(id_num) attributes = [ ('ref', ref), ('r:id', r_id)] if location is not None: attributes.append(('location', location)) if display is not None: attributes.append(('display', display)) if tooltip is not None: attributes.append(('tooltip', tooltip)) self._xml_empty_tag('hyperlink', attributes) def _write_hyperlink_internal(self, row, col, location=None, display=None, tooltip=None): # Write the element for internal links. ref = xl_rowcol_to_cell(row, col) attributes = [ ('ref', ref), ('location', location)] if tooltip is not None: attributes.append(('tooltip', tooltip)) attributes.append(('display', display)) self._xml_empty_tag('hyperlink', attributes) def _write_auto_filter(self): # Write the element. if not self.autofilter_ref: return attributes = [('ref', self.autofilter_ref)] if self.filter_on: # Autofilter defined active filters. self._xml_start_tag('autoFilter', attributes) self._write_autofilters() self._xml_end_tag('autoFilter') else: # Autofilter defined without active filters. self._xml_empty_tag('autoFilter', attributes) def _write_autofilters(self): # Function to iterate through the columns that form part of an # autofilter range and write the appropriate filters. (col1, col2) = self.filter_range for col in range(col1, col2 + 1): # Skip if column doesn't have an active filter. if col not in self.filter_cols: continue # Retrieve the filter tokens and write the autofilter records. tokens = self.filter_cols[col] filter_type = self.filter_type[col] # Filters are relative to first column in the autofilter. self._write_filter_column(col - col1, filter_type, tokens) def _write_filter_column(self, col_id, filter_type, filters): # Write the element. attributes = [('colId', col_id)] self._xml_start_tag('filterColumn', attributes) if filter_type == 1: # Type == 1 is the new XLSX style filter. self._write_filters(filters) else: # Type == 0 is the classic "custom" filter. self._write_custom_filters(filters) self._xml_end_tag('filterColumn') def _write_filters(self, filters): # Write the element. non_blanks = [filter for filter in filters if str(filter).lower() != 'blanks'] attributes = [] if len(filters) != len(non_blanks): attributes = [('blank', 1)] if len(filters) == 1 and len(non_blanks) == 0: # Special case for blank cells only. self._xml_empty_tag('filters', attributes) else: # General case. self._xml_start_tag('filters', attributes) for autofilter in sorted(non_blanks): self._write_filter(autofilter) self._xml_end_tag('filters') def _write_filter(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('filter', attributes) def _write_custom_filters(self, tokens): # Write the element. if len(tokens) == 2: # One filter expression only. self._xml_start_tag('customFilters') self._write_custom_filter(*tokens) self._xml_end_tag('customFilters') else: # Two filter expressions. attributes = [] # Check if the "join" operand is "and" or "or". if tokens[2] == 0: attributes = [('and', 1)] else: attributes = [('and', 0)] # Write the two custom filters. self._xml_start_tag('customFilters', attributes) self._write_custom_filter(tokens[0], tokens[1]) self._write_custom_filter(tokens[3], tokens[4]) self._xml_end_tag('customFilters') def _write_custom_filter(self, operator, val): # Write the element. attributes = [] operators = { 1: 'lessThan', 2: 'equal', 3: 'lessThanOrEqual', 4: 'greaterThan', 5: 'notEqual', 6: 'greaterThanOrEqual', 22: 'equal', } # Convert the operator from a number to a descriptive string. if operators[operator] is not None: operator = operators[operator] else: warn("Unknown operator = %s" % operator) # The 'equal' operator is the default attribute and isn't stored. if not operator == 'equal': attributes.append(('operator', operator)) attributes.append(('val', val)) self._xml_empty_tag('customFilter', attributes) def _write_sheet_protection(self): # Write the element. attributes = [] if not self.protect_options: return options = self.protect_options if options['password']: attributes.append(('password', options['password'])) if options['sheet']: attributes.append(('sheet', 1)) if options['content']: attributes.append(('content', 1)) if not options['objects']: attributes.append(('objects', 1)) if not options['scenarios']: attributes.append(('scenarios', 1)) if options['format_cells']: attributes.append(('formatCells', 0)) if options['format_columns']: attributes.append(('formatColumns', 0)) if options['format_rows']: attributes.append(('formatRows', 0)) if options['insert_columns']: attributes.append(('insertColumns', 0)) if options['insert_rows']: attributes.append(('insertRows', 0)) if options['insert_hyperlinks']: attributes.append(('insertHyperlinks', 0)) if options['delete_columns']: attributes.append(('deleteColumns', 0)) if options['delete_rows']: attributes.append(('deleteRows', 0)) if not options['select_locked_cells']: attributes.append(('selectLockedCells', 1)) if options['sort']: attributes.append(('sort', 0)) if options['autofilter']: attributes.append(('autoFilter', 0)) if options['pivot_tables']: attributes.append(('pivotTables', 0)) if not options['select_unlocked_cells']: attributes.append(('selectUnlockedCells', 1)) self._xml_empty_tag('sheetProtection', attributes) def _write_drawings(self): # Write the elements. if not self.drawing: return self.rel_count += 1 self._write_drawing(self.rel_count) def _write_drawing(self, drawing_id): # Write the element. r_id = 'rId' + str(drawing_id) attributes = [('r:id', r_id)] self._xml_empty_tag('drawing', attributes) def _write_legacy_drawing(self): # Write the element. if not self.has_vml: return # Increment the relationship id for any drawings or comments. self.rel_count += 1 r_id = 'rId' + str(self.rel_count) attributes = [('r:id', r_id)] self._xml_empty_tag('legacyDrawing', attributes) def _write_legacy_drawing_hf(self): # Write the element. if not self.has_header_vml: return # Increment the relationship id for any drawings or comments. self.rel_count += 1 r_id = 'rId' + str(self.rel_count) attributes = [('r:id', r_id)] self._xml_empty_tag('legacyDrawingHF', attributes) def _write_data_validations(self): # Write the element. validations = self.validations count = len(validations) if not count: return attributes = [('count', count)] self._xml_start_tag('dataValidations', attributes) for validation in validations: # Write the dataValidation element. self._write_data_validation(validation) self._xml_end_tag('dataValidations') def _write_data_validation(self, options): # Write the element. sqref = '' attributes = [] # Set the cell range(s) for the data validation. for cells in options['cells']: # Add a space between multiple cell ranges. if sqref != '': sqref += ' ' (row_first, col_first, row_last, col_last) = cells # Swap last row/col for first row/col as necessary if row_first > row_last: (row_first, row_last) = (row_last, row_first) if col_first > col_last: (col_first, col_last) = (col_last, col_first) # If the first and last cell are the same write a single cell. if (row_first == row_last) and (col_first == col_last): sqref += xl_rowcol_to_cell(row_first, col_first) else: sqref += xl_range(row_first, col_first, row_last, col_last) if options['validate'] != 'none': attributes.append(('type', options['validate'])) if options['criteria'] != 'between': attributes.append(('operator', options['criteria'])) if 'error_type' in options: if options['error_type'] == 1: attributes.append(('errorStyle', 'warning')) if options['error_type'] == 2: attributes.append(('errorStyle', 'information')) if options['ignore_blank']: attributes.append(('allowBlank', 1)) if not options['dropdown']: attributes.append(('showDropDown', 1)) if options['show_input']: attributes.append(('showInputMessage', 1)) if options['show_error']: attributes.append(('showErrorMessage', 1)) if 'error_title' in options: attributes.append(('errorTitle', options['error_title'])) if 'error_message' in options: attributes.append(('error', options['error_message'])) if 'input_title' in options: attributes.append(('promptTitle', options['input_title'])) if 'input_message' in options: attributes.append(('prompt', options['input_message'])) attributes.append(('sqref', sqref)) if options['validate'] == 'none': self._xml_empty_tag('dataValidation', attributes) else: self._xml_start_tag('dataValidation', attributes) # Write the formula1 element. self._write_formula_1(options['value']) # Write the formula2 element. if options['maximum'] is not None: self._write_formula_2(options['maximum']) self._xml_end_tag('dataValidation') def _write_formula_1(self, formula): # Write the element. if type(formula) is list: formula = self._csv_join(*formula) formula = '"%s"' % formula else: # Check if the formula is a number. try: float(formula) except ValueError: # Not a number. Remove the formula '=' sign if it exists. if formula.startswith('='): formula = formula.lstrip('=') self._xml_data_element('formula1', formula) def _write_formula_2(self, formula): # Write the element. # Check if the formula is a number. try: float(formula) except ValueError: # Not a number. Remove the formula '=' sign if it exists. if formula.startswith('='): formula = formula.lstrip('=') self._xml_data_element('formula2', formula) def _write_conditional_formats(self): # Write the Worksheet conditional formats. ranges = sorted(self.cond_formats.keys()) if not ranges: return for cond_range in ranges: self._write_conditional_formatting(cond_range, self.cond_formats[cond_range]) def _write_conditional_formatting(self, cond_range, params): # Write the element. attributes = [('sqref', cond_range)] self._xml_start_tag('conditionalFormatting', attributes) for param in params: # Write the cfRule element. self._write_cf_rule(param) self._xml_end_tag('conditionalFormatting') def _write_cf_rule(self, params): # Write the element. attributes = [('type', params['type'])] if 'format' in params and params['format'] is not None: attributes.append(('dxfId', params['format'])) attributes.append(('priority', params['priority'])) if params.get('stop_if_true'): attributes.append(('stopIfTrue', 1)) if params['type'] == 'cellIs': attributes.append(('operator', params['criteria'])) self._xml_start_tag('cfRule', attributes) if 'minimum' in params and 'maximum' in params: self._write_formula_element(params['minimum']) self._write_formula_element(params['maximum']) else: self._write_formula_element(params['value']) self._xml_end_tag('cfRule') elif params['type'] == 'aboveAverage': if re.search('below', params['criteria']): attributes.append(('aboveAverage', 0)) if re.search('equal', params['criteria']): attributes.append(('equalAverage', 1)) if re.search('[123] std dev', params['criteria']): match = re.search('([123]) std dev', params['criteria']) attributes.append(('stdDev', match.group(1))) self._xml_empty_tag('cfRule', attributes) elif params['type'] == 'top10': if 'criteria' in params and params['criteria'] == '%': attributes.append(('percent', 1)) if 'direction' in params: attributes.append(('bottom', 1)) rank = params['value'] or 10 attributes.append(('rank', rank)) self._xml_empty_tag('cfRule', attributes) elif params['type'] == 'duplicateValues': self._xml_empty_tag('cfRule', attributes) elif params['type'] == 'uniqueValues': self._xml_empty_tag('cfRule', attributes) elif (params['type'] == 'containsText' or params['type'] == 'notContainsText' or params['type'] == 'beginsWith' or params['type'] == 'endsWith'): attributes.append(('operator', params['criteria'])) attributes.append(('text', params['value'])) self._xml_start_tag('cfRule', attributes) self._write_formula_element(params['formula']) self._xml_end_tag('cfRule') elif params['type'] == 'timePeriod': attributes.append(('timePeriod', params['criteria'])) self._xml_start_tag('cfRule', attributes) self._write_formula_element(params['formula']) self._xml_end_tag('cfRule') elif (params['type'] == 'containsBlanks' or params['type'] == 'notContainsBlanks' or params['type'] == 'containsErrors' or params['type'] == 'notContainsErrors'): self._xml_start_tag('cfRule', attributes) self._write_formula_element(params['formula']) self._xml_end_tag('cfRule') elif params['type'] == 'colorScale': self._xml_start_tag('cfRule', attributes) self._write_color_scale(params) self._xml_end_tag('cfRule') elif params['type'] == 'dataBar': self._xml_start_tag('cfRule', attributes) self._write_data_bar(params) if params.get('is_data_bar_2010'): self._write_data_bar_ext(params) self._xml_end_tag('cfRule') elif params['type'] == 'expression': self._xml_start_tag('cfRule', attributes) self._write_formula_element(params['criteria']) self._xml_end_tag('cfRule') elif params['type'] == 'iconSet': self._xml_start_tag('cfRule', attributes) self._write_icon_set(params) self._xml_end_tag('cfRule') def _write_formula_element(self, formula): # Write the element. # Check if the formula is a number. try: float(formula) except ValueError: # Not a number. Remove the formula '=' sign if it exists. if formula.startswith('='): formula = formula.lstrip('=') self._xml_data_element('formula', formula) def _write_color_scale(self, param): # Write the element. self._xml_start_tag('colorScale') self._write_cfvo(param['min_type'], param['min_value']) if param['mid_type'] is not None: self._write_cfvo(param['mid_type'], param['mid_value']) self._write_cfvo(param['max_type'], param['max_value']) self._write_color('rgb', param['min_color']) if param['mid_color'] is not None: self._write_color('rgb', param['mid_color']) self._write_color('rgb', param['max_color']) self._xml_end_tag('colorScale') def _write_data_bar(self, param): # Write the element. attributes = [] # Min and max bar lengths in in the spec but not supported directly by # Excel. if param.get('min_length'): attributes.append(('minLength', param['min_length'])) if param.get('max_length'): attributes.append(('maxLength', param['max_length'])) if param.get('bar_only'): attributes.append(('showValue', 0)) self._xml_start_tag('dataBar', attributes) self._write_cfvo(param['min_type'], param['min_value']) self._write_cfvo(param['max_type'], param['max_value']) self._write_color('rgb', param['bar_color']) self._xml_end_tag('dataBar') def _write_data_bar_ext(self, param): # Write the dataBar extension element. # Create a pseudo GUID for each unique Excel 2010 data bar. worksheet_count = self.index + 1 data_bar_count = len(self.data_bars_2010) + 1 guid = "{DA7ABA51-AAAA-BBBB-%04X-%012X}" % (worksheet_count, data_bar_count) # Store the 2010 data bar parameters to write the extLst elements. param['guid'] = guid self.data_bars_2010.append(param) self._xml_start_tag('extLst') self._write_ext('{B025F937-C7B1-47D3-B67F-A62EFF666E3E}') self._xml_data_element('x14:id', guid) self._xml_end_tag('ext') self._xml_end_tag('extLst') def _write_icon_set(self, param): # Write the element. attributes = [] # Don't set attribute for default style. if param['icon_style'] != '3TrafficLights': attributes = [('iconSet', param['icon_style'])] if param.get('icons_only'): attributes.append(('showValue', 0)) if param.get('reverse_icons'): attributes.append(('reverse', 1)) self._xml_start_tag('iconSet', attributes) # Write the properties for different icon styles. for icon in reversed(param['icons']): self._write_cfvo( icon['type'], icon['value'], icon['criteria']) self._xml_end_tag('iconSet') def _write_cfvo(self, cf_type, val, criteria=None): # Write the element. attributes = [('type', cf_type)] if val is not None: attributes.append(('val', val)) if criteria: attributes.append(('gte', 0)) self._xml_empty_tag('cfvo', attributes) def _write_color(self, name, value): # Write the element. attributes = [(name, value)] self._xml_empty_tag('color', attributes) def _write_selections(self): # Write the elements. for selection in self.selections: self._write_selection(*selection) def _write_selection(self, pane, active_cell, sqref): # Write the element. attributes = [] if pane: attributes.append(('pane', pane)) if active_cell: attributes.append(('activeCell', active_cell)) if sqref: attributes.append(('sqref', sqref)) self._xml_empty_tag('selection', attributes) def _write_panes(self): # Write the frozen or split elements. panes = self.panes if not len(panes): return if panes[4] == 2: self._write_split_panes(*panes) else: self._write_freeze_panes(*panes) def _write_freeze_panes(self, row, col, top_row, left_col, pane_type): # Write the element for freeze panes. attributes = [] y_split = row x_split = col top_left_cell = xl_rowcol_to_cell(top_row, left_col) active_pane = '' state = '' active_cell = '' sqref = '' # Move user cell selection to the panes. if self.selections: (_, active_cell, sqref) = self.selections[0] self.selections = [] # Set the active pane. if row and col: active_pane = 'bottomRight' row_cell = xl_rowcol_to_cell(row, 0) col_cell = xl_rowcol_to_cell(0, col) self.selections.append(['topRight', col_cell, col_cell]) self.selections.append(['bottomLeft', row_cell, row_cell]) self.selections.append(['bottomRight', active_cell, sqref]) elif col: active_pane = 'topRight' self.selections.append(['topRight', active_cell, sqref]) else: active_pane = 'bottomLeft' self.selections.append(['bottomLeft', active_cell, sqref]) # Set the pane type. if pane_type == 0: state = 'frozen' elif pane_type == 1: state = 'frozenSplit' else: state = 'split' if x_split: attributes.append(('xSplit', x_split)) if y_split: attributes.append(('ySplit', y_split)) attributes.append(('topLeftCell', top_left_cell)) attributes.append(('activePane', active_pane)) attributes.append(('state', state)) self._xml_empty_tag('pane', attributes) def _write_split_panes(self, row, col, top_row, left_col, pane_type): # Write the element for split panes. attributes = [] has_selection = 0 active_pane = '' active_cell = '' sqref = '' y_split = row x_split = col # Move user cell selection to the panes. if self.selections: (_, active_cell, sqref) = self.selections[0] self.selections = [] has_selection = 1 # Convert the row and col to 1/20 twip units with padding. if y_split: y_split = int(20 * y_split + 300) if x_split: x_split = self._calculate_x_split_width(x_split) # For non-explicit topLeft definitions, estimate the cell offset based # on the pixels dimensions. This is only a workaround and doesn't take # adjusted cell dimensions into account. if top_row == row and left_col == col: top_row = int(0.5 + (y_split - 300) / 20 / 15) left_col = int(0.5 + (x_split - 390) / 20 / 3 * 4 / 64) top_left_cell = xl_rowcol_to_cell(top_row, left_col) # If there is no selection set the active cell to the top left cell. if not has_selection: active_cell = top_left_cell sqref = top_left_cell # Set the Cell selections. if row and col: active_pane = 'bottomRight' row_cell = xl_rowcol_to_cell(top_row, 0) col_cell = xl_rowcol_to_cell(0, left_col) self.selections.append(['topRight', col_cell, col_cell]) self.selections.append(['bottomLeft', row_cell, row_cell]) self.selections.append(['bottomRight', active_cell, sqref]) elif col: active_pane = 'topRight' self.selections.append(['topRight', active_cell, sqref]) else: active_pane = 'bottomLeft' self.selections.append(['bottomLeft', active_cell, sqref]) # Format splits to the same precision as Excel. if x_split: attributes.append(('xSplit', "%.16g" % x_split)) if y_split: attributes.append(('ySplit', "%.16g" % y_split)) attributes.append(('topLeftCell', top_left_cell)) if has_selection: attributes.append(('activePane', active_pane)) self._xml_empty_tag('pane', attributes) def _calculate_x_split_width(self, width): # Convert column width from user units to pane split width. max_digit_width = 7 # For Calabri 11. padding = 5 # Convert to pixels. if width < 1: pixels = int(width * (max_digit_width + padding) + 0.5) else: pixels = int(width * max_digit_width + 0.5) + padding # Convert to points. points = pixels * 3 / 4 # Convert to twips (twentieths of a point). twips = points * 20 # Add offset/padding. width = twips + 390 return width def _write_table_parts(self): # Write the element. tables = self.tables count = len(tables) # Return if worksheet doesn't contain any tables. if not count: return attributes = [('count', count,)] self._xml_start_tag('tableParts', attributes) for _ in tables: # Write the tablePart element. self.rel_count += 1 self._write_table_part(self.rel_count) self._xml_end_tag('tableParts') def _write_table_part(self, r_id): # Write the element. r_id = 'rId' + str(r_id) attributes = [('r:id', r_id,)] self._xml_empty_tag('tablePart', attributes) def _write_ext_list(self): # Write the element for data bars and sparklines. has_data_bars = len(self.data_bars_2010) has_sparklines = len(self.sparklines) if not has_data_bars and not has_sparklines: return # Write the extLst element. self._xml_start_tag('extLst') if has_data_bars: self._write_ext_list_data_bars() if has_sparklines: self._write_ext_list_sparklines() self._xml_end_tag('extLst') def _write_ext_list_data_bars(self): # Write the Excel 2010 data_bar subelements. self._write_ext('{78C0D931-6437-407d-A8EE-F0AAD7539E65}') self._xml_start_tag('x14:conditionalFormattings') # Write the Excel 2010 conditional formatting data bar elements. for data_bar in self.data_bars_2010: # Write the x14:conditionalFormatting element. self._write_conditional_formatting_2010(data_bar) self._xml_end_tag('x14:conditionalFormattings') self._xml_end_tag('ext') def _write_conditional_formatting_2010(self, data_bar): # Write the element. xmlns_xm = 'http://schemas.microsoft.com/office/excel/2006/main' attributes = [('xmlns:xm', xmlns_xm)] self._xml_start_tag('x14:conditionalFormatting', attributes) # Write the x14:cfRule element. self._write_x14_cf_rule(data_bar) # Write the x14:dataBar element. self._write_x14_data_bar(data_bar) # Write the x14 max and min data bars. self._write_x14_cfvo(data_bar['x14_min_type'], data_bar['min_value']) self._write_x14_cfvo(data_bar['x14_max_type'], data_bar['max_value']) if not data_bar['bar_no_border']: # Write the x14:borderColor element. self._write_x14_border_color(data_bar['bar_border_color']) # Write the x14:negativeFillColor element. if not data_bar['bar_negative_color_same']: self._write_x14_negative_fill_color( data_bar['bar_negative_color']) # Write the x14:negativeBorderColor element. if (not data_bar['bar_no_border'] and not data_bar['bar_negative_border_color_same']): self._write_x14_negative_border_color( data_bar['bar_negative_border_color']) # Write the x14:axisColor element. if data_bar['bar_axis_position'] is not 'none': self._write_x14_axis_color(data_bar['bar_axis_color']) self._xml_end_tag('x14:dataBar') self._xml_end_tag('x14:cfRule') # Write the xm:sqref element. self._xml_data_element('xm:sqref', data_bar['range']) self._xml_end_tag('x14:conditionalFormatting') def _write_x14_cf_rule(self, data_bar): # Write the element. rule_type = 'dataBar' guid = data_bar['guid'] attributes = [('type', rule_type), ('id', guid)] self._xml_start_tag('x14:cfRule', attributes) def _write_x14_data_bar(self, data_bar): # Write the element. min_length = 0 max_length = 100 attributes = [ ('minLength', min_length), ('maxLength', max_length), ] if not data_bar['bar_no_border']: attributes.append(('border', 1)) if data_bar['bar_solid']: attributes.append(('gradient', 0)) if data_bar['bar_direction'] is 'left': attributes.append(('direction', 'leftToRight')) if data_bar['bar_direction'] is 'right': attributes.append(('direction', 'rightToLeft')) if data_bar['bar_negative_color_same']: attributes.append(('negativeBarColorSameAsPositive', 1)) if (not data_bar['bar_no_border'] and not data_bar['bar_negative_border_color_same']): attributes.append(('negativeBarBorderColorSameAsPositive', 0)) if data_bar['bar_axis_position'] is 'middle': attributes.append(('axisPosition', 'middle')) if data_bar['bar_axis_position'] is 'none': attributes.append(('axisPosition', 'none')) self._xml_start_tag('x14:dataBar', attributes) def _write_x14_cfvo(self, rule_type, value): # Write the element. attributes = [('type', rule_type)] if rule_type in ('min', 'max', 'autoMin', 'autoMax'): self._xml_empty_tag('x14:cfvo', attributes) else: self._xml_start_tag('x14:cfvo', attributes) self._xml_data_element('xm:f', value) self._xml_end_tag('x14:cfvo') def _write_x14_border_color(self, rgb): # Write the element. attributes = [('rgb', rgb)] self._xml_empty_tag('x14:borderColor', attributes) def _write_x14_negative_fill_color(self, rgb): # Write the element. attributes = [('rgb', rgb)] self._xml_empty_tag('x14:negativeFillColor', attributes) def _write_x14_negative_border_color(self, rgb): # Write the element. attributes = [('rgb', rgb)] self._xml_empty_tag('x14:negativeBorderColor', attributes) def _write_x14_axis_color(self, rgb): # Write the element. attributes = [('rgb', rgb)] self._xml_empty_tag('x14:axisColor', attributes) def _write_ext_list_sparklines(self): # Write the sparkline extension sub-elements. self._write_ext('{05C60535-1F16-4fd2-B633-F4F36F0B64E0}') # Write the x14:sparklineGroups element. self._write_sparkline_groups() # Write the sparkline elements. for sparkline in reversed(self.sparklines): # Write the x14:sparklineGroup element. self._write_sparkline_group(sparkline) # Write the x14:colorSeries element. self._write_color_series(sparkline['series_color']) # Write the x14:colorNegative element. self._write_color_negative(sparkline['negative_color']) # Write the x14:colorAxis element. self._write_color_axis() # Write the x14:colorMarkers element. self._write_color_markers(sparkline['markers_color']) # Write the x14:colorFirst element. self._write_color_first(sparkline['first_color']) # Write the x14:colorLast element. self._write_color_last(sparkline['last_color']) # Write the x14:colorHigh element. self._write_color_high(sparkline['high_color']) # Write the x14:colorLow element. self._write_color_low(sparkline['low_color']) if sparkline['date_axis']: self._xml_data_element('xm:f', sparkline['date_axis']) self._write_sparklines(sparkline) self._xml_end_tag('x14:sparklineGroup') self._xml_end_tag('x14:sparklineGroups') self._xml_end_tag('ext') def _write_sparklines(self, sparkline): # Write the element and sub-elements. # Write the sparkline elements. self._xml_start_tag('x14:sparklines') for i in range(sparkline['count']): spark_range = sparkline['ranges'][i] location = sparkline['locations'][i] self._xml_start_tag('x14:sparkline') self._xml_data_element('xm:f', spark_range) self._xml_data_element('xm:sqref', location) self._xml_end_tag('x14:sparkline') self._xml_end_tag('x14:sparklines') def _write_ext(self, uri): # Write the element. schema = 'http://schemas.microsoft.com/office/' xmlns_x14 = schema + 'spreadsheetml/2009/9/main' attributes = [ ('xmlns:x14', xmlns_x14), ('uri', uri), ] self._xml_start_tag('ext', attributes) def _write_sparkline_groups(self): # Write the element. xmlns_xm = 'http://schemas.microsoft.com/office/excel/2006/main' attributes = [('xmlns:xm', xmlns_xm)] self._xml_start_tag('x14:sparklineGroups', attributes) def _write_sparkline_group(self, options): # Write the element. # # Example for order. # # # empty = options.get('empty') attributes = [] if options.get('max') is not None: if options['max'] == 'group': options['cust_max'] = 'group' else: attributes.append(('manualMax', options['max'])) options['cust_max'] = 'custom' if options.get('min') is not None: if options['min'] == 'group': options['cust_min'] = 'group' else: attributes.append(('manualMin', options['min'])) options['cust_min'] = 'custom' # Ignore the default type attribute (line). if options['type'] != 'line': attributes.append(('type', options['type'])) if options.get('weight'): attributes.append(('lineWeight', options['weight'])) if options.get('date_axis'): attributes.append(('dateAxis', 1)) if empty: attributes.append(('displayEmptyCellsAs', empty)) if options.get('markers'): attributes.append(('markers', 1)) if options.get('high'): attributes.append(('high', 1)) if options.get('low'): attributes.append(('low', 1)) if options.get('first'): attributes.append(('first', 1)) if options.get('last'): attributes.append(('last', 1)) if options.get('negative'): attributes.append(('negative', 1)) if options.get('axis'): attributes.append(('displayXAxis', 1)) if options.get('hidden'): attributes.append(('displayHidden', 1)) if options.get('cust_min'): attributes.append(('minAxisType', options['cust_min'])) if options.get('cust_max'): attributes.append(('maxAxisType', options['cust_max'])) if options.get('reverse'): attributes.append(('rightToLeft', 1)) self._xml_start_tag('x14:sparklineGroup', attributes) def _write_spark_color(self, element, color): # Helper function for the sparkline color functions below. attributes = [] if color.get('rgb'): attributes.append(('rgb', color['rgb'])) if color.get('theme'): attributes.append(('theme', color['theme'])) if color.get('tint'): attributes.append(('tint', color['tint'])) self._xml_empty_tag(element, attributes) def _write_color_series(self, color): # Write the element. self._write_spark_color('x14:colorSeries', color) def _write_color_negative(self, color): # Write the element. self._write_spark_color('x14:colorNegative', color) def _write_color_axis(self): # Write the element. self._write_spark_color('x14:colorAxis', {'rgb': 'FF000000'}) def _write_color_markers(self, color): # Write the element. self._write_spark_color('x14:colorMarkers', color) def _write_color_first(self, color): # Write the element. self._write_spark_color('x14:colorFirst', color) def _write_color_last(self, color): # Write the element. self._write_spark_color('x14:colorLast', color) def _write_color_high(self, color): # Write the element. self._write_spark_color('x14:colorHigh', color) def _write_color_low(self, color): # Write the element. self._write_spark_color('x14:colorLow', color) def _write_phonetic_pr(self): # Write the element. attributes = [ ('fontId', '0'), ('type', 'noConversion'), ] self._xml_empty_tag('phoneticPr', attributes) XlsxWriter-1.1.2/xlsxwriter/xmlwriter.py0000644000076500000240000001540013335051513020530 0ustar Johnstaff00000000000000############################################################################### # # XMLwriter - A base class for XlsxWriter classes. # # Used in conjunction with XlsxWriter. # # Copyright 2013-2018, John McNamara, jmcnamara@cpan.org # # Standard packages. import re import codecs # Standard packages in Python 2/3 compatibility mode. from .compatibility 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 = codecs.open(filename, 'w', '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( """\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("" % 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" % (tag, data, end_tag)) def _xml_string_element(self, index, attributes=[]): # Optimized tag writer for 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("""%d""" % (attr, index)) def _xml_si_element(self, string, attributes=[]): # Optimized tag writer for shared strings 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("""%s""" % (attr, string)) def _xml_rich_si_element(self, string): # Optimized tag writer for shared strings rich string elements. self.fh.write("""%s""" % string) def _xml_number_element(self, number, attributes=[]): # Optimized tag writer for 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("""%.16g""" % (attr, number)) def _xml_formula_element(self, formula, result, attributes=[]): # Optimized tag writer for 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("""%s%s""" % (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 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("""%s""" % (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("""%s""" % (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 = re.sub('[&]', '&', attribute) attribute = re.sub('["]', '"', attribute) attribute = re.sub('[<]', '<', attribute) attribute = re.sub('[>]', '>', attribute) attribute = re.sub('[\n]', ' ', attribute) 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 = re.sub('[&]', '&', data) data = re.sub('[<]', '<', data) data = re.sub('[>]', '>', data) return data XlsxWriter-1.1.2/XlsxWriter.egg-info/0000755000076500000240000000000013362632140017514 5ustar Johnstaff00000000000000XlsxWriter-1.1.2/XlsxWriter.egg-info/dependency_links.txt0000644000076500000240000000000113362632140023562 0ustar Johnstaff00000000000000 XlsxWriter-1.1.2/XlsxWriter.egg-info/PKG-INFO0000644000076500000240000000603513362632140020615 0ustar Johnstaff00000000000000Metadata-Version: 1.1 Name: XlsxWriter Version: 1.1.2 Summary: A Python module for creating Excel XLSX files. Home-page: https://github.com/jmcnamara/XlsxWriter Author: John McNamara Author-email: jmcnamara@cpan.org License: BSD Description: XlsxWriter ========== **XlsxWriter** is a Python module for writing files in the Excel 2007+ XLSX file format. XlsxWriter can be used to write text, numbers, formulas and hyperlinks to multiple worksheets and it supports features such as formatting and many more, including: * 100% compatible Excel XLSX files. * Full formatting. * Merged cells. * Defined names. * Charts. * Autofilters. * Data validation and drop down lists. * Conditional formatting. * Worksheet PNG/JPEG/BMP/WMF/EMF images. * Rich multi-format strings. * Cell comments. * Integration with Pandas. * Textboxes. * Memory optimization mode for writing large files. It supports Python 2.5, 2.6, 2.7, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, Jython and PyPy and uses standard libraries only. Here is a simple example: .. code-block:: python import xlsxwriter # Create an new Excel file and add a worksheet. workbook = xlsxwriter.Workbook('demo.xlsx') worksheet = workbook.add_worksheet() # Widen the first column to make the text clearer. worksheet.set_column('A:A', 20) # Add a bold format to use to highlight cells. bold = workbook.add_format({'bold': True}) # Write some simple text. worksheet.write('A1', 'Hello') # Text with formatting. worksheet.write('A2', 'World', bold) # Write some numbers, with row/column notation. worksheet.write(2, 0, 123) worksheet.write(3, 0, 123.456) # Insert an image. worksheet.insert_image('B5', 'logo.png') workbook.close() .. image:: https://raw.github.com/jmcnamara/XlsxWriter/master/dev/docs/source/_images/demo.png See the full documentation at: https://xlsxwriter.readthedocs.io Release notes: https://xlsxwriter.readthedocs.io/changes.html Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 XlsxWriter-1.1.2/XlsxWriter.egg-info/SOURCES.txt0000644000076500000240000000637113362632140021407 0ustar Johnstaff00000000000000Changes LICENSE.txt MANIFEST.in README.rst setup.cfg setup.py XlsxWriter.egg-info/PKG-INFO XlsxWriter.egg-info/SOURCES.txt XlsxWriter.egg-info/dependency_links.txt XlsxWriter.egg-info/top_level.txt docs/readme.html docs/_static/basic.css docs/_static/default.css docs/_static/hello01.png docs/_static/logo.png docs/_static/pygments.css examples/array_formula.py examples/autofilter.py examples/autofilter_data.txt examples/cell_indentation.py examples/chart.py examples/chart_area.py examples/chart_bar.py examples/chart_clustered.py examples/chart_column.py examples/chart_combined.py examples/chart_data_table.py examples/chart_data_tools.py examples/chart_date_axis.py examples/chart_doughnut.py examples/chart_gradient.py examples/chart_line.py examples/chart_pareto.py examples/chart_pattern.py examples/chart_pie.py examples/chart_radar.py examples/chart_scatter.py examples/chart_secondary_axis.py examples/chart_stock.py examples/chart_styles.py examples/chartsheet.py examples/comments1.py examples/comments2.py examples/conditional_format.py examples/context_manager.py examples/data_validate.py examples/datetimes.py examples/defined_name.py examples/demo.py examples/diagonal_border.py examples/django_simple.py examples/doc_properties.py examples/headers_footers.py examples/hello_world.py examples/hide_row_col.py examples/hide_sheet.py examples/http_server_py2.py examples/http_server_py3.py examples/hyperlink.py examples/images.py examples/images_bytesio.py examples/inheritance1.py examples/inheritance2.py examples/macros.py examples/merge1.py examples/merge_rich_string.py examples/outline.py examples/outline_collapsed.py examples/pandas_chart.py examples/pandas_chart_columns.py examples/pandas_chart_line.py examples/pandas_chart_stock.py examples/pandas_column_formats.py examples/pandas_conditional_format.py examples/pandas_datetime.py examples/pandas_header_format.py examples/pandas_multiple.py examples/pandas_positioning.py examples/pandas_simple.py examples/panes.py examples/rich_strings.py examples/right_to_left.py examples/sparklines1.py examples/sparklines2.py examples/tab_colors.py examples/tables.py examples/text_indent.py examples/textbox.py examples/tutorial1.py examples/tutorial2.py examples/tutorial3.py examples/unicode_polish_utf8.py examples/unicode_polish_utf8.txt examples/unicode_python2.py examples/unicode_python3.py examples/unicode_shift_jis.py examples/unicode_shift_jis.txt examples/vbaProject.bin examples/vba_extract.py examples/worksheet_protection.py xlsxwriter/__init__.py xlsxwriter/app.py xlsxwriter/chart.py xlsxwriter/chart_area.py xlsxwriter/chart_bar.py xlsxwriter/chart_column.py xlsxwriter/chart_doughnut.py xlsxwriter/chart_line.py xlsxwriter/chart_pie.py xlsxwriter/chart_radar.py xlsxwriter/chart_scatter.py xlsxwriter/chart_stock.py xlsxwriter/chartsheet.py xlsxwriter/comments.py xlsxwriter/compat_collections.py xlsxwriter/compatibility.py xlsxwriter/contenttypes.py xlsxwriter/core.py xlsxwriter/custom.py xlsxwriter/drawing.py xlsxwriter/exceptions.py xlsxwriter/format.py xlsxwriter/packager.py xlsxwriter/relationships.py xlsxwriter/shape.py xlsxwriter/sharedstrings.py xlsxwriter/styles.py xlsxwriter/table.py xlsxwriter/theme.py xlsxwriter/utility.py xlsxwriter/vml.py xlsxwriter/workbook.py xlsxwriter/worksheet.py xlsxwriter/xmlwriter.pyXlsxWriter-1.1.2/XlsxWriter.egg-info/top_level.txt0000644000076500000240000000001313362632140022240 0ustar Johnstaff00000000000000xlsxwriter