XlsxWriter-0.5.2/0000755000076500000240000000000012260572662013702 5ustar Johnstaff00000000000000XlsxWriter-0.5.2/Changes0000644000076500000240000003362112260571562015200 0ustar Johnstaff00000000000000 Release 0.5.2 - December 31 2013 -------------------------------- * Added date axis handling to charts. See :ref:`ex_chart_date_axis`. Issue `#73 `_. * Added support for non-contiguous chart ranges. Issue `#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. Issue `#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. Issue `#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. Issue `#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:`condtional_format()`. Release 0.4.8 - November 13 2013 -------------------------------- * Added ``in_memory`` :func:`Workbook` constructor option to allow XlsxWriter to work on Google App Engine. Issue `#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 Bootstap 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. Issue `#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 behaviour 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. Issue `#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. Issue `#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 stacktrace. PR from Andrei Korostelev, `#21 `_. Release 0.3.2 - May 1 2013 ----------------------------- * Speed optimisations. 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 minimise 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 colour. 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. * print_area() * fit_to_pages() * set_start_page() * set_print_scale() * set_h_pagebreaks() * set_v_pagebreaks() Release 0.0.6 - February 22 2013 -------------------------------- * Added page setup method. * print_row_col_headers Release 0.0.5 - February 21 2013 -------------------------------- * Added page setup methods. * repeat_rows() * 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. * center_horizontally() * center_vertically() * set_header() * set_footer() * hide_gridlines() Release 0.0.3 - February 19 2013 -------------------------------- * Added page setup method. * set_margins() Release 0.0.2 - February 18 2013 -------------------------------- * Added page setup methods. * set_landscape() * set_portrait() * set_page_view() * set_paper() * print_across() Release 0.0.1 - February 17 2013 -------------------------------- * First public release. XlsxWriter-0.5.2/docs/0000755000076500000240000000000012260572662014632 5ustar Johnstaff00000000000000XlsxWriter-0.5.2/docs/_static/0000755000076500000240000000000012260572662016260 5ustar Johnstaff00000000000000XlsxWriter-0.5.2/docs/_static/basic.css0000644000076500000240000002051212155215717020051 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-0.5.2/docs/_static/default.css0000644000076500000240000001011512155215717020412 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-0.5.2/docs/_static/hello01.png0000644000076500000240000017473612155215717020252 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-0.5.2/docs/_static/logo.png0000644000076500000240000001522212155215717017726 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-0.5.2/docs/_static/pygments.css0000644000076500000240000000733112155215717020642 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-0.5.2/docs/readme.html0000644000076500000240000001445612230067135016756 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-0.5.2/examples/0000755000076500000240000000000012260572662015520 5ustar Johnstaff00000000000000XlsxWriter-0.5.2/examples/array_formula.py0000644000076500000240000000176312227556135020744 0ustar Johnstaff00000000000000####################################################################### # # Example of how to use Python and the XlsxWriter module to write # simple array formulas. # # Copyright 2013, 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-0.5.2/examples/autofilter.py0000644000076500000240000001410012203261520020225 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, 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-0.5.2/examples/autofilter_data.txt0000644000076500000240000000356312124652616021434 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-0.5.2/examples/cell_indentation.py0000644000076500000240000000116512173557213021406 0ustar Johnstaff00000000000000############################################################################## # # A simple formatting example using XlsxWriter. # # This program demonstrates the indentation cell format. # # Copyright 2013, 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-0.5.2/examples/chart.py0000644000076500000240000000164112173557213017173 0ustar Johnstaff00000000000000####################################################################### # # An example of a simple Excel chart with Python and XlsxWriter. # # Copyright 2013, 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-0.5.2/examples/chart_area.py0000644000076500000240000000650412242520435020157 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Area charts with Python and XlsxWriter. # # Copyright 2013, 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-0.5.2/examples/chart_bar.py0000644000076500000240000000647312242520472020021 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Bar charts with Python and XlsxWriter. # # Copyright 2013, 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-0.5.2/examples/chart_column.py0000644000076500000240000000651512242520526020547 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Column charts with Python and XlsxWriter. # # Copyright 2013, 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-0.5.2/examples/chart_data_table.py0000644000076500000240000000524412242520705021327 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Column charts with data tables using # Python and XlsxWriter. # # Copyright 2013, 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-0.5.2/examples/chart_data_tools.py0000644000076500000240000001222312173557213021402 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, 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. and some axis labels. 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. and some axis labels. 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. and some axis labels. 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. and some axis labels. 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. and some axis labels. 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. and some axis labels. 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-0.5.2/examples/chart_date_axis.py0000644000076500000240000000272112260565361021214 0ustar Johnstaff00000000000000####################################################################### # # An example of creating an Excel charts with a date axis using # Python and XlsxWriter. # # Copyright 2013, 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-0.5.2/examples/chart_line.py0000644000076500000240000000312712242520615020174 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Line charts with Python and XlsxWriter. # # Copyright 2013, 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-0.5.2/examples/chart_pie.py0000644000076500000240000000450712242521025020021 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Pie charts with Python and XlsxWriter. # # The demo also shows how to set segment colours. It is possible to # define chart colors for most types of XlsxWriter charts # via the add_series() method. However, Pie 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, 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', 'embedded': 1}) # Configure the series and add user defined segment colours. chart2.add_series({ 'name': 'Pie sales data', 'categories': '=Sheet1!A2:A4', 'values': '=Sheet1!B2:B4', '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}) workbook.close() XlsxWriter-0.5.2/examples/chart_radar.py0000644000076500000240000000652412242520646020346 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Radar charts with Python and XlsxWriter. # # Copyright 2013, 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-0.5.2/examples/chart_scatter.py0000644000076500000240000001244312242520656020720 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Scatter charts with Python and XlsxWriter. # # Copyright 2013, 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('D51', 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-0.5.2/examples/chart_secondary_axis.py0000644000076500000240000000263012173557213022265 0ustar Johnstaff00000000000000####################################################################### # # An example of creating an Excel Line chart with a secondary axis # using Python and XlsxWriter. # # Copyright 2013, 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-0.5.2/examples/chart_stock.py0000644000076500000240000000326112173557213020376 0ustar Johnstaff00000000000000####################################################################### # # An example of creating Excel Stock charts with Python and XlsxWriter. # # Copyright 2013, 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-0.5.2/examples/chartsheet.py0000644000076500000240000000320712242515641020220 0ustar Johnstaff00000000000000####################################################################### # # An example of creating an Excel chart in a chartsheet with Python # and XlsxWriter. # # Copyright 2013, 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) workbook.close() XlsxWriter-0.5.2/examples/comments1.py0000644000076500000240000000074412173557213020003 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, 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-0.5.2/examples/comments2.py0000644000076500000240000001663012173557213020005 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, 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 colour. # 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 colour.' comment = 'Hello.' worksheet6.write('C3', cell_text, text_wrap) worksheet6.write_comment('C3', comment, {'color': 'green'}) cell_text = 'This cell comment has the default colour.' worksheet6.write('C6', cell_text, text_wrap) worksheet6.write_comment('C6', comment) cell_text = 'This cell comment has a different colour.' 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-0.5.2/examples/conditional_format.py0000644000076500000240000001776412173557213021762 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, 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() # 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 and data bars. Default colours.' data = range(1, 13) worksheet7.write('A1', caption) worksheet7.write('B2', "2 Color Scale") worksheet7.write('D2', "3 Color Scale") worksheet7.write('F2', "Data Bars") 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, 5, row_data) worksheet7.conditional_format('B3:B14', {'type': '2_color_scale'}) worksheet7.conditional_format('D3:D14', {'type': '3_color_scale'}) worksheet7.conditional_format('F3:F14', {'type': 'data_bar'}) ############################################################################### # # Example 8. # caption = 'Examples of color scales and data bars. Modified colours.' data = range(1, 13) worksheet8.write('A1', caption) worksheet8.write('B2', "2 Color Scale") worksheet8.write('D2', "3 Color Scale") worksheet8.write('F2', "Data Bars") 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.conditional_format('B3:B14', {'type': '2_color_scale', 'min_color': "#FF0000", 'max_color': "#00FF00"}) worksheet8.conditional_format('D3:D14', {'type': '3_color_scale', 'min_color': "#C5D9F1", 'mid_color': "#8DB4E3", 'max_color': "#538ED5"}) worksheet8.conditional_format('F3:F14', {'type': 'data_bar', 'bar_color': '#63C384'}) workbook.close() XlsxWriter-0.5.2/examples/data_validate.py0000644000076500000240000001562112173557213020657 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, 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('B10', {'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-0.5.2/examples/datetimes.py0000644000076500000240000000341212214413463020041 0ustar Johnstaff00000000000000############################################################################## # # A simple program to write some dates and times to an Excel file # using the XlsxWriter Python module. # # Copyright 2013, 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-0.5.2/examples/defined_name.py0000644000076500000240000000217212173557213020470 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, 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') XlsxWriter-0.5.2/examples/demo.py0000644000076500000240000000151012206216155017003 0ustar Johnstaff00000000000000############################################################################## # # A simple example of some of the features of the XlsxWriter Python module. # # Copyright 2013, 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': 1}) # 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-0.5.2/examples/doc_properties.py0000644000076500000240000000145312173557213021114 0ustar Johnstaff00000000000000############################################################################## # # An example of adding document properties to a XlsxWriter file. # # Copyright 2013, 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.") XlsxWriter-0.5.2/examples/headers_footers.py0000644000076500000240000000666712173557213021263 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 # # && Miscellaneous Literal ampersand & # # See the main XlsxWriter documentation for more information. # # Copyright 2013, 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 centred 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) ###################################################################### # # This is an example of some of the header/footer variables. # worksheet2 = workbook.add_worksheet('Variables') header2 = '&LPage &P of &N' + '&CFilename: &F' + '&RSheetname: &A' footer2 = '&LCurrent date: &D' + '&RCurrent time: &T' worksheet2.set_header(header2) worksheet2.set_footer(footer2) worksheet2.set_column('A:A', 50) worksheet2.write('A1', preview) worksheet2.write('A21', 'Next sheet') worksheet2.set_h_pagebreaks([20]) ###################################################################### # # This example shows how to use more than one font # worksheet3 = workbook.add_worksheet('Mixed fonts') header3 = '&C&"Courier New,Bold"Hello &"Arial,Italic"World' footer3 = '&C&"Symbol"e&"Arial" = mc&X2' worksheet3.set_header(header3) worksheet3.set_footer(footer3) worksheet3.set_column('A:A', 50) worksheet3.write('A1', preview) ###################################################################### # # Example of line wrapping # worksheet4 = workbook.add_worksheet('Word wrap') header4 = "&CHeading 1\nHeading 2" worksheet4.set_header(header4) worksheet4.set_column('A:A', 50) worksheet4.write('A1', preview) ###################################################################### # # Example of inserting a literal ampersand & # worksheet5 = workbook.add_worksheet('Ampersand') header5 = '&CCuriouser && Curiouser - Attorneys at Law' worksheet5.set_header(header5) worksheet5.set_column('A:A', 50) worksheet5.write('A1', preview) workbook.close() XlsxWriter-0.5.2/examples/hello_world.py0000644000076500000240000000055412173557213020406 0ustar Johnstaff00000000000000############################################################################## # # A hello world spreadsheet using the XlsxWriter Python module. # # Copyright 2013, 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-0.5.2/examples/hide_row_col.py0000644000076500000240000000166412173557213020534 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 optimisation to hide all rows that don't have data. # # Copyright 2013, 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-0.5.2/examples/hide_sheet.py0000644000076500000240000000131112173557213020165 0ustar Johnstaff00000000000000####################################################################### # # Example of how to hide a worksheet with XlsxWriter. # # Copyright 2013, 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-0.5.2/examples/http_server.py0000644000076500000240000000334212240542212020423 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, John McNamara, jmcnamara@cpan.org # # Note: This is a Python 2 example. For Python 3 use http.server (or # equivalent) and io.StringIO. import SimpleHTTPServer import SocketServer import StringIO import xlsxwriter class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_GET(self): # Create an in-memory output file for the new workbook. output = StringIO.StringIO() # 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-0.5.2/examples/hyperlink.py0000644000076500000240000000246512207750644020105 0ustar Johnstaff00000000000000############################################################################### # # Example of how to use the XlsxWriter module to write hyperlinks # # Copyright 2013, 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 the standard url link format. url_format = workbook.add_format({ 'color': 'blue', 'underline': 1 }) # Add a sample alternative link format. red_format = workbook.add_format({ 'color': 'red', 'bold': 1, 'underline': 1, 'size': 12, }) # Add an alternate description string to the URL. string = 'Python home' # Add a "tool tip" to the URL. tip = 'Get the latest Python news here.' # Write some hyperlinks worksheet.write('A1', 'http://www.python.org/') # Implicit format. worksheet.write('A3', 'http://www.python.org/', url_format, string) worksheet.write('A5', 'http://www.python.org/', url_format, string, tip) worksheet.write('A7', 'http://www.python.org/', red_format) worksheet.write('A9', 'mailto:jmcnamaracpan.org', url_format, 'Mail me') # Write a URL that isn't a hyperlink worksheet.write_string('A11', 'http://www.python.org/') workbook.close() XlsxWriter-0.5.2/examples/images.py0000644000076500000240000000164012173557213017336 0ustar Johnstaff00000000000000############################################################################## # # An example of inserting images into a worksheet using the XlsxWriter # Python module. # # Copyright 2013, 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-0.5.2/examples/merge1.py0000644000076500000240000000163512173557213017255 0ustar Johnstaff00000000000000############################################################################## # # A simple example of merging cells with the XlsxWriter Python module. # # Copyright 2013, 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-0.5.2/examples/merge_rich_string.py0000644000076500000240000000230012237463224021554 0ustar Johnstaff00000000000000############################################################################## # # An example of merging cells which contain a rich string using the # XlsxWriter Python module. # # Copyright 2013, 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-0.5.2/examples/outline.py0000644000076500000240000001511712173557213017554 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, 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-0.5.2/examples/outline_collapsed.py0000644000076500000240000001704512173557213021604 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, 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-0.5.2/examples/panes.py0000644000076500000240000000614512173557213017204 0ustar Johnstaff00000000000000####################################################################### # # Example of using Python and the XlsxWriter module to create # worksheet panes. # # Copyright 2013, 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-0.5.2/examples/rich_strings.py0000644000076500000240000000303112173557213020563 0ustar Johnstaff00000000000000####################################################################### # # An example of using Python and XlsxWriter to write some "rich strings", # i.e., strings with multiple formats. # # Copyright 2013, 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-0.5.2/examples/right_to_left.py0000644000076500000240000000140612173557213020722 0ustar Johnstaff00000000000000####################################################################### # # Example of how to use Python and the XlsxWriter module to change the # default worksheet direction from left-to-right to right-to-left as # required by some middle eastern versions of Excel. # # Copyright 2013, 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() # Change the direction for worksheet2. worksheet2.right_to_left() # Write some data to show the difference. # Standard direction: A1, B1, C1, ... worksheet1.write('A1', 'Hello') # Right to left direction: ..., C1, B1, A1 worksheet2.write('A1', 'Hello') workbook.close() XlsxWriter-0.5.2/examples/sparklines1.py0000644000076500000240000000250212173557213020323 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, 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-0.5.2/examples/sparklines2.py0000644000076500000240000002021512173557213020325 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, 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 colour.' 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-0.5.2/examples/tab_colors.py0000644000076500000240000000123212173557213020215 0ustar Johnstaff00000000000000####################################################################### # # Example of how to set Excel worksheet tab colours using Python # and the XlsxWriter module.. # # Copyright 2013, 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 colours worksheet1.set_tab_color('red') worksheet2.set_tab_color('green') worksheet3.set_tab_color('#FF9900') # Orange # worksheet4 will have the default colour. workbook.close() XlsxWriter-0.5.2/examples/tables.py0000644000076500000240000002422612173557213017350 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, 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-0.5.2/examples/text_indent.py0000644000076500000240000000115712173557213020421 0ustar Johnstaff00000000000000############################################################################## # # A simple formatting example using XlsxWriter. # # This program demonstrates the indentation cell format. # # Copyright 2013, 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-0.5.2/examples/tutorial1.py0000644000076500000240000000173312173557213020020 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, 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-0.5.2/examples/tutorial2.py0000644000076500000240000000237712173557213020026 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, 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-0.5.2/examples/tutorial3.py0000644000076500000240000000326012173557213020017 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, 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-0.5.2/examples/unicode_polish_utf8.py0000644000076500000240000000204712173557213022045 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, 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-0.5.2/examples/unicode_polish_utf8.txt0000644000076500000240000000144212106312053022215 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-0.5.2/examples/unicode_python2.py0000644000076500000240000000117712242523316021201 0ustar Johnstaff00000000000000############################################################################### # _*_ coding: utf-8 # # A simple Unicode spreadsheet in Python 2 using the XlsxWriter Python module. # # Copyright 2013, 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-0.5.2/examples/unicode_python3.py0000644000076500000240000000100712242523322021167 0ustar Johnstaff00000000000000############################################################################### # # # A simple Unicode spreadsheet in Python 3 using the XlsxWriter Python module. # # Copyright 2013, 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-0.5.2/examples/unicode_shift_jis.py0000644000076500000240000000205612173557213021563 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, 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-0.5.2/examples/unicode_shift_jis.txt0000644000076500000240000000066512106312053021741 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-0.5.2/examples/worksheet_protection.py0000644000076500000240000000203012173557213022344 0ustar Johnstaff00000000000000######################################################################## # # Example of cell locking and formula hiding in an Excel worksheet # using Python and the XlsxWriter module. # # Copyright 2013, 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-0.5.2/LICENSE.txt0000644000076500000240000000277512100640427015524 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-0.5.2/PKG-INFO0000644000076500000240000000457212260572662015007 0ustar Johnstaff00000000000000Metadata-Version: 1.0 Name: XlsxWriter Version: 0.5.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 images. * Rich multi-format strings. * Cell comments. * Memory optimisation mode for writing large files. It supports Python 2.5, 2.6, 2.7, 3.1, 3.2, 3.3, 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': 1}) # 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: http://xlsxwriter.readthedocs.org Release notes: https://xlsxwriter.readthedocs.org/en/latest/changes.html Platform: UNKNOWN XlsxWriter-0.5.2/README.rst0000644000076500000240000000316512227610533015367 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 images. * Rich multi-format strings. * Cell comments. * Memory optimisation mode for writing large files. It supports Python 2.5, 2.6, 2.7, 3.1, 3.2, 3.3, 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': 1}) # 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: http://xlsxwriter.readthedocs.org Release notes: https://xlsxwriter.readthedocs.org/en/latest/changes.html XlsxWriter-0.5.2/setup.py0000644000076500000240000000055712260572213015413 0ustar Johnstaff00000000000000from distutils.core import setup setup( name='XlsxWriter', version='0.5.2', author='John McNamara', author_email='jmcnamara@cpan.org', url='https://github.com/jmcnamara/XlsxWriter', packages=['xlsxwriter'], license='BSD', description='A Python module for creating Excel XLSX files.', long_description=open('README.rst').read(), ) XlsxWriter-0.5.2/xlsxwriter/0000755000076500000240000000000012260572662016135 5ustar Johnstaff00000000000000XlsxWriter-0.5.2/xlsxwriter/__init__.py0000644000076500000240000000011712260572213020235 0ustar Johnstaff00000000000000__version__ = '0.5.2' __VERSION__ = __version__ from .workbook import Workbook XlsxWriter-0.5.2/xlsxwriter/app.py0000644000076500000240000001246712163114777017302 0ustar Johnstaff00000000000000############################################################################### # # App - A class for writing the Excel XLSX App file. # # Copyright 2013, 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_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 not 'manager' 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_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-0.5.2/xlsxwriter/chart.py0000644000076500000240000030167312260563066017620 0ustar Johnstaff00000000000000############################################################################### # # Chart - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013, John McNamara, jmcnamara@cpan.org # import re from warnings import warn from . import xmlwriter from .utility import xl_color from .utility import xl_rowcol_to_cell from .utility import supported_datetime from .utility import datetime_to_excel_datetime 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 = '' self.series_index = 0 self.style_id = 2 self.axis_ids = [] self.axis2_ids = [] self.cat_has_num_fmt = 0 self.requires_category = 0 self.legend_position = 'right' self.legend_delete_series = None self.legend_font = None self.legend_layout = None 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 = None self.series_overlap = 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._set_default_properties() def add_series(self, options): """ 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. # Check that the required input has been specified. if not 'values' in options: warn("Must specify 'values' in add_series()") return if self.requires_category and not 'categories' in options: warn("Must specify 'categories' in add_series() " "for this chart type") # 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 = self._get_line_properties(options.get('line')) # Allow 'border' as a synonym for 'line' in bar/column style charts. if options.get('border'): line = self._get_line_properties(options['border']) # Set the fill properties for the series. fill = self._get_fill_properties(options.get('fill')) # 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 gap for Bar/Column charts. if options.get('gap') is not None: self.series_gap = options['gap'] # Set the overlap for Bar/Column charts. if options.get('overlap'): self.series_overlap = options['overlap'] # Set the secondary axis properties. x2_axis = options.get('x2_axis') y2_axis = options.get('y2_axis') # 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, '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): """ Set the chart title options. Args: options: A dictionary of chart title options. Returns: Nothing. """ 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. """ self.legend_position = options.get('position', 'right') self.legend_delete_series = options.get('delete_series') self.legend_font = self._convert_font_args(options.get('font')) self.legend_layout = self._get_layout_properties(options.get('layout'), False) # Turn off the legend. if options.get('none'): self.legend_position = 'none' 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 42 built-in Excel chart styles. The default is 2. if style_id is None: style_id = 2 if style_id < 0 or style_id > 42: 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 not option 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): """ Set size or scale of the chart. Args: options: A dictionary of chart size options. Returns: Nothing. """ # 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.x_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) 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 = self._get_line_properties(options['up']['border']) if 'line' in options['up']: up_line = self._get_line_properties(options['up']['line']) if 'fill' in options['up']: up_fill = self._get_line_properties(options['up']['fill']) # Set properties for 'down' bar. if options.get('down'): if 'border' in options['down']: # Map border to line. down_line = \ self._get_line_properties(options['down']['border']) if 'line' in options['down']: down_line = self._get_line_properties(options['down']['line']) if 'fill' in options['down']: down_fill = self._get_line_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 = self._get_line_properties(options.get('line')) fill = self._get_fill_properties(options.get('fill')) self.drop_lines = {'line': line, 'fill': fill} 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 = self._get_line_properties(options.get('line')) fill = self._get_fill_properties(options.get('fill')) self.hi_low_lines = {'line': line, 'fill': fill} ########################################################################### # # 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'), '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'), 'num_format': options.get('num_format'), 'num_format_linked': options.get('num_format_linked'), 'interval_unit': options.get('interval_unit'), } if 'visible' in options: axis['visible'] = options.get('visible') else: axis['visible'] = 1 # 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 # 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) if axis.get('max') and supported_datetime(axis['max']): axis['max'] = datetime_to_excel_datetime(axis['max'], self.date_1904) if axis.get('crossing') and supported_datetime(axis['crossing']): axis['crossing'] = datetime_to_excel_datetime(axis['crossing'], self.date_1904) # 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) 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'] *= 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: return data sheet = data[0] range1 = xl_rowcol_to_cell(data[1], data[2], True, True) range2 = xl_rowcol_to_cell(data[3], data[4], True, True) return sheet + '!' + range1 + ':' + range2 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 = name[0] + '!' + cell name = '' elif re.match(r'^=?[^!]+!\$', 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' # 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 not formula 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_color(self, color): # Convert the user specified colour to an RGB colour. rgb_color = xl_color(color) # Remove leading FF from RGB colour for charts. rgb_color = re.sub(r'^FF', '', rgb_color) return rgb_color def _get_line_properties(self, line): # Convert user line properties to the structure required internally. if not line: return {'defined': False} 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 def _get_fill_properties(self, fill): # Convert user fill properties to the structure required internally. if not fill: return {'defined': False} fill['defined'] = True return fill def _get_marker_properties(self, marker): # Convert user marker properties to the structure required internally. if not marker: return 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 == 'automatic': marker['automatic'] = 1 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 = self._get_line_properties(marker.get('line')) # Allow 'border' as a synonym for 'line'. if 'border' in marker: line = self._get_line_properties(marker['border']) # Set the fill properties for the marker. fill = self._get_fill_properties(marker.get('fill')) marker['line'] = line marker['fill'] = fill return marker def _get_trendline_properties(self, trendline): # Convert user trendline properties to structure required internally. if not trendline: return 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 = self._get_line_properties(trendline.get('line')) # Allow 'border' as a synonym for 'line'. if 'border' in trendline: line = self._get_line_properties(trendline['border']) # Set the fill properties for the trendline. fill = self._get_fill_properties(trendline.get('fill')) trendline['line'] = line trendline['fill'] = fill 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. if 'plus_values'in options: error_bars['plus_values'] = options['plus_values'] if 'minus_values'in options: error_bars['minus_values'] = options['minus_values'] if 'plus_data'in options: error_bars['plus_data'] = options['plus_data'] if 'minus_data'in options: error_bars['minus_data'] = options['minus_data'] # Set the line properties for the error bars. error_bars['line'] = self._get_line_properties(options.get('line')) error_bars['fill'] = self._get_line_properties(options.get('fill')) 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'] = self._get_line_properties(options.get('line')) gridline['fill'] = self._get_line_properties(options.get('fill')) return gridline def _get_labels_properties(self, labels): # Convert user labels properties to the structure required internally. if not labels: return None # Map user defined label positions to Excel positions. position = labels.get('position') if position: positions = { 'center': 'ctr', 'right': 'r', 'left': 'l', 'top': 't', 'above': 't', 'bottom': 'b', 'below': 'b', 'inside_end': 'inEnd', 'outside_end': 'outEnd', 'best_fit': 'bestFit', } if position in positions: labels['position'] = positions[position] else: warn("Unknown label position '%s'" % position) return 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 = self._get_line_properties(options.get('line')) # Allow 'border' as a synonym for 'line'. if options.get('border'): line = self._get_line_properties(options['border']) # Set the fill properties for the chartarea. fill = self._get_fill_properties(options.get('fill')) # Set the plotarea layout. layout = self._get_layout_properties(options.get('layout'), False) area['line'] = line area['fill'] = fill area['layout'] = layout return area 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' not allowed 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 = self._get_line_properties(user_point.get('line')) # Allow 'border' as a synonym for 'line'. if 'border' in user_point: line = self._get_line_properties(user_point['border']) # Set the fill properties for the chartarea. fill = self._get_fill_properties(user_point.get('fill')) point['line'] = line point['fill'] = fill points.append(point) return points 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 = 1 + int(self.id) axis_count = 1 + len(self.axis2_ids) + len(self.axis_ids) id1 = '5%03d%04d' % (chart_id, axis_count) id2 = '5%03d%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 _get_font_style_attributes(self, font): # _get_font_style_attributes. attributes = [] if not font: return attributes if font['size']: attributes.append(('sz', font['size'])) if font['bold'] is not None: attributes.append(('b', 0 + font['bold'])) if font['italic'] is not None: attributes.append(('i', 0 + font['italic'])) if font['underline'] is not None: attributes.append(('u', 'sng')) # Turn off baseline for fonts that don't use it. if font['baseline'] != -1: attributes.append(('baseline', font['baseline'])) return attributes def _get_font_latin_attributes(self, 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 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}) # 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) 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) 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_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:tickLblPos element. self._write_tick_label_pos(x_axis.get('label_position')) # 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:labelAlign element. self._write_label_align('ctr') # 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')) 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:tickLblPos element. self._write_tick_label_pos(y_axis.get('label_position')) # 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')) 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'], y_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:tickLblPos element. self._write_tick_label_pos(x_axis.get('label_position')) # 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')) 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:tickLblPos element. self._write_tick_label_pos(x_axis.get('label_position')) # 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: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 linkedSource. 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_major_tick_mark(self, val): # Write the element. if not val: return attributes = [('val', val)] self._xml_empty_tag('c:majorTickMark', 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): # Write the element. 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_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. position = self.legend_position font = self.legend_font delete_series = [] overlay = 0 if (self.legend_delete_series is not None and type(self.legend_delete_series) is list): delete_series = self.legend_delete_series if position.startswith('overlay_'): position = position.replace('overlay_', '') overlay = 1 allowed = { 'right': 'r', 'left': 'l', 'top': 't', 'bottom': 'b', } if position == 'none': return if not position 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(self.legend_layout, 'legend') if font: self._write_tx_pr(None, font) # Write the c:overlay element. if overlay: self._write_overlay() 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. b = 0.75 l = 0.7 r = 0.7 t = 0.75 header = 0.3 footer = 0.3 attributes = [ ('b', b), ('l', l), ('r', r), ('t', t), ('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 = self._get_font_style_attributes(font) latin_attributes = self._get_font_latin_attributes(font) if font and font['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 = self._get_font_style_attributes(font) latin_attributes = self._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 'automatic' in marker: 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_value(self): # Write the element without a sub-element. style = self.default_marker if not style: return attributes = [('val', 1)] self._xml_empty_tag('c:marker', attributes) 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 if 'fill' in series and series['fill']['defined']: has_fill = True if 'line' in series and series['line']['defined']: has_line = True if not has_fill and not has_line: return self._xml_start_tag('c:spPr') # Write the fill elements for solid charts such as pie and bar. if series['fill'] is not None 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']) # Write the a:ln element. if 'line' in series 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, line): # Write the element. self._xml_start_tag('a:solidFill') if 'color' in line: color = self._get_color(line['color']) # Write the a:srgbClr element. self._write_a_srgb_clr(color) self._xml_end_tag('a:solidFill') def _write_a_srgb_clr(self, val): # Write the element. attributes = [('val', val)] self._xml_empty_tag('a:srgbClr', 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')) 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. # val = _[0] is not None ? _[0]: 2 attributes = [('val', val)] self._xml_empty_tag('c:order', attributes) def _write_period(self, val): # Write the element. # val = _[0] is not None ? _[0]: 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_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:dLblPos element. if labels.get('position'): self._write_d_lbl_pos(labels['position']) # 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:showLeaderLines element. if labels.get('leader_lines'): self._write_show_leader_lines() self._xml_end_tag('c:dLbls') 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_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() 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') XlsxWriter-0.5.2/xlsxwriter/chart_area.py0000644000076500000240000000475212232024151020570 0ustar Johnstaff00000000000000############################################################################### # # ChartArea - A class for writing the Excel XLSX Area charts. # # Copyright 2013, 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%' 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:marker element. self._write_marker_value() # Write the c:axId elements self._write_axis_ids(args) self._xml_end_tag('c:areaChart') XlsxWriter-0.5.2/xlsxwriter/chart_bar.py0000644000076500000240000000720512232024151020420 0ustar Johnstaff00000000000000############################################################################### # # ChartBar - A class for writing the Excel XLSX Bar charts. # # Copyright 2013, John McNamara, jmcnamara@cpan.org # from . import chart 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%' self.set_x_axis({}) self.set_y_axis({}) ########################################################################### # # 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 is None: self.series_overlap = 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:marker element. self._write_marker_value() # Write the c:gapWidth element. self._write_gap_width(self.series_gap) # Write the c:overlap element. self._write_overlap(self.series_overlap) # 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-0.5.2/xlsxwriter/chart_column.py0000644000076500000240000000603612232024151021152 0ustar Johnstaff00000000000000############################################################################### # # ChartColumn - A class for writing the Excel XLSX Column charts. # # Copyright 2013, 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%' 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 is None: self.series_overlap = 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:marker element. self._write_marker_value() # Write the c:gapWidth element. self._write_gap_width(self.series_gap) # Write the c:overlap element. self._write_overlap(self.series_overlap) # 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-0.5.2/xlsxwriter/chart_line.py0000644000076500000240000000525212232024151020603 0ustar Johnstaff00000000000000############################################################################### # # ChartLine - A class for writing the Excel XLSX Line charts. # # Copyright 2013, 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 ########################################################################### # # 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') XlsxWriter-0.5.2/xlsxwriter/chart_pie.py0000644000076500000240000001217312246737355020457 0ustar Johnstaff00000000000000############################################################################### # # ChartPie - A class for writing the Excel XLSX Pie charts. # # Copyright 2013, John McNamara, jmcnamara@cpan.org # 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 ########################################################################### # # 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) self._xml_end_tag('c:plotArea') def _write_legend(self): # Over-ridden method to add to legend. # Write the element. position = self.legend_position font = self.legend_font delete_series = [] overlay = 0 if (self.legend_delete_series is not None and type(self.legend_delete_series) is list): delete_series = self.legend_delete_series if position.startswith('overlay_'): position = position.replace('overlay_', '') overlay = 1 allowed = { 'right': 'r', 'left': 'l', 'top': 't', 'bottom': 'b', } if position == 'none': return if not position 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(self.legend_layout, 'legend') # Write the c:overlay element. if overlay: self._write_overlay() # 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', 0)] self._xml_empty_tag('c:firstSliceAng', attributes) XlsxWriter-0.5.2/xlsxwriter/chart_radar.py0000644000076500000240000000473712232024151020754 0ustar Johnstaff00000000000000############################################################################### # # ChartRadar - A class for writing the Excel XLSX Radar charts. # # Copyright 2013, 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({}) # 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-0.5.2/xlsxwriter/chart_scatter.py0000644000076500000240000002155612241430627021337 0ustar Johnstaff00000000000000############################################################################### # # ChartScatter - A class for writing the Excel XLSX Scatter charts. # # Copyright 2013, John McNamara, jmcnamara@cpan.org # from . import chart 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_postion = 'b' self.smooth_allowed = True ########################################################################### # # 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' if subtype == 'smooth_with_markers': style = 'smoothMarker' if subtype == 'smooth': style = 'smoothMarker' # 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:marker element. self._write_marker_value() # 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, } # Turn markers off for subtypes that don't have them. if not 'marker' in subtype: # Go through each series and define default values. for series in self.series: # Set a marker type unless there is a user defined type. if not series.get('marker'): series['marker'] = {'type': 'none', '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-0.5.2/xlsxwriter/chart_stock.py0000644000076500000240000000633212260425542021011 0ustar Johnstaff00000000000000############################################################################### # # ChartStock - A class for writing the Excel XLSX Stock charts. # # Copyright 2013, 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' 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:marker element. self._write_marker_value() # 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-0.5.2/xlsxwriter/chartsheet.py0000644000076500000240000001154112242515641020635 0ustar Johnstaff00000000000000############################################################################### # # Chartsheet - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013, 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. """ # Overridden from parent worksheet class. if self.chart: self.chart.protection = True else: self.protection = True if not options: options = {} options = options.copy() options['sheet'] = False options['content'] = True options['scenarios'] = True # Call the parent method. super(Chartsheet, self).protect(password, options) ########################################################################### # # 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-0.5.2/xlsxwriter/comments.py0000644000076500000240000001221712232024425020323 0ustar Johnstaff00000000000000############################################################################### # # Comments - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013, 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 not author 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] # Look up the author id. author_id = None if author is not None: author_id = self.author_ids[author] # Write the comment element. self._write_comment(row, col, text, author_id) self._xml_end_tag('commentList') def _write_comment(self, row, col, text, author_id): # 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) self._xml_end_tag('comment') def _write_text(self, text): # Write the element. self._xml_start_tag('text') # Write the text r element. self._write_text_r(text) self._xml_end_tag('text') def _write_text_r(self, text): # Write the element. self._xml_start_tag('r') # Write the rPr element. self._write_r_pr() # 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('^\s', text) or re.search('\s$', text): attributes.append(('xml:space', 'preserve')) self._xml_data_element('t', text, attributes) def _write_r_pr(self): # Write the element. self._xml_start_tag('rPr') # Write the sz element. self._write_sz() # Write the color element. self._write_color() # Write the rFont element. self._write_r_font() # Write the family element. self._write_family() self._xml_end_tag('rPr') def _write_sz(self): # Write the element. attributes = [('val', 8)] 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): # Write the element. attributes = [('val', 'Tahoma')] self._xml_empty_tag('rFont', attributes) def _write_family(self): # Write the element. attributes = [('val', 2)] self._xml_empty_tag('family', attributes) XlsxWriter-0.5.2/xlsxwriter/compat_collections.py0000644000076500000240000001744612206732123022372 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 accessable 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 enviroments 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-0.5.2/xlsxwriter/compatibility.py0000644000076500000240000000126212240540011021336 0ustar Johnstaff00000000000000############################################################################### # # Python 2/3 compatibility functions for XlsxWriter. # # Copyright (c), 2013, John McNamara, jmcnamara@cpan.org # 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 XlsxWriter-0.5.2/xlsxwriter/contenttypes.py0000644000076500000240000001473712232024476021254 0ustar Johnstaff00000000000000############################################################################### # # ContentTypes - A class for writing the Excel XLSX ContentTypes file. # # Copyright 2013, John McNamara, jmcnamara@cpan.org # 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__() self.defaults = defaults[:] self.overrides = 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: self._add_default((image_type, '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. # TODO: Fix when test is ported. # Change the workbook.xml content-type from xlsx to xlsx. # for aref in self.overrides: # if aref[0] == '/xl/workbook.xml': # aref[1]='application/vnd.ms-excel.sheet.macroEnabled.main+xml' self._add_default(('bin', 'application/vnd.ms-office.vbaProject')) ########################################################################### # # 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-0.5.2/xlsxwriter/core.py0000644000076500000240000001251712163115070017431 0ustar Johnstaff00000000000000############################################################################### # # Core - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013, 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 _localtime_to_iso8601_date(self, date): # Convert to a ISO 8601 style "2010-01-01T00:00:00Z" date. if not date: date = datetime.now() 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.now()) xsi_type = 'dcterms:W3CDTF' date = self._localtime_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.now()) xsi_type = 'dcterms:W3CDTF' date = self._localtime_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-0.5.2/xlsxwriter/drawing.py0000644000076500000240000005511312242515641020141 0ustar Johnstaff00000000000000############################################################################### # # Drawing - A class for writing the Excel XLSX Drawing file. # # Copyright 2013, John McNamara, jmcnamara@cpan.org # from . import xmlwriter 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 dimensions in self.drawings: # Write the xdr:twoCellAnchor element. self._write_two_cell_anchor(index, dimensions) 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. self.drawings.append(drawing_object) ########################################################################### # # 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, dimensions): # Write the element. anchor_type = dimensions[0] col_from = dimensions[1] row_from = dimensions[2] col_from_offset = dimensions[3] row_from_offset = dimensions[4] col_to = dimensions[5] row_to = dimensions[6] col_to_offset = dimensions[7] row_to_offset = dimensions[8] col_absolute = dimensions[9] row_absolute = dimensions[10] width = dimensions[11] height = dimensions[12] description = dimensions[13] shape = dimensions[14] attributes = [] # Add attribute for images. if anchor_type == 2: attributes.append(('editAs', 'oneCell')) # Add editAs attribute for shapes. if shape and shape.editAs: attributes.append(('editAs', shape.editAs)) self._xml_start_tag('xdr:twoCellAnchor', attributes) # Write the xdr:from element. self._write_from( col_from, row_from, col_from_offset, row_from_offset) # Write the xdr:from element. self._write_to( col_to, row_to, col_to_offset, row_to_offset) if anchor_type == 1: # Graphic frame. # Write the xdr:graphicFrame element for charts. self._write_graphic_frame(index, description) elif anchor_type == 2: # Write the xdr:pic element. self._write_pic(index, col_absolute, row_absolute, width, height, description) else: # Write the xdr:sp element for shapes. self._write_sp(index, col_absolute, row_absolute, width, 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, descr=None): # Write the element. attributes = [('id', index), ('name', name)] # Add description attribute for images. if descr is not None: attributes.append(('descr', descr)) 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:txBody element. if shape.text: 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') shape.name = shape.type + ' ' + index if shape.name is not None: self._write_c_nv_pr(shape.id, shape.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') shape_name = shape.type + ' ' + index self._write_c_nv_pr(shape.id, shape_name) if shape.txBox: attributes = [('txBox', 1)] self._xml_start_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, description): # Write the element. self._xml_start_tag('xdr:pic') # Write the xdr:nvPicPr element. self._write_nv_pic_pr(index, description) # Write the xdr:blipFill element. self._write_blip_fill(index) # Pictures are rectangle shapes by default. shape = {'type': 'rect'} # 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, description): # Write the element. self._xml_start_tag('xdr:nvPicPr') # Write the xdr:cNvPr element. self._write_c_nv_pr(index + 1, 'Picture ' + str(index), description) # 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={}): # 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 = [('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) fill = shape.fill if len(fill) > 1: # Write the a:solidFill element. self._write_a_solid_fill(fill) else: self._xml_empty_tag('a:noFill') # Write the a:ln element. self._write_a_ln(shape) self._xml_end_tag('xdr:spPr') def _write_a_xfrm(self, col_absolute, row_absolute, width, height, shape={}): # Write the element. attributes = [] if "rotation" in shape: rotation = shape.rotation rotation *= 60000 attributes.append(('rot', rotation)) if 'flip_h' in shape: attributes.append(('flipH', 1)) if 'flip_v' in shape: 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={}): # Write the element. attributes = [] if 'type' in shape: attributes = [('prst', shape['type'])] 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={}): # Write the element. adjustments = [] if 'adjustments' in shape: 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 'connect' in shape: 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 not rgb is not None: rgb = '000000' attributes = [('val', rgb)] self._xml_start_tag('a:solidFill') self._xml_empty_tag('a:srgbClr', attributes) self._xml_end_tag('a:solidFill') def _write_a_ln(self, shape={}): # Write the element. weight = shape.line_weight attributes = [('w', weight * 9525)] self._xml_start_tag('a:ln', attributes) line = shape.line if len(line) > 1: # Write the a:solidFill element. self._write_a_solid_fill(line) else: self._xml_empty_tag('a:noFill') if shape.line_type: attributes = [('val', shape.line_type)] self._xml_empty_tag('a:prstDash', attributes) if shape.connect: self._xml_empty_tag('a:round') else: attributes = [('lim', 800000)] self._xml_empty_tag('a:miter', attributes) self._xml_empty_tag('a:headEnd') self._xml_empty_tag('a:tailEnd') self._xml_end_tag('a:ln') def _write_tx_body(self, col_absolute, row_absolute, width, height, shape): # Write the element. attributes = [ ('vertOverflow', "clip"), ('wrap', "square"), ('lIns', "27432"), ('tIns', "22860"), ('rIns', "27432"), ('bIns', "22860"), ('anchor', shape.valign), ('upright', "1"), ] self._xml_start_tag('xdr:txBody') self._xml_empty_tag('a:bodyPr', attributes) self._xml_empty_tag('a:lstStyle') self._xml_start_tag('a:p') rotation = shape.format.rotation if not rotation is not None: rotation = 0 rotation *= 60000 attributes = [('algn', shape.align), ('rtl', rotation)] self._xml_start_tag('a:pPr', attributes) attributes = [('sz', "1000")] self._xml_empty_tag('a:defRPr', attributes) self._xml_end_tag('a:pPr') self._xml_start_tag('a:r') size = shape.format.size if not size is not None: size = 8 size *= 100 bold = shape.format.bold if not bold is not None: bold = 0 italic = shape.format.italic if not italic is not None: italic = 0 underline = shape['format']['underline'] if underline: underline = 'sng' else: underline = 'none' strike = shape['format']['font_strikeout'] if strike: strike = 'Strike' else: strike = 'noStrike' attributes = [ ('lang', 'en-US'), ('sz', size), ('b', bold), ('i', italic), ('u', underline), ('strike', strike), ('baseline', 0), ] self._xml_start_tag('a:rPr', attributes) color = shape.format.color if color is not None: color = shape._get_palette_color(color) # color =~ s/^FF//; # Remove leading FF from rgb for shape color. else: color = '000000' self._write_a_solid_fill(color) font = shape.format.font if font is not None: font = 'Calibri' attributes = [('typeface', font)] self._xml_empty_tag('a:latin', attributes) self._xml_empty_tag('a:cs', attributes) self._xml_end_tag('a:rPr') self._xml_data_element('a:t', shape.text) self._xml_end_tag('a:r') self._xml_end_tag('a:p') self._xml_end_tag('xdr:txBody') XlsxWriter-0.5.2/xlsxwriter/format.py0000644000076500000240000006320312232025325017767 0ustar Johnstaff00000000000000############################################################################### # # Format - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013, John McNamara, jmcnamara@cpan.org # # Package imports. from . import xmlwriter class Format(xmlwriter.XMLwriter): """ A class for writing the Excel XLSX Format file. """ ########################################################################### # # Public API. # ########################################################################### def __init__(self, properties={}, xf_indicies=None, dxf_indicies=None): """ Constructor. """ super(Format, self).__init__() self.xf_format_indices = xf_indicies self.dxf_format_indices = dxf_indicies 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 = 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.center_across = 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) ########################################################################### # # 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=1): """ Set the Format bold property. Args: bold: Default is 1, turns property on. Returns: Nothing. """ self.bold = bold def set_italic(self, italic=1): """ Set the Format italic property. Args: italic: Default is 1, 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=1): """ Set the Format font_strikeout property. Args: font_strikeout: Default is 1, 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=1): """ Set the Format font_outline property. Args: font_outline: Default is 1, turns property on. Returns: Nothing. """ self.font_outline = font_outline def set_font_shadow(self, font_shadow=1): """ Set the Format font_shadow property. Args: font_shadow: Default is 1, 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=1): """ Set the Format locked property. Args: locked: Default is 1, turns property on. Returns: Nothing. """ self.locked = locked def set_hidden(self, hidden=1): """ Set the Format hidden property. Args: hidden: Default is 1, 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, center_across=1): """ Set the Format center_across property. Args: center_across: Default is 1, turns property on. Returns: Nothing. """ self.center_across = center_across def set_text_wrap(self, text_wrap=1): """ Set the Format text_wrap property. Args: text_wrap: Default is 1, 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 rotation >= -90 or rotation <= 90: if rotation < 0: rotation = -rotation + 90 else: raise Exception( "Rotation rotation outside range: -90 <= angle <= 90") self.rotation = rotation def set_indent(self, indent=1): """ Set the Format indent property. Args: indent: Default is 1, turns property on. Returns: Nothing. """ self.indent = indent def set_shrink(self, shrink=1): """ Set the Format shrink property. Args: shrink: Default is 1, turns property on. Returns: Nothing. """ self.shrink = shrink def set_text_justlast(self, text_justlast=1): """ Set the Format text_justlast property. Args: text_justlast: Default is 1, 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 colour. 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 colour. 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=1): # Set the has_font property. self.has_font = has_font def set_has_fill(self, has_fill=1): # 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, reading_order=1): # Set the reading_order property. self.reading_order = reading_order 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=1): # Set the properties for the hyperlink style. This doesn't # currently work. To be fixed when styles are supported. self.set_underline(1) self.set_theme(10) self.set_align('top') 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 _assemble_xml_file(self): # Assemble and write the XML file. # Write the XML declaration. self._xml_declaration() # Close the file. self._xml_close() 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. 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 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)) 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 index 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 index 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 # colour name into an RGB formatted string. These colours 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-0.5.2/xlsxwriter/packager.py0000644000076500000240000004516112240540011020250 0ustar Johnstaff00000000000000############################################################################### # # Packager - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013, John McNamara, jmcnamara@cpan.org # # Standard packages. import os import sys import tempfile from shutil import copy from .compatibility import StringIO # Package imports. from xlsxwriter.app import App from xlsxwriter.contenttypes import ContentTypes from xlsxwriter.core import Core from xlsxwriter.relationships import Relationships from xlsxwriter.sharedstrings import SharedStrings from xlsxwriter.styles import Styles from xlsxwriter.theme import Theme from xlsxwriter.vml import Vml from xlsxwriter.table import Table from xlsxwriter.comments import Comments 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.sheet_names = [] 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.sheet_names = workbook.sheetnames 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_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)) 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.optimization == 1: 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: 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: continue 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_array, worksheet.buttons_array) 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_array) 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_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() 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') 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 not worksheet.drawing_links: continue index += 1 # 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 _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] os_filename = self._filename('xl/media/image' + str(index) + ext) if not self.in_memory: # In file mode we just copy the image file. copy(filename, os_filename) else: # For in-memory mode we read the image into a string. image_file = open(filename, mode='rb') image_data = image_file.read() if sys.version_info < (2, 6, 0): os_filename = StringIO(image_data) else: from io import BytesIO os_filename = BytesIO(image_data) image_file.close() index += 1 def _add_vba_project(self): # Note: not implemented yet. # Write the vbaProject.bin file. vba_project = self.workbook.vba_project if not vba_project: return # copy(vba_project, xlsx_dir + '/xl/vbaProject.bin') XlsxWriter-0.5.2/xlsxwriter/relationships.py0000644000076500000240000000650012124652617021372 0ustar Johnstaff00000000000000############################################################################### # # Relationships - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013, 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): # Add container relationship to XLSX .rels xml files. rel_type = document_schema + rel_type self.relationships.append((rel_type, target, None)) 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-0.5.2/xlsxwriter/sharedstrings.py0000644000076500000240000001070412257407375021375 0ustar Johnstaff00000000000000############################################################################### # # SharedStrings - A class for writing the Excel XLSX sharedStrings file. # # Copyright 2013, John McNamara, jmcnamara@cpan.org # # Standard packages. import re # 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) # Add attribute to preserve leading or trailing whitespace. if re.search('^\s', string) or re.search('\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-0.5.2/xlsxwriter/styles.py0000644000076500000240000005050512232025634020026 0ustar Johnstaff00000000000000############################################################################### # # Styles - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013, 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 = [] ########################################################################### # # 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: 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)]) self._xml_empty_tag('family', [('val', xf_format.font_family)]) if xf_format.font_name == 'Calibri' and not xf_format.hyperlink: self._xml_empty_tag( 'scheme', [('val', xf_format.font_scheme)]) 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. attributes = [('count', 1)] self._xml_start_tag('cellStyleXfs', attributes) self._write_style_xf() 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): # Write the style element. num_fmt_id = 0 font_id = 0 fill_id = 0 border_id = 0 attributes = [ ('numFmtId', num_fmt_id), ('fontId', font_id), ('fillId', fill_id), ('borderId', border_id), ] 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 = 0 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: 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: attributes.append(('applyAlignment', 1)) # Check for cell protection properties. protection = xf_format._get_protection_properties() if protection: attributes.append(('applyProtection', 1)) 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. attributes = [('count', 1)] self._xml_start_tag('cellStyles', attributes) self._write_cell_style() self._xml_end_tag('cellStyles') def _write_cell_style(self): # Write the element. name = 'Normal' xf_id = 0 builtin_id = 0 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 colours. # 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-0.5.2/xlsxwriter/table.py0000644000076500000240000001214012232025717017565 0ustar Johnstaff00000000000000############################################################################### # # Table - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013, 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-0.5.2/xlsxwriter/theme.py0000644000076500000240000002144112240540011017570 0ustar Johnstaff00000000000000# # Theme - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013, 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-0.5.2/xlsxwriter/utility.py0000644000076500000240000005443412260562367020225 0ustar Johnstaff00000000000000############################################################################### # # Worksheet - A class for writing Excel Worksheets. # # Copyright 2013, 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. """ 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): """ Optimised 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_num, 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 += 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) 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) return range1 + ':' + range2 def xl_color(color): # Used in conjunction with the XlsxWriter *color() methods to convert # a colour name into an RGB formatted string. These colours 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_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))) def datetime_to_excel_datetime(dt_obj, date_1904): # 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. 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): pass elif isinstance(dt_obj, datetime.date): dt_obj = datetime.datetime.fromordinal(dt_obj.toordinal()) elif isinstance(dt_obj, datetime.time): dt_obj = datetime.datetime.combine(epoch, dt_obj) else: raise TypeError("Unknown or unsupported datetime type") # Convert a Python datetime.datetime value to an Excel date number. delta = dt_obj - epoch excel_time = (delta.days + (float(delta.seconds) + float(delta.microseconds) / 1E6) / (60 * 60 * 24)) # Special case for datetime where time only has been specified and # the default date of 1900-01-01 is used. if 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-0.5.2/xlsxwriter/vml.py0000644000076500000240000003464212232026037017303 0ustar Johnstaff00000000000000############################################################################### # # Vml - A class for writing the Excel XLSX Vml file. # # Copyright 2013, 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, buttons_data): # 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 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_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_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_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[6] (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_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) XlsxWriter-0.5.2/xlsxwriter/workbook.py0000644000076500000240000012245412260562344020350 0ustar Johnstaff00000000000000############################################################################### # # Workbook - A class for writing the Excel XLSX Workbook file. # # Copyright 2013, John McNamara, jmcnamara@cpan.org # # Standard packages. import sys import re import os import operator from warnings import warn from datetime import datetime from zipfile import ZipFile, ZIP_DEFLATED from struct import unpack # Package imports. from . import xmlwriter from xlsxwriter.worksheet import Worksheet from xlsxwriter.chartsheet import Chartsheet from xlsxwriter.sharedstrings import SharedStringTable from xlsxwriter.format import Format from xlsxwriter.packager import Packager from .utility import xl_cell_to_rowcol from xlsxwriter.chart_area import ChartArea from xlsxwriter.chart_bar import ChartBar from xlsxwriter.chart_column import ChartColumn from xlsxwriter.chart_line import ChartLine from xlsxwriter.chart_pie import ChartPie from xlsxwriter.chart_radar import ChartRadar from xlsxwriter.chart_scatter import ChartScatter from xlsxwriter.chart_stock import ChartStock class Workbook(xmlwriter.XMLwriter): """ A class for writing the Excel XLSX Workbook file. """ ########################################################################### # # Public API. # ########################################################################### def __init__(self, filename=None, options={}): """ Constructor. """ 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.default_date_format = options.get('default_date_format', None) self.optimization = options.get('constant_memory', False) self.in_memory = options.get('in_memory', False) 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.localtime = datetime.now() 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 = 500 self.str_table = SharedStringTable() self.vba_project = None self.vba_codename = None self.image_types = {} self.images = [] self.border_count = 0 self.fill_count = 0 self.drawing_count = 0 # We can't do 'constant_memory' mode while doing 'in_memory' mode. if self.in_memory: self.optimization = False # Add the default cell format. self.add_format({'xf_index': 0}) # Add a default URL format. self.default_url_format = self.add_format({'color': 'blue', 'underline': 1}) # 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 __del__(self): """Close file in destructor if it hasn't been closed explicitly.""" if not self.fileclosed: self.close() def add_worksheet(self, name=None): """ Add a new worksheet to the Excel workbook. Args: name: The worksheet name. Defaults to 'Sheet1', etc. Returns: Reference to a worksheet object. """ return self._add_sheet(name, is_chartsheet=False) def add_chartsheet(self, name=None): """ Add a new chartsheet to the Excel workbook. Args: name: The chartsheet name. Defaults to 'Sheet1', etc. Returns: Reference to a chartsheet object. """ return self._add_sheet(name, is_chartsheet=True) def add_format(self, properties={}): """ Add a new Format to the Excel Workbook. Args: properties: The format properties. Returns: Reference to a Format object. """ xf_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', 'None') 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 == '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 self.charts.append(chart) return chart def close(self): """ Call finalisation code and close file. Args: None. Returns: Nothing. """ if not self.fileclosed: self.fileclosed = 1 self._store_workbook() 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 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()" % 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'" % 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'" % 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()" % 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 ########################################################################### # # 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) # Add XML sub-files to the Zip file with their Excel filename. for os_filename, xml_filename in xml_files: if self.in_memory: # The files are in-memory StringIOs. xlsx_file.writestr(xml_filename, os_filename.getvalue().encode('utf-8')) else: # The files are tempfiles. xlsx_file.write(os_filename, xml_filename) os.remove(os_filename) xlsx_file.close() def _add_sheet(self, name, is_chartsheet): # Utility for shared code in add_worksheet() and add_chartsheet(). sheet_index = len(self.worksheets_objs) name = self._check_sheetname(name, is_chartsheet) # Initialisation data to pass to the worksheet. init_data = { 'name': name, 'index': sheet_index, 'str_table': self.str_table, 'worksheet_meta': self.worksheet_meta, 'optimization': self.optimization, '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, 'default_date_format': self.default_date_format, 'default_url_format': self.default_url_format, } if is_chartsheet: worksheet = Chartsheet() else: worksheet = Worksheet() worksheet._initialize(init_data) self.worksheets_objs.append(worksheet) self.sheetnames.append(name) 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: 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 Exception("Excel worksheet name '%s' must be <= 31 chars." % sheetname) # Check that sheetname doesn't contain any invalid characters if invalid_char.search(sheetname): raise Exception( "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 Exception( "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 records is not None start from index 0xA4. num_formats = {} index = 164 num_format_count = 0 is_number = re.compile(r'^\d+$') is_zeroes = re.compile(r'^0+\d') 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. # Also check for a string of zeros, which is a valid number # format string but would evaluate to zero. try: if (is_number.match(str(num_format)) and not is_zeroes.match(str(num_format))): # Index to a built-in number xf_format. xf_format.num_format_index = int(num_format) continue except (TypeError, UnicodeEncodeError): pass 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 colours 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 colours and patterns: # 1. For a solid fill (_pattern == 1) Excel reverses the role of # foreground and background colours, and # 2. If the user specifies a foreground or background colour # 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 normalise name string to each list for sorting. for name_list in names: (defined_name, _, sheet_name, _) = name_list # Normalise the defined name by removing any leading '_xmln.' # from internal names and lowercasing the string. defined_name = defined_name.replace('_xlnm.', '').lower() # Normalise the sheetname by removing the leading quote and # lowercasing the string. sheet_name = sheet_name.lstrip("'").lower() name_list.append(defined_name + "::" + sheet_name) # Remove the extra key for sorting. names.sort(key=operator.itemgetter(4)) 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 for sheet in self.worksheets(): chart_count = len(sheet.charts) image_count = len(sheet.images) shape_count = len(sheet.shapes) if not (chart_count + image_count + shape_count): continue drawing_id += 1 for index in range(chart_count): chart_ref_id += 1 sheet._prepare_chart(index, chart_ref_id, drawing_id) for index in range(image_count): filename = sheet.images[index][2] (image_type, width, height, name) = \ self._get_image_properties(filename) image_ref_id += 1 sheet._prepare_image(index, image_ref_id, drawing_id, width, height, name, image_type) # for index in range(shape_count): # sheet._prepare_shape(index, drawing_id) drawing = sheet.drawing self.drawings.append(drawing) # Sort the workbook charts references into the order that the were # written from the worksheets above. self.charts = sorted(self.charts, key=lambda chart: chart.id) self.drawing_count = drawing_id def _get_image_properties(self, filename): # Extract dimension information from the image file. height = 0 width = 0 # Open the image file and read in the data. fh = open(filename, "rb") data = fh.read() # 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] if sys.version_info < (2, 6, 0): # Python 2.5/Jython. png_marker = 'PNG' bmp_marker = 'BM' else: # Eval the binary literals for Python 2.5/Jython compatibility. png_marker = eval("b'PNG'") bmp_marker = eval("b'BM'") if marker1 == png_marker: self.image_types['png'] = 1 (image_type, width, height) = self._process_png(data) elif marker2 == 0xFFD8: self.image_types['jpeg'] = 1 (image_type, width, height) = self._process_jpg(data) elif marker3 == bmp_marker: self.image_types['bmp'] = 1 (image_type, width, height) = self._process_bmp(data) else: raise Exception("%s: Unknown or unsupported image file format." % filename) # Check that we found the required data. if not height or not width: raise Exception("%s: no size data found in image file." % filename) # Store image data to copy it into file container. self.images.append([filename, image_type]) fh.close() return image_type, width, height, image_name def _process_png(self, data): # Extract width and height information from a PNG file. width = (unpack('>I', data[16:20]))[0] height = (unpack('>I', data[20:24]))[0] return 'png', width, height def _process_jpg(self, data): # Extract width and height information from a JPEG file. offset = 2 data_length = len(data) # Search through the image data to find the 0xFFC0 marker. # The height and width are contained in the data for that # sub-element. found = 0 width = 0 height = 0 while not found and offset < data_length: marker = (unpack('>H', data[offset + 0:offset + 2]))[0] length = (unpack('>H', data[offset + 2:offset + 4]))[0] if marker == 0xFFC0 or marker == 0xFFC2: height = (unpack('>H', data[offset + 5:offset + 7]))[0] width = (unpack('>H', data[offset + 7:offset + 9]))[0] found = 1 continue offset = offset + length + 2 if marker == 0xFFDA: found = 1 continue return 'jpeg', width, height 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() def _prepare_tables(self): # Set the table ids for the worksheet tables. table_id = 0 for sheet in self.worksheets(): table_count = len(sheet.tables) if not table_count: continue sheet._prepare_tables(table_id + 1) table_id += table_count def _add_chart_data(self): # Add "cached" data to charts to provide the numCache and strCacher # data for series and title/axis ranges. worksheets = {} seen_ranges = {} # Map worksheet names to worksheet objects. for worksheet in self.worksheets(): worksheets[worksheet.name] = worksheet for chart in self.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 (not c_range 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 not sheetname in worksheets: warn("Unknown worksheet reference '%s' in range " "'%s' passed to add_series()" % (sheetname, 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 '!'. # TODO. Fix this to match from right. pos = c_range.find('!') if pos > 0: sheetname, cells = c_range.split('!', 1) else: return 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("''", "'") (row_start, col_start) = xl_cell_to_rowcol(cell_1) (row_end, col_end) = xl_cell_to_rowcol(cell_2) # Check that we have a 1D range only. if row_start != row_end and col_start != col_end: return 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 != 500: 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', '124519'), ('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-0.5.2/xlsxwriter/worksheet.py0000644000076500000240000060736112260562367020540 0ustar Johnstaff00000000000000############################################################################### # # Worksheet - A class for writing the Excel XLSX Worksheet file. # # Copyright 2013, John McNamara, jmcnamara@cpan.org # # Standard packages. import re import datetime import tempfile import codecs import os import sys from decimal import Decimal from warnings import warn # Standard packages in Python 2/3 compatibility mode. from .compatibility import StringIO from .compatibility import Fraction from .compatibility import defaultdict from .compatibility import namedtuple # Package imports. from . import xmlwriter from .format import Format from .drawing import Drawing 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 ############################################################################### # # 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): int(args[0]) return method(self, *args, **kwargs) except ValueError: # First arg isn't an int, convert to A1 notation. new_args = list(xl_cell_to_rowcol(args[0])) new_args.extend(args[1:]) return method(self, *new_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]) return method(self, *args, **kwargs) 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:]) return method(self, *new_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]) return method(self, *args, **kwargs) 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:]) return method(self, *new_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.optimization = 0 self.tmpdir = None self.is_chartsheet = False self.ext_sheets = [] self.fileclosed = 0 self.excel_version = 2007 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.activesheet = 0 self.firstsheet = 0 self.page_setup_changed = 0 self.paper_size = 0 self.orientation = 1 self.print_options_changed = 0 self.hcenter = 0 self.vcenter = 0 self.print_gridlines = 0 self.screen_gridlines = 1 self.print_headers = 0 self.header_footer_changed = 0 self.header = '' self.footer = '' 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 = 0 self.default_row_height = 15 self.default_row_zeroed = 0 self.names = {} self.write_match = [] self.table = defaultdict(dict) self.merge = [] self.row_spans = {} self.has_vml = 0 self.has_comments = 0 self.comments = defaultdict(dict) self.comments_array = [] self.comments_author = '' self.comments_visible = 0 self.vml_shape_id = 1024 self.buttons_array = [] self.autofilter_area = '' self.autofilter_ref = None self.filter_range = [] self.filter_on = 0 self.filter_range = [] self.filter_cols = {} self.filter_type = {} self.col_sizes = {} self.row_sizes = {} self.col_formats = {} self.col_size_changed = 0 self.row_size_changed = 0 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.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.dxf_priority = 1 self.is_chartsheet = 0 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.strings_to_formulas = True self.default_date_format = None self.default_url_format = None 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 @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. """ # 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] # Types to check in Python 2/3. if sys.version_info[0] == 2: num_types = (float, int, long, Decimal, Fraction) str_types = basestring date_types = (datetime.datetime, datetime.date, datetime.time) else: num_types = (float, int, Decimal, Fraction) str_types = str date_types = (datetime.datetime, datetime.date, datetime.time) # Write None as a blank cell. if token is None: return self.write_blank(row, col, *args) # Write boolean types. if isinstance(token, bool): return self.write_boolean(row, col, *args) # Write datetime objects. if isinstance(token, date_types): return self.write_datetime(row, col, *args) # Write number types. if isinstance(token, num_types): return self.write_number(row, col, *args) # Write string types. if isinstance(token, str_types): # Map the data to the appropriate write_*() method. if token == '': return self.write_blank(row, col, *args) elif self.strings_to_formulas and token.startswith('='): return self.write_formula(row, col, *args) elif 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) elif self.strings_to_numbers: try: f = float(token) if 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) # We haven't matched a supported type. Try float. try: f = float(token) if not self._isnan(f) and not self._isinf(f): return self.write_number(row, col, f, *args[1:]) except ValueError: pass # 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. """ 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 optimisation mode. if self.optimization == 0: string_index = self.str_table._get_shared_string_index(string) else: string_index = string # Write previous row if in in-line string optimization mode. if self.optimization 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. """ if self._isnan(number) or self._isinf(number): raise TypeError("NAN/INF not supported in write_number()") # 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 optimization mode. if self.optimization 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. """ # 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 optimization mode. if self.optimization 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. 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 optimization mode. if self.optimization 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. """ # 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 optimization mode. if self.optimization 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.optimization: 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. """ # 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 optimization mode. if self.optimization 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. """ # 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 optimization mode. if self.optimization 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 """ # Default link type such as http://. link_type = 1 # Remove the URI scheme from internal links. if re.match("internal:", url): url = url.replace('internal:', '') link_type = 2 # Remove the URI scheme from external links. if re.match("external:", url): url = url.replace('external:', '') link_type = 3 # Set the displayed string to the URL unless defined by the user. if string is None: string = url # For external links change the directory separator from Unix to Dos. if link_type == 3: url = url.replace('/', '\\') string = string.replace('/', '\\') # Strip the mailto header. string = string.replace('mailto:', '') # 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 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: # Escape URL unless it looks already escaped. if not re.search('%[0-9a-fA-F]{2}', 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') # Ordinary URL style external links don't have a "location" string. url_str = None elif link_type == 3: # External Workbook links need to be modified into correct format. # The URL will look something like 'c:\temp\file.xlsx#Sheet!A1'. # We need the part to the left of the # as the URL and the part to # the right as the "location" string (if it exists). if re.search('#', url): url, url_str = url.split('#') else: url_str = None # Add the file:/// URI to the url if non-local. # Windows style "C:/" link. # Network share. if re.match('\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) # Treat as a default external link now the data has been modified. link_type = 1 # Excel limits escaped URL to 255 characters. if len(url) > 255: warn("Ignoring URL '%s' > 255 characters since it exceeds " "Excel's limit for URLS" % 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." % url) return -5 # Write previous row if in in-line string optimization mode. if self.optimization == 1 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. """ 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 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) # 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: 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('^\s', token) or re.search('\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 optimisation mode. if self.optimization == 0: string_index = self.str_table._get_shared_string_index(string) else: string_index = string # Write previous row if in in-line string optimization mode. if self.optimization 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, image, options={}): """ 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). image: Path and filename for image in PNG, JPG or BMP format. options: Position and scale of the image. Returns: 0: Success. """ 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) # if not -e image: # croak "Couldn't locate image: $!" self.images.append([row, col, image, x_offset, y_offset, x_scale, y_scale]) @convert_cell_args def insert_chart(self, row, col, chart, options={}): """ 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. """ 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: x_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={}): """ 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. """ # 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 self.activesheet = 0 self.firstsheet = 0 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, firstcol, lastcol, width=None, cell_format=None, options={}): """ Set the width, and other properties of a single column or a range of columns. Args: firstcol: First column (zero-indexed). lastcol: Last column (zero-indexed). Can be same as firstcol. 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. """ # Ensure 2nd col is larger than first. if firstcol > lastcol: (firstcol, lastcol) = (lastcol, firstcol) # 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, lastcol, ignore_row, ignore_col): return -1 if self._check_dimensions(0, firstcol, 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" % firstcol] = [firstcol, lastcol, width, cell_format, hidden, level, collapsed] # Store the column change to allow optimisations. self.col_size_changed = 1 # 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(firstcol, lastcol + 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={}): """ Set the width, and other properties of a row. range of columns. 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. """ # 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 optimisations. self.row_size_changed = 1 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=15, 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 != 15: # Store the row change to allow optimisations. self.row_size_changed = 1 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, first_col): 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): """ 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 # List of valid input parameters. valid_parameters = { 'validate': 1, 'criteria': 1, 'value': 1, 'source': 1, 'minimum': 1, 'maximum': 1, 'ignore_blank': 1, 'dropdown': 1, 'show_input': 1, 'input_title': 1, 'input_message': 1, 'show_error': 1, 'error_title': 1, 'error_message': 1, 'error_type': 1, 'other_cells': 1, } # Check for valid input parameters. for param_key in options.keys(): if not param_key in valid_parameters: warn("Unknown parameter 'param_key' in data_validation()") 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 not 'validate' 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 options['validate'] == 'none': return -2 # The list and custom validations don't have a criteria so we use # a default of 'between'. if options['validate'] == 'list' or options['validate'] == 'custom': options['criteria'] = 'between' options['maximum'] = None # 'criteria' is a required parameter. if not 'criteria' in options: warn("Parameter 'criteria' is required in data_validation()") return -2 # List of 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 not 'maximum' in options: warn("Parameter 'maximum' is required in data_validation() " "when using 'between' or 'not between' criteria") return -2 else: options['maximum'] = None # List of valid error dialog types. error_types = { 'stop': 0, 'warning': 1, 'information': 2, } # Check for valid error dialog types. if not 'error_type' 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 not supported_datetime(options['value']): warn("Data validation 'value/minimum' 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'] = "%.15g" % date_time if options['maximum']: 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'] = "%.15g" % date_time # Set some defaults if they haven't been defined by the user. if not 'ignore_blank' in options: options['ignore_blank'] = 1 if not 'dropdown' in options: options['dropdown'] = 1 if not 'show_input' in options: options['show_input'] = 1 if not 'show_error' 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 = {} # Copy the user defined options so they aren't modified. options = options.copy() # List of valid input parameters. valid_parameter = { 'type': 1, 'format': 1, 'criteria': 1, 'value': 1, 'minimum': 1, 'maximum': 1, 'min_type': 1, 'mid_type': 1, 'max_type': 1, 'min_value': 1, 'mid_value': 1, 'max_value': 1, 'min_color': 1, 'mid_color': 1, 'max_color': 1, 'multi_range': 1, 'bar_color': 1} # Check for valid input parameters. for param_key in options.keys(): if param_key not in valid_parameter: warn("Unknown parameter '%s' in conditional_formatting()" % param_key) return -2 # 'type' is a required parameter. if 'type' not in options: warn("Parameter 'type' is required in conditional_formatting()") return -2 # List of valid validation 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'} # Check for valid validation types. if options['type'] not in valid_type: warn("Unknown validation type '%s' for parameter 'type' " "in conditional_formatting()" % options['type']) return -2 else: if options['type'] == 'bottom': options['direction'] = 'bottom' options['type'] = valid_type[options['type']] # List of 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', 'continue week': 'continueWeek', 'last month': 'lastMonth', 'this month': 'thisMonth', 'continue month': 'continueMonth'} # 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'] = "%.15g" % 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'] = "%.15g" % 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'] = "%.15g" % date_time # 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 # 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 'options['criteria']' " "in conditional_formatting()") # 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'] == 'continueWeek': 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'] == 'continueMonth': 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 'options['criteria']' " "in conditional_formatting()") # 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 not 'mid_value' 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 options.setdefault('min_type', 'min') options.setdefault('max_type', 'max') options.setdefault('min_value', 0) options.setdefault('max_value', 0) options.setdefault('bar_color', '#638EC6') options['bar_color'] = xl_color(options['bar_color']) # Store the validation information 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 optimisation mode. -2: Row or column is out of worksheet bounds. -3: Incorrect parameter or option. """ table = {} col_formats = {} if options is None: options = {} if self.optimization == 1: warn("add_table() isn't supported when set_optimization() is on") 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 # List of valid input parameters. valid_parameter = { 'autofilter': 1, 'banded_columns': 1, 'banded_rows': 1, 'columns': 1, 'data': 1, 'first_column': 1, 'header_row': 1, 'last_column': 1, 'name': 1, 'style': 1, 'total_row': 1, } # Check for valid input parameters. for param_key in options.keys(): if not param_key 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: table['name'] = options['name'] # 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 'header_row' in options: first_data_row += 1 if 'total_row' in options: 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'] = [] 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': '', 'formula': '', 'format': None, } # Overwrite the defaults with any use defined values. if 'columns' in options: # Check if there are user defined values for this column. user_data = options['columns'][col_id - 1] 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'] # 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']) self.write_formula(last_row, col_num, formula, xformat) 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_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): """ 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)]} # List of 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 not param_key in valid_parameters: warn("Unknown parameter '%s' in add_sparkline()" % param_key) return -1 # 'range' is a required parameter. if not 'range' 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 = self._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 colours with user defined colours. 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, outline_on=1, outline_below=1, outline_right=1, outline_style=0): """ Control outline settings. Args: outline_on: Outlines are visible. Optional, defaults to True. outline_below: Show row outline symbols below the outline bar. Optional, defaults to True. outline_right: Show column outline symbols to the right of the outline bar. Optional, defaults to True. outline_style: Use Automatic style. Optional, defaults to False. Returns: 0: Nothing. """ self.outline_on = outline_on self.outline_below = outline_below self.outline_right = outline_right self.outline_style = outline_style self.outline_changed = 1 @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 colour 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': 1, 'content': 0, 'objects': 0, 'scenarios': 0, 'format_cells': 0, 'format_columns': 0, 'format_rows': 0, 'insert_columns': 0, 'insert_rows': 0, 'insert_hyperlinks': 0, 'delete_columns': 0, 'delete_rows': 0, 'select_locked_cells': 1, 'sort': 0, 'autofilter': 0, 'pivot_tables': 0, 'select_unlocked_cells': 1} # 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 ########################################################################### # # 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 = 1 def set_portrait(self): """ Set the page orientation as portrait. Args: None. Returns: Nothing. """ self.orientation = 1 self.page_setup_changed = 1 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 = 1 def center_horizontally(self): """ Center the page horizontally. Args: None. Returns: Nothing. """ self.print_options_changed = 1 self.hcenter = 1 def center_vertically(self): """ Center the page vertically. Args: None. Returns: Nothing. """ self.print_options_changed = 1 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='', margin=0.3): """ Set the page header caption and optional margin. Args: header: Header string. margin: Header margin. Returns: Nothing. """ if len(header) >= 255: warn('Header string must be less than 255 characters') return self.header = header self.margin_header = margin self.header_footer_changed = 1 def set_footer(self, footer='', margin=0.3): """ Set the page footer caption and optional margin. Args: footer: Footer string. margin: Footer margin. Returns: Nothing. """ if len(footer) >= 255: warn('Footer string must be less than 255 characters') return self.footer = footer self.margin_footer = margin self.header_footer_changed = 1 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 = self._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 = self._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 = 1 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 = 1 self.print_options_changed = 1 @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 = 1 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 = 1 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 self.custom_start = 1 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 = 1 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 # # set_v_pagebreaks(@breaks) # # Store the vertical page breaks on a worksheet. # 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 ########################################################################### # # 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.optimization = init_data['optimization'] 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.default_date_format = init_data['default_date_format'] self.default_url_format = init_data['default_url_format'] # Open a temp filehandle to store row data in optimization mode. if self.optimization == 1: # 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 self.optimization == 0: self._write_sheet_data() else: self._write_optimized_sheet_data() # Write the sheetProtection element. self._write_sheet_protection() # 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 tableParts element. self._write_table_parts() # Write the extLst and sparklines. self._write_ext_sparklines() # 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 >= self.xls_rowmax or col >= self.xls_colmax: return -1 # In optimization mode we don't change dimensions for rows # that are already written. if not ignore_row and not ignore_col and self.optimization == 1: if row < self.previous_row: return -1 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) def _options_changed(self): # Check to see if any of the worksheet options have changed. options_changed = 0 print_changed = 0 setup_changed = 0 if (self.orientation == 0 or self.hcenter == 1 or self.vcenter == 1 or self.header != '' or self.footer != '' or self.margin_header != 0.50 or self.margin_footer != 0.50 or self.margin_left != 0.75 or self.margin_right != 0.75 or self.margin_top != 1.00 or self.margin_bottom != 1.00): setup_changed = 1 # Special case for 1x1 page fit. if self.fit_width == 1 and self.fit_height == 1: options_changed = 1 self.fit_width = 0 self.fit_height = 0 if (self.fit_width > 1 or self.fit_height > 1 or self.page_order == 1 or self.black_white == 1 or self.draft_quality == 1 or self.print_comments == 1 or self.paper_size != 0 or self.print_scale != 100 or self.print_gridlines == 1 or self.print_headers == 1 or self.hbreaks > 0 or self.vbreaks > 0): print_changed = 1 if print_changed or setup_changed: options_changed = 1 if self.screen_gridlines == 0: options_changed = 1 if self.filter_on: options_changed = 1 return options_changed, print_changed, setup_changed def _quote_sheetname(self, sheetname): # Sheetnames used in references should be quoted if they # contain any spaces, special characters or if the look like # something that isn't a sheet name. # TODO. Probably need to handle more special cases. if re.match(r'Sheet\d+', sheetname): return sheetname else: return "'%s'" % sheetname 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 = self._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('(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): # Set up images/drawings. drawing_type = 2 (row, col, _, x_offset, y_offset, x_scale, y_scale) = \ self.images[index] 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 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(['/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 # 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: # Optimisation for when the column widths haven't changed. x_abs += 64 * col_start x_abs += x1 # Calculate the absolute y offset of the top-left vertex. # Store the column change to allow optimisations. if self.row_size_changed: for row_id in range(row_start): y_abs += self._size_row(row_id) else: # Optimisation for when the row heights haven't changed. y_abs += 20 * 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 # Initialise 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 = 64 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, } # 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 colour. params['color'] = xl_color(params['color']).lower() # Convert from Excel XML style colour to XML html style colour. 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 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']] + [vertices]) def _prepare_vml_objects(self, vml_data_id, vml_shape_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(comment_id) + '.vml']) if self.has_comments: self.comments_array = 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_tables(self, table_id): # 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) # 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 = '' 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 colour. if not user_color 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.optimization: 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 not row_num 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] if type(cell).__name__ == 'Number': # Return a number with Excel's precision. data.append("%.15g" % 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 ########################################################################### # # 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 optimization 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 optimization 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 ########################################################################### # # 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 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 != 15: 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', "%.15g" % 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 the memory optimisation 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 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', self.page_start)) 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. if not self.header_footer_changed: return self._xml_start_tag('headerFooter') if self.header: self._write_odd_header() if self.footer: self._write_odd_footer() self._xml_end_tag('headerFooter') 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 memory optimisation 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 optimised 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 optimised 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 optimisation 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 != 15: attributes.append(('ht', height)) if hidden: attributes.append(('hidden', 1)) if height != 15: 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())) # 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.optimization: # Write a shared string. self._xml_string_element(string, attributes) else: # Write an optimised 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) # 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('^\s', string) or re.search('\s$', string): preserve = 1 self._xml_inline_string(string, preserve, attributes) elif type(cell).__name__ == 'Formula': # Write a formula. First check if the formula value is a string. try: float(cell.value) except ValueError: attributes.append(('t', 'str')) self._xml_formula_element(cell.formula, cell.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 not col 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. if len(filters) == 1 and filters[0] == 'blanks': # Special case for blank cells only. self._xml_empty_tag('filters', [('blank', 1)]) else: # General case. self._xml_start_tag('filters') for autofilter in filters: 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_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) 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)) 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 = ','.join([str(item) for item in 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['type'] == 'cellIs': attributes.append(('operator', params['criteria'])) self._xml_start_tag('cfRule', attributes) if 'minimum' in params and 'maximum' in params: self._write_formula(params['minimum']) self._write_formula(params['maximum']) else: self._write_formula(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(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(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(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) self._xml_end_tag('cfRule') elif params['type'] == 'expression': self._xml_start_tag('cfRule', attributes) self._write_formula(params['criteria']) self._xml_end_tag('cfRule') def _write_formula(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. self._xml_start_tag('dataBar') 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_cfvo(self, cf_type, val): # Write the element. attributes = [('type', cf_type), ('val', val)] 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', "%.15g" % x_split)) if y_split: attributes.append(('ySplit', "%.15g" % 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_sparklines(self): # Write the element and sparkline sub-elements. sparklines = self.sparklines count = len(sparklines) # Return if worksheet doesn't contain any sparklines. if not count: return # Write the extLst element. self._xml_start_tag('extLst') # Write the ext element. self._write_ext() # Write the x14:sparklineGroups element. self._write_sparkline_groups() # Write the sparkline elements. for sparkline in reversed(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') self._xml_end_tag('extLst') 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): # Write the element. schema = 'http://schemas.microsoft.com/office/' xmlns_x_14 = schema + 'spreadsheetml/2009/9/main' uri = '{05C60535-1F16-4fd2-B633-F4F36F0B64E0}' attributes = [ ('xmlns:x14', xmlns_x_14), ('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'): if options['max'] == 'group': options['cust_max'] = 'group' else: attributes.append(('manualMax', options['max'])) options['cust_max'] = 'custom' if options.get('min'): 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) XlsxWriter-0.5.2/xlsxwriter/xmlwriter.py0000644000076500000240000001526412240540011020531 0ustar Johnstaff00000000000000############################################################################### # # XMLwriter - A base class for XlsxWriter classes. # # Used in conjunction with XlsxWriter. # # Copyright 2013, 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('["&<>]') 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 optimisation 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 XML start tag with optional, unencoded, attributes. # This is a minor speed optimisation 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=[]): # Optimised 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=[]): # Optimised 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): # Optimised tag writer for shared strings rich string elements. self.fh.write("""%s""" % string) def _xml_number_element(self, number, attributes=[]): # Optimised 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("""%.15g""" % (attr, number)) def _xml_formula_element(self, formula, result, attributes=[]): # Optimised 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=[]): # Optimised 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=[]): # Optimised 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 = attribute.replace('&', '&') attribute = attribute.replace('"', '"') attribute = attribute.replace('<', '<') attribute = attribute.replace('>', '>') return attribute def _escape_data(self, data): # Escape XML characters in data sections of tags. Note, this # is different from _escape_attributes() in that double quotes # are not escaped by Excel. try: if not self.escapes.search(data): return data except TypeError: return data data = data.replace('&', '&') data = data.replace('<', '<') data = data.replace('>', '>') return data