pax_global_header00006660000000000000000000000064122451447520014520gustar00rootroot0000000000000052 comment=b8afda42c5a2b8d462d4fc10333e12c9980a5048 openpyxl-1.7.0+ds1/000077500000000000000000000000001224514475200140665ustar00rootroot00000000000000openpyxl-1.7.0+ds1/.hgignore000066400000000000000000000003421224514475200156700ustar00rootroot00000000000000syntax: regexp \.pyc$ .coverage org.eclipse \.pytest (build|dist|openpyxl.egg-info) \.(dot|svg|pf) \.noseids \.*\.swp \.*\.DS_Store \.*\.~.* syntax: glob *~ *.orig .idea *.pstats *.png *.sh .git travis.yml openpyxl-1.7.0+ds1/.travis.yml000066400000000000000000000003071224514475200161770ustar00rootroot00000000000000language: python python: - "2.5" - "2.6" - "2.7" # - "3.2" - "3.3" # command to run tests script: nosetests notifications: email: recipients: - charlie.clark@clark-consulting.eu openpyxl-1.7.0+ds1/AUTHORS000066400000000000000000000022301224514475200151330ustar00rootroot00000000000000This project is developed by Eric Gazoni. It is *heavily* inspired by the PHPExcel library that can be found here: http://www.phpexcel.net/ Kudos goes to the PHPExcel team, I'm only standing on their shoulders. I'd also like to greatly thank all those who participate in the project (in alphabetical order): * Stephane Bard * Day Barr * Bernt R. Brenna * Anders Chrigstrom * ccoacley * Charlie Clark * Maarten De Paepe * Etienne Desautels * echlebek_ * Alexandre Fayolle * Eric Gazoni * Brice Gelineau * Alex Gronholm * Yaroslav Halchenko * Jeff Holman * Brent Hoover * JarekPS * Heikki Junes * Chi Ho Kwok * Yingjie Lan * Greg Lehmann * Adam Lofts * Marko Loparic * Samuel Loretan * Amin Mirzaee * Adam Morris * aceMueller * Gabi Nagy * Jun Omae * Waldemar Osuch * Jonathan Peirce * Ted Pollari * Rick Rankin * ramn_se * Philip Roche * James Smagala * Joseph Tate * Dieter Vandenbussche * Paul Van Der Linden * Gerald Van Huffelen * Laurent Vasseur * Kay Webber Special thanks for Heikki Junes, for all his work on porting and maintaining the Python3.x fork Project logo designed by Eric Gazoni, font by claudeserieux (http://www.dafont.com/profile.php?user=337503) openpyxl-1.7.0+ds1/CHANGES000066400000000000000000000015771224514475200150730ustar00rootroot000000000000001.7.0 (2013-10-31) ================== Major changes ------------- Drops support for Python < 2.5 and last version to support Python 2.5 Compatibility ------------- Tests run on Python 2.5, 2.6, 2.7, 3.2, 3.3 Merged pull requests -------------------- #27 Include more metadata #41 Able to read files with chart sheets #45 Configurable Worksheet classes #3 Correct serialisation of Decimal #36 Preserve VBA macros when reading files #44 Handle empty oddheader and oddFooter tags #43 Fixed issue that the reader never set the active sheet #33 Reader set value and type explicitly and TYPE_ERROR checking #22 added page breaks, fixed formula serialization #39 Fix Python 2.6 compatibility #47 Improvements in styling Known bugfixes -------------- #109 #165 #179 #209 #112 #166 #109 #223 #124 #157 Miscellaneous ------------- Performance improvements in optimised writer Docs updated openpyxl-1.7.0+ds1/LICENCE000066400000000000000000000033001224514475200150470ustar00rootroot00000000000000This software is under the MIT Licence ====================================== Copyright (c) 2010 openpyxl Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Odict implementation in openpyxl/writer/odict.py uses the following licence: Copyright (c) 2001-2011 Python Software Foundation 2011 Raymond Hettinger License: PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 See http://www.opensource.org/licenses/Python-2.0 for full terms Note: backport changes by Raymond were originally distributed under MIT license, but since the original license for Python is more restrictive than MIT, code cannot be released under its terms and still adheres to the limitations of Python license. openpyxl-1.7.0+ds1/MANIFEST.in000066400000000000000000000001241224514475200156210ustar00rootroot00000000000000prune openpyxl/tests include LICENCE include AUTHORS include README include CHANGES openpyxl-1.7.0+ds1/README000066400000000000000000000011151224514475200147440ustar00rootroot00000000000000OpenPyxl is a Python library to read/write Excel 2007 xlsx/xlsm files. It was born from lack of existing library to read/write natively from Python the new Open Office XML format. All kudos to the PHPExcel team as openpyxl is a Python port of PHPExcel http://www.phpexcel.net/ == Mailing List == Official user list can be found on http://groups.google.com/group/openpyxl-users == Official documentation == The new homepage is now http://openpyxl.readthedocs.org You will find: * every installation methods * the official documentation * code examples * instructions for contributingopenpyxl-1.7.0+ds1/doc/000077500000000000000000000000001224514475200146335ustar00rootroot00000000000000openpyxl-1.7.0+ds1/doc/Makefile000066400000000000000000000107721224514475200163020ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/openpyxl.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/openpyxl.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/openpyxl" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/openpyxl" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." openpyxl-1.7.0+ds1/doc/make.bat000066400000000000000000000102551224514475200162430ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\openpyxl.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\openpyxl.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end openpyxl-1.7.0+ds1/doc/source/000077500000000000000000000000001224514475200161335ustar00rootroot00000000000000openpyxl-1.7.0+ds1/doc/source/_static/000077500000000000000000000000001224514475200175615ustar00rootroot00000000000000openpyxl-1.7.0+ds1/doc/source/_static/.placeholder000066400000000000000000000000001224514475200220320ustar00rootroot00000000000000openpyxl-1.7.0+ds1/doc/source/api.rst000066400000000000000000000027341224514475200174440ustar00rootroot00000000000000Module :mod:`openpyxl.workbook` -- Workbook ============================================================= .. autoclass:: openpyxl.workbook.Workbook :members: Module :mod:`openpyxl.worksheet` -- Worksheet ============================================================= .. autoclass:: openpyxl.worksheet.Worksheet :members: Module :mod:`openpyxl.reader.iter_worksheet` -- Optimized reader ================================================================ .. autoclass:: openpyxl.reader.iter_worksheet.IterableWorksheet :members: .. autoclass:: openpyxl.reader.iter_worksheet.RawCell :members: Module :mod:`openpyxl.cell` -- Worksheet Cell ============================================================= .. autoclass:: openpyxl.cell.Cell :members: Module :mod:`openpyxl.reader.excel` -- Filesystem reader ============================================================= .. autofunction:: openpyxl.reader.excel.load_workbook Module :mod:`openpyxl.writer.dump_worksheet` -- Optimized writer ================================================================= .. autoclass:: openpyxl.writer.dump_worksheet.DumpWorksheet :members: Module :mod:`openpyxl.datavalidation` ===================================== .. autoclass:: openpyxl.datavalidation.DataValidation :members: .. autoclass:: openpyxl.datavalidation.ValidationType :members: .. autoclass:: openpyxl.datavalidation.ValidationOperator :members: openpyxl-1.7.0+ds1/doc/source/conf.py000066400000000000000000000166411224514475200174420ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # openpyxl documentation build configuration file, created by # sphinx-quickstart on Fri Sep 10 09:50:03 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os import os.path as osp # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) up = osp.dirname sys.path.insert(0, osp.abspath(osp.join(up(up(os.getcwd())), '.'))) import openpyxl # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'openpyxl' copyright = u'2010, %s' % openpyxl.__author__ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = openpyxl.__version__ # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = 'logo.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'openpyxldoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'openpyxl.tex', u'openpyxl Documentation', u'Eric Gazoni', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'openpyxl', u'openpyxl Documentation', [u'Eric Gazoni'], 1) ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} openpyxl-1.7.0+ds1/doc/source/development.rst000066400000000000000000000024541224514475200212140ustar00rootroot00000000000000Development ================ With the ongoing development of openpyxl, there is occasional information useful to assist developers. This documentation contains examples for making the development process easier. Benchmarking ----------------- As openpyxl does not include any internal memory benchmarking tools, the python *pympler* package was used during the testing of styles to profile the memory usage in :func:`openpyxl.reader.excel.read_style_table()`:: # in openpyxl/reader/style.py from pympler import muppy, summary def read_style_table(xml_source): ... if cell_xfs is not None: # ~ line 47 initialState = summary.summarize(muppy.get_objects()) # Capture the initial state for index, cell_xfs_node in enumerate(cell_xfs_nodes): ... table[index] = new_style finalState = summary.summarize(muppy.get_objects()) # Capture the final state diff = summary.get_diff(initialState, finalState) # Compare summary.print_(diff) *pympler.summary.print_()* prints to the console a report of object memory usage, allowing the comparison of different methods and examination of memory usage. A useful future development would be to construct a benchmarking package to measure the performance of different components.openpyxl-1.7.0+ds1/doc/source/index.rst000066400000000000000000000103701224514475200177750ustar00rootroot00000000000000:mod:`openpyxl` - A Python library to read/write Excel 2007 xlsx/xlsm files ============================================================================= .. module:: openpyxl .. moduleauthor:: Eric Gazoni :Author: Eric Gazoni :Source code: http://bitbucket.org/ericgazoni/openpyxl/src :Issues: http://bitbucket.org/ericgazoni/openpyxl/issues :Generated: |today| :License: MIT/Expat :Version: |release| Introduction ------------ OpenPyxl is a Python library to read/write Excel 2007 xlsx/xlsm files. It was born from lack of existing library to read/write natively from Python the new Open Office XML format. All kudos to the PHPExcel team as openpyxl is a Python port of PHPExcel http://www.phpexcel.net/ User List --------- Official user list can be found on http://groups.google.com/group/openpyxl-users How to Contribute Code ---------------------- Any help will be greatly appreciated, just follow those steps: 1. Please start a new fork (https://bitbucket.org/ericgazoni/openpyxl/fork) for each independant feature, don't try to fix all problems at the same time, it's easier for those who will review and merge your changes ;-) 2. Hack hack hack 3. Don't forget to add unit tests for your changes ! (YES, even if it's a one-liner, or there is a high probability your work will not be taken into consideration). There are plenty of examples in the /test directory if you lack know-how or inspiration. 4. If you added a whole new feature, or just improved something, you can be proud of it, so add yourself to the AUTHORS file :-) 5. Let people know about the shiny thing you just implemented, update the docs ! 6. When it's done, just issue a pull request (click on the large "pull request" button on *your* repository) and wait for your code to be reviewed, and, if you followed all theses steps, merged into the main repository. .. note: This is an open-source project, maintained by volunteers on their spare time, so while we try to work on this project as often as possible, sometimes life gets in the way. Please be patient. Other ways to help ------------------ There are several ways to contribute, even if you can't code (or can't code well): - triaging bugs on the bug tracker: closing bugs that have already been closed, are not relevant, cannot be reproduced, ... - updating documentation in virtually every area: many large features have been added (mainly about charts and images at the moment) but without any documentation, it's pretty hard to do anything with it - proposing compatibility fixes for different versions of Python: we try to support 2.5 to 3.3, so if it does not work on your environment, let us know :-) Installation ------------ The best method to install openpyxl is using a PyPi client such as easy_install (setuptools) or pip:: $ pip install openpyxl or :: $ easy_install install openpyxl .. note:: To install from sources (there is nothing to build, openpyxl is 100% pure Python), you can download an archive from `bitbucket`_ (look in the "tags" tab). After extracting the archive, you can do:: $ python setup.py install .. _bitbucket: https://bitbucket.org/ericgazoni/openpyxl/downloads .. warning:: To be able to include images (jpeg,png,bmp,...) into an openpyxl file, you will also need the 'PIL' library that can be installed with:: $ pip install pillow or browse https://pypi.python.org/pypi/Pillow/, pick the latest version and head to the bottom of the page for Windows binaries. Getting the source ------------------ Source code is hosted on bitbucket.org. You can get it using a Mercurial client and the following URLs: * $ hg clone \https://bitbucket.org/ericgazoni/openpyxl -r |release| or to get the latest development version: * $ hg clone \https://bitbucket.org/ericgazoni/openpyxl Usage examples ------------------ Tutorial ++++++++ .. toctree:: tutorial Cookbook ++++++++ .. toctree:: usage Read/write large files ++++++++++++++++++++++ .. toctree:: optimized API Documentation ------------------ .. toctree:: api Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` openpyxl-1.7.0+ds1/doc/source/logo.png000066400000000000000000000126011224514475200176010ustar00rootroot00000000000000PNG  IHDR#r;sBIT|d pHYs-ۜtEXtSoftwarewww.inkscape.org<IDATx{\]?wfvgwN4q& Q BD4*HJyT «hҖҤ$-؎qv3;;{3wClt5;s=gfg$I :ȬFsY49C]9^ȔZgr\t] [aJƯk]xZ.;B!0G8*@տe+E,,~ۂfV|5d<'n_Ft;\W / L)W ȴaX 1 *jY4j>sIP3e0Yf`"j;|(p]0QZQ3<|{ 8[]G If~`h~8EmۏB @G^C>dxnIrFVWBI 6t0 QE@< 63 x=o|dSfq ~28f ULWhuVuE\ x2I9`c& n΀"R>hkqu 4ـW FmcZ ʇQ%࿈h)~+vڮ*O{~nd_ ǁ?6@#vN׆qdPq.(r~h%IҷnʷRrbTnA<[2}M9 ]Iv`uGj'\D@enդ_:⁍K?oF.GFn` 0X g,Ձb8M*_0vBRRBYX^C2֢GMϙ^#O~?WAEͫ8L968,P lԧ ?!wM?X+π' q,?Ydnƫ }aͼXc`g 'yrꐮ*ԧ!$Άׅu~z~]>2  ǧc&,ՁߍH=sP! b N6qv>퇑@fkg_7R3Œy~U_, -i99ڼ=~KFGovw&DoC+0!WoIfe+h\mWK!_5Ta8hm z!o{9̺ }L_ozaN EBD; UlB6*["mY=,o6";GZ=`a8|x绀sk*?̷Yg_Ǜ|."U_rA]"l6Pދ҄F]Lۯ@A3.b#?d ߻ K ^wE"L[BldBC3 @:ൾv5j 1O G@IYoE6s? рB>3 ԁכmw{ߏho!Y #~w' wo3ާӈF^^M1LqmiB,SY_;1+EDx1`< zoO#Uf5 9E6DcvQڀl^$xTmn%!r~g w bzߋC9^(My9V܉ .eyDP3Oo|ೃT oB4:+}5Ex47< Zyܥ?myӛYg0rßv狝tCsiHAd/f"]Y$5󯗥G"LlBQq "̳1x-D[*Ut ,]X"HSZ%zx3":_"J_3d Α=UOϥp ]H|]jK8-]Z! jqjČϦ-`KoFVSWfevCgJp*H^7W japBG(mEJ4 <i_MTUr˷ٷM!UayօQZAGO{Ok:3N 0߄ce5h7`:r5^6A?sրY.#\:Lyn-zfܘ#ii+8F~&A#ΡhQ1spH⟫fl"`n~wNL߯]Ǘ 4v!#4/E*2‰cpUk$T6?yQ8YnU:T&7G߈q{Fgo9H1ki`BU26r+5 nnj.u]jl{F4Qu44GwBbcܯ0" __!m:u9 *ӈ`o+1w%{*ʰB] Sn$*{&_)B==_?=?)1{\ &qD{j[@4@4^י̬L-]cך6Huo:},C$-7Hu&a\Suj6gJJrG(N?_:,r.cdMDa,ieiFw[]YދlXe~/6nܕ{<5@%#Iߗ?6+0#g%PT-OaXNkԣul+"4BCFK~9716ٴ{9D"@#W-%5b,)4uWCz,vjeUjzԝ,jܟ'd/@2My CnRj"cCpj7y{)opw;SQ0"qǠ"] ;p0횛 9y&6"&l} ~ DYӒg[3c~3+ߊs ߏjq3HqYxH {EKB\OI$x6WR4^KurE Hڀ=ŧncT8P[wAuZXÆ+e+Nd93zKrhr$؛{2IuΕ*85\$589Es6kfdT%eOG5_ ֺjuB"@j8.eUJv-ʫ(W͆AQ0OȦ!w-FiV㋾պUK;* Cȝs.I=1 k]ؼo `오71U,(r=蟆Ʊp)A%(2^' EW=c5JVh{5ɺ1jbT[XD1ѱnXI\Tt6U*fm NBSTgC]㈯!D k{pVnU$[RjIP_]"'F\X% ϝJlfLm`||K25s.nDY0c7nɩAus/f6VMj3ZТI\~KYBM"X{`{v.z^Az57ljRVLZdP K G\]S}(S;Jk]^b%m:UhekqX&fNu>t[Y%e ud(:VzIJNX#5+C/K|3X+m3g;Vpt.ΌWQ,(&ZPk9[Z@k|smc|/S@]Y$by ",:>> from openpyxl import Workbook >>> wb = Workbook() A workbook is always created with at least one worksheet. You can get it by using the :func:`openpyxl.workbook.Workbook.get_active_sheet` method :: >>> ws = wb.get_active_sheet() .. note:: This function uses the `_active_sheet_index` property, set to 0 by default. Unless you modify its value, you will always get the first worksheet by using this method. You can also create new worksheets by using the :func:`openpyxl.workbook.Workbook.create_sheet` method :: >>> ws1 = wb.create_sheet() # insert at the end (default) # or >>> ws2 = wb.create_sheet(0) # insert at first position Sheets are given a name automatically when they are created. They are numbered in sequence (Sheet, Sheet1, Sheet2, ...). You can change this name at any time with the `title` property:: ws.title = "New Title" Once you gave a worksheet a name, you can get it using the :func:`openpyxl.workbook.Workbook.get_sheet_by_name` method :: >>> ws3 = wb.get_sheet_by_name("New Title") >>> ws is ws3 True You can review the names of all worksheets of the workbook with the :func:`openpyxl.workbook.Workbook.get_sheet_names` method :: >>> print wb.get_sheet_names() ['Sheet2', 'New Title', 'Sheet1'] Playing with data ------------------ Accessing one cell ++++++++++++++++++ Now we know how to access a worksheet, we can start modifying cells content. To access a cell, use the :func:`openpyxl.worksheet.Worksheet.cell` method:: >>> c = ws.cell('A4') You can also access a cell using row and column notation:: >>> d = ws.cell(row = 4, column = 2) .. note:: When a worksheet is created in memory, it contains no `cells`. They are created when first accessed. This way we don't create objects that would never be accessed, thus reducing the memory footprint. .. warning:: Because of this feature, scrolling through cells instead of accessing them directly will create them all in memory, even if you don't assign them a value. Something like :: >>> for i in xrange(0,100): ... for j in xrange(0,100): ... ws.cell(row = i, column = j) will create 100x100 cells in memory, for nothing. However, there is a way to clean all those unwanted cells, we'll see that later. Accessing many cells ++++++++++++++++++++ If you want to access a `range`, wich is a two-dimension array of cells, you can use the :func:`openpyxl.worksheet.Worksheet.range` method:: >>> ws.range('A1:C2') ((, , ), (, , )) >>> for row in ws.range('A1:C2'): ... for cell in row: ... print cell If you need to iterate through all the rows or columns of a file, you can instead use the :func:`openpyxl.worksheet.Worksheet.rows` property:: >>> ws = wb.get_active_sheet() >>> ws.cell('C9').value = 'hello world' >>> ws.rows ((, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , )) or the :func:`openpyxl.worksheet.Worksheet.columns` property:: >>> ws.columns ((, , , , , , ... , , ), (, , , , , , , , )) Data storage ++++++++++++ Once we have a :class:`openpyxl.cell.Cell`, we can assign it a value:: >>> c.value = 'hello, world' >>> print c.value 'hello, world' >>> d.value = 3.14 >>> print d.value 3.14 There is also a neat format detection feature that converts data on the fly:: >>> c.value = '12%' >>> print c.value 0.12 >>> import datetime >>> d.value = datetime.datetime.now() >>> print d.value datetime.datetime(2010, 9, 10, 22, 25, 18) >>> c.value = '31.50' >>> print c.value 31.5 Saving to a file ================ The simplest and safest way to save a workbook is by using the :func:`openpyxl.workbook.Workbook.save()` method of the :class:`openpyxl.workbook.Workbook` object:: >>> wb = Workbook() >>> wb.save('balances.xlsx') .. warning:: This operation will overwrite existing files without warning. .. note:: Extension is not forced to be xlsx or xlsm, although you might have some trouble opening it directly with another application if you don't use an official extension. As OOXML files are basically ZIP files, you can also end the filename with .zip and open it with your favourite ZIP archive manager. Loading from a file =================== The same way as writing, you can import :func:`openpyxl.load_workbook` to open an existing workbook:: >>> from openpyxl import load_workbook >>> wb2 = load_workbook('test.xlsx') >>> print wb2.get_sheet_names() ['Sheet2', 'New Title', 'Sheet1'] This ends the tutorial for now, you can proceed to the :doc:`usage` section openpyxl-1.7.0+ds1/doc/source/usage.rst000066400000000000000000000104461224514475200177760ustar00rootroot00000000000000Simple usage ======================= Write a workbook ------------------ :: from openpyxl import Workbook from openpyxl.cell import get_column_letter wb = Workbook() dest_filename = r'empty_book.xlsx' ws = wb.worksheets[0] ws.title = "range names" for col_idx in xrange(1, 40): col = get_column_letter(col_idx) for row in xrange(1, 600): ws.cell('%s%s'%(col, row)).value = '%s%s' % (col, row) ws = wb.create_sheet() ws.title = 'Pi' ws.cell('F5').value = 3.14 wb.save(filename = dest_filename) Read an existing workbook ----------------------------- :: from openpyxl import load_workbook wb = load_workbook(filename = r'empty_book.xlsx') sheet_ranges = wb.get_sheet_by_name(name = 'range names') print sheet_ranges.cell('D18').value # D18 Using number formats ---------------------- :: import datetime from openpyxl import Workbook wb = Workbook() ws = wb.worksheets[0] # set date using a Python datetime ws.cell('A1').value = datetime.datetime(2010, 7, 21) print ws.cell('A1').style.number_format.format_code # returns 'yyyy-mm-dd' # set percentage using a string followed by the percent sign ws.cell('B1').value = '3.14%' print ws.cell('B1').value # returns 0.031400000000000004 print ws.cell('B1').style.number_format.format_code # returns '0%' Inserting an image ------------------- :: from openpyxl import Workbook from openpyxl.drawing import Image wb = Workbook() ws = wb.get_active_sheet() ws.cell('A1').value = 'You should see a logo below' # create an image instance img = Image('logo.png') # place it if required img.drawing.left = 200 img.drawing.top = 100 # you could also 'anchor' the image to a specific cell # img.anchor(ws.cell('B12')) # add to worksheet ws.add_image(img) wb.save('logo.xlsx') Validating cells ---------------- :: from openpyxl import Workbook from openpyxl.datavalidation import DataValidation, ValidationType # Create the workbook and worksheet we'll be working with wb = Workbook() ws = wb.get_active_sheet() # Create a data-validation object with list validation dv = DataValidation(ValidationType.LIST, formula1='"Dog,Cat,Bat"', allow_blank=True) # Optionally set a custom error message dv.set_error_message('Your entry is not in the list', 'Invalid Entry') # Optionally set a custom prompt message dv.set_prompt_message('Please select from the list', 'List Selection') # Add the data-validation object to the worksheet ws.add_data_validation(dv) # Create some cells, and add them to the data-validation object c1 = ws.cell("A1") c1.value = "Dog" dv.add_cell(c1) c2 = ws.cell("A2") c2.value = "An invalid value" dv.add_cell(c2) # Or, apply the validation to a range of cells dv.ranges.append('B1:B1048576') # Write the sheet out. If you now open the sheet in Excel, you'll find that # the cells have data-validation applied. wb.save("test.xlsx") Other validation examples ------------------------- Any whole number: :: dv = DataValidation(ValidationType.WHOLE) Any whole number above 100: :: dv = DataValidation(ValidationType.WHOLE, ValidationOperator.GREATER_THAN, 100) Any decimal number: :: dv = DataValidation(ValidationType.DECIMAL) Any decimal number between 0 and 1: :: dv = DataValidation(ValidationType.DECIMAL, ValidationOperator.BETWEEN, 0, 1) Any date: :: dv = DataValidation(ValidationType.DATE) or time: :: dv = DataValidation(ValidationType.TIME) Any string at most 15 characters: :: dv = DataValidation(ValidationType.TEXT_LENGTH, ValidationOperator.LESS_THAN_OR_EQUAL, 15) Custom rule: :: dv = DataValidation(ValidationType.CUSTOM, None, "=SOMEFORMULA") .. note:: See http://www.contextures.com/xlDataVal07.html for custom rules openpyxl-1.7.0+ds1/openpyxl/000077500000000000000000000000001224514475200157445ustar00rootroot00000000000000openpyxl-1.7.0+ds1/openpyxl/__init__.py000066400000000000000000000041531224514475200200600ustar00rootroot00000000000000# file openpyxl/__init__.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Imports for the openpyxl package.""" # package imports from openpyxl import cell from openpyxl import namedrange from openpyxl import style from openpyxl import workbook from openpyxl import worksheet from openpyxl import reader from openpyxl import shared from openpyxl import writer # shortcuts from openpyxl.workbook import Workbook from openpyxl.reader.excel import load_workbook # constants __major__ = 1 # for major interface/format changes __minor__ = 7 # for minor interface/format changes __release__ = 0 # for tweaks, bug-fixes, or development __version__ = '%d.%d.%d' % (__major__, __minor__, __release__) __author__ = 'Eric Gazoni' __license__ = 'MIT/Expat' __author_email__ = 'eric.gazoni@gmail.com' __maintainer_email__ = 'openpyxl-users@googlegroups.com' __url__ = 'http://openpyxl.readthedocs.org' __downloadUrl__ = "http://bitbucket.org/ericgazoni/openpyxl/downloads" __all__ = ('reader', 'shared', 'writer',) openpyxl-1.7.0+ds1/openpyxl/cell.py000066400000000000000000000423451224514475200172450ustar00rootroot00000000000000# file openpyxl/cell.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Manage individual cells in a spreadsheet. The Cell class is required to know its value and type, display options, and any other features of an Excel cell. Utilities for referencing cells using Excel's 'A1' column/row nomenclature are also provided. """ __docformat__ = "restructuredtext en" # Python stdlib imports from openpyxl.shared import (NUMERIC_TYPES, DEFAULT_ROW_HEIGHT, DEFAULT_COLUMN_WIDTH) from openpyxl.shared.compat import all, unicode, basestring from openpyxl.shared.date_time import SharedDate from openpyxl.shared.exc import (CellCoordinatesException, ColumnStringIndexException, DataTypeException) from openpyxl.shared.units import points_to_pixels from openpyxl.style import NumberFormat import datetime import re # package imports # constants COORD_RE = re.compile('^[$]?([A-Z]+)[$]?(\d+)$') ABSOLUTE_RE = re.compile('^[$]?([A-Z]+)[$]?(\d+)(:[$]?([A-Z]+)[$]?(\d+))?$') def coordinate_from_string(coord_string): """Convert a coordinate string like 'B12' to a tuple ('B', 12)""" match = COORD_RE.match(coord_string.upper()) if not match: msg = 'Invalid cell coordinates (%s)' % coord_string raise CellCoordinatesException(msg) column, row = match.groups() row = int(row) if not row: msg = "There is no row 0 (%s)" % coord_string raise CellCoordinatesException(msg) return (column, row) def absolute_coordinate(coord_string): """Convert a coordinate to an absolute coordinate string (B12 -> $B$12)""" m = ABSOLUTE_RE.match(coord_string) if m: parts = m.groups() if all(parts[-2:]): return '$%s$%s:$%s$%s' % (parts[0], parts[1], parts[3], parts[4]) else: return '$%s$%s' % (parts[0], parts[1]) else: return coord_string def column_index_from_string(column, fast=False): """Convert a column letter into a column number (e.g. B -> 2) Excel only supports 1-3 letter column names from A -> ZZZ, so we restrict our column names to 1-3 characters, each in the range A-Z. .. note:: Fast mode is faster but does not check that all letters are capitals between A and Z """ column = column.upper() clen = len(column) if not fast and not all('A' <= char <= 'Z' for char in column): msg = 'Column string must contain only characters A-Z: got %s' % column raise ColumnStringIndexException(msg) if clen == 1: return ord(column[0]) - 64 elif clen == 2: return ((1 + (ord(column[0]) - 65)) * 26) + (ord(column[1]) - 64) elif clen == 3: return ((1 + (ord(column[0]) - 65)) * 676) + ((1 + (ord(column[1]) - 65)) * 26) + (ord(column[2]) - 64) elif clen > 3: raise ColumnStringIndexException('Column string index can not be longer than 3 characters') else: raise ColumnStringIndexException('Column string index can not be empty') def get_column_letter(col_idx): """Convert a column number into a column letter (3 -> 'C') Right shift the column col_idx by 26 to find column letters in reverse order. These numbers are 1-based, and can be converted to ASCII ordinals by adding 64. """ # these indicies corrospond to A -> ZZZ and include all allowed # columns if not 1 <= col_idx <= 18278: msg = 'Column index out of bounds: %s' % col_idx raise ColumnStringIndexException(msg) ordinals = [] temp = col_idx while temp: quotient, remainder = divmod(temp, 26) # check for exact division and borrow if needed if remainder == 0: quotient -= 1 remainder = 26 ordinals.append(remainder + 64) temp = quotient ordinals.reverse() return ''.join([chr(ordinal) for ordinal in ordinals]) class Cell(object): """Describes cell associated properties. Properties of interest include style, type, value, and address. """ __slots__ = ('column', 'row', '_value', '_data_type', 'parent', 'xf_index', '_hyperlink_rel', '_shared_date', 'merged') ERROR_CODES = {'#NULL!': 0, '#DIV/0!': 1, '#VALUE!': 2, '#REF!': 3, '#NAME?': 4, '#NUM!': 5, '#N/A': 6} TYPE_STRING = 's' TYPE_FORMULA = 'f' TYPE_NUMERIC = 'n' TYPE_BOOL = 'b' TYPE_NULL = 's' TYPE_INLINE = 'inlineStr' TYPE_ERROR = 'e' TYPE_FORMULA_CACHE_STRING = 'str' VALID_TYPES = [TYPE_STRING, TYPE_FORMULA, TYPE_NUMERIC, TYPE_BOOL, TYPE_NULL, TYPE_INLINE, TYPE_ERROR, TYPE_FORMULA_CACHE_STRING] RE_PATTERNS = { 'percentage': re.compile(r'^\-?[0-9]*\.?[0-9]*\s?\%$'), 'time': re.compile(r'^(\d|[0-1]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$'), 'numeric': re.compile(r'^-?([\d]|[\d]+\.[\d]*|\.[\d]+|[1-9][\d]+\.?[\d]*)((E|e)-?[\d]+)?$'), } def __init__(self, worksheet, column, row, value=None): self.column = column.upper() self.row = row # _value is the stored value, while value is the displayed value self._value = None self._hyperlink_rel = None self._data_type = self.TYPE_NULL if value: self.value = value self.parent = worksheet self.xf_index = 0 self._shared_date = SharedDate(base_date=worksheet.parent.excel_base_date) self.merged = False @property def encoding(self): return self.parent.encoding def __repr__(self): return unicode("") % (self.parent.title, self.get_coordinate()) def check_string(self, value): """Check string coding, length, and line break character""" # convert to unicode string if not isinstance(value, unicode): value = unicode(value, self.encoding) value = unicode(value) # string must never be longer than 32,767 characters # truncate if necessary value = value[:32767] # we require that newline is represented as "\n" in core, # not as "\r\n" or "\r" value = value.replace('\r\n', '\n') return value def check_numeric(self, value): """Cast value to int or float if necessary""" if not isinstance(value, NUMERIC_TYPES): try: value = int(value) except ValueError: value = float(value) return value def check_error(self, value): """Tries to convert Error" else N/A""" try: return unicode(value) except: return unicode('#N/A') def set_value_explicit(self, value=None, data_type=TYPE_STRING): """Coerce values according to their explicit type""" type_coercion_map = { self.TYPE_INLINE: self.check_string, self.TYPE_STRING: self.check_string, self.TYPE_FORMULA: self.check_string, self.TYPE_NUMERIC: self.check_numeric, self.TYPE_BOOL: bool, self.TYPE_ERROR: self.check_error} try: self._value = type_coercion_map[data_type](value) except KeyError: if data_type not in self.VALID_TYPES: msg = 'Invalid data type: %s' % data_type raise DataTypeException(msg) self._data_type = data_type def data_type_for_value(self, value): """Given a value, infer the correct data type""" if value is None: data_type = self.TYPE_NULL elif value is True or value is False: data_type = self.TYPE_BOOL elif isinstance(value, NUMERIC_TYPES): data_type = self.TYPE_NUMERIC elif isinstance(value, (datetime.datetime, datetime.date, datetime.time, datetime.timedelta)): data_type = self.TYPE_NUMERIC elif not value: data_type = self.TYPE_STRING elif isinstance(value, basestring) and value[0] == '=': data_type = self.TYPE_FORMULA elif isinstance(value, unicode) and self.RE_PATTERNS['numeric'].match(value): data_type = self.TYPE_NUMERIC elif not isinstance(value, unicode) and self.RE_PATTERNS['numeric'].match(str(value)): data_type = self.TYPE_NUMERIC elif isinstance(value, basestring) and value.strip() in self.ERROR_CODES: data_type = self.TYPE_ERROR elif isinstance(value, list): data_type = self.TYPE_ERROR else: data_type = self.TYPE_STRING return data_type def bind_value(self, value): """Given a value, infer type and display options.""" self._data_type = self.data_type_for_value(value) if value is None: self.set_value_explicit('', self.TYPE_NULL) return True elif self._data_type == self.TYPE_STRING: # percentage detection if isinstance(value, unicode): percentage_search = self.RE_PATTERNS['percentage'].match(value) else: percentage_search = self.RE_PATTERNS['percentage'].match(str(value)) if percentage_search and value.strip() != '%': value = float(value.replace('%', '')) / 100.0 self.set_value_explicit(value, self.TYPE_NUMERIC) self._set_number_format(NumberFormat.FORMAT_PERCENTAGE) return True # time detection if isinstance(value, unicode): time_search = self.RE_PATTERNS['time'].match(value) else: time_search = self.RE_PATTERNS['time'].match(str(value)) if time_search: sep_count = value.count(':') # pylint: disable=E1103 if sep_count == 1: hours, minutes = [int(bit) for bit in value.split(':')] # pylint: disable=E1103 seconds = 0 elif sep_count == 2: hours, minutes, seconds = \ [int(bit) for bit in value.split(':')] # pylint: disable=E1103 days = (hours / 24.0) + (minutes / 1440.0) + \ (seconds / 86400.0) self.set_value_explicit(days, self.TYPE_NUMERIC) self._set_number_format(NumberFormat.FORMAT_DATE_TIME3) return True if self._data_type == self.TYPE_NUMERIC: # date detection # if the value is a date, but not a date time, make it a # datetime, and set the time part to 0 if isinstance(value, datetime.date) and not \ isinstance(value, datetime.datetime): value = datetime.datetime.combine(value, datetime.time()) if isinstance(value, (datetime.datetime, datetime.time, datetime.timedelta)): if isinstance(value, datetime.datetime): self._set_number_format(NumberFormat.FORMAT_DATE_YYYYMMDD2) elif isinstance(value, datetime.time): self._set_number_format(NumberFormat.FORMAT_DATE_TIME6) elif isinstance(value, datetime.timedelta): self._set_number_format(NumberFormat.FORMAT_DATE_TIMEDELTA) value = SharedDate().datetime_to_julian(date=value) self.set_value_explicit(value, self.TYPE_NUMERIC) return True self.set_value_explicit(value, self._data_type) def _get_value(self): """Return the value, formatted as a date if needed""" value = self._value if self.is_date(): value = self._shared_date.from_julian(value) return value def _set_value(self, value): """Set the value and infer type and display options.""" self.bind_value(value) value = property(_get_value, _set_value, doc='Get or set the value held in the cell.\n\n' ':rtype: depends on the value (string, float, int or ' ':class:`datetime.datetime`)') def _set_hyperlink(self, val): """Set value and display for hyperlinks in a cell""" if self._hyperlink_rel is None: self._hyperlink_rel = self.parent.create_relationship("hyperlink") self._hyperlink_rel.target = val self._hyperlink_rel.target_mode = "External" if self._value is None: self.value = val def _get_hyperlink(self): """Return the hyperlink target or an empty string""" return self._hyperlink_rel is not None and \ self._hyperlink_rel.target or '' hyperlink = property(_get_hyperlink, _set_hyperlink, doc='Get or set the hyperlink held in the cell. ' 'Automatically sets the `value` of the cell with link text, ' 'but you can modify it afterwards by setting the ' '`value` property, and the hyperlink will remain.\n\n' ':rtype: string') @property def hyperlink_rel_id(self): """Return the id pointed to by the hyperlink, or None""" return self._hyperlink_rel is not None and \ self._hyperlink_rel.id or None def _set_number_format(self, format_code): """Set a new formatting code for numeric values""" self.style.number_format.format_code = format_code @property def has_style(self): """Check if the parent worksheet has a style for this cell""" return self.get_coordinate() in self.parent._styles # pylint: disable=W0212 @property def style(self): """Returns the :class:`openpyxl.style.Style` object for this cell""" return self.parent.get_style(self.get_coordinate()) @property def data_type(self): """Return the data type represented by this cell""" return self._data_type def get_coordinate(self): """Return the coordinate string for this cell (e.g. 'B12') :rtype: string """ return '%s%s' % (self.column, self.row) @property def address(self): """Return the coordinate string for this cell (e.g. 'B12') :rtype: string """ return self.get_coordinate() def offset(self, row=0, column=0): """Returns a cell location relative to this cell. :param row: number of rows to offset :type row: int :param column: number of columns to offset :type column: int :rtype: :class:`openpyxl.cell.Cell` """ offset_column = get_column_letter(column_index_from_string( column=self.column) + column) offset_row = self.row + row return self.parent.cell('%s%s' % (offset_column, offset_row)) def is_date(self): """Returns whether the value is *probably* a date or not :rtype: bool """ return (self.has_style and self.style.number_format.is_date_format() and isinstance(self._value, NUMERIC_TYPES)) @property def anchor(self): """ returns the expected position of a cell in pixels from the top-left of the sheet. For example, A1 anchor should be (0,0). :rtype: tuple(int, int) """ left_columns = (column_index_from_string(self.column, True) - 1) column_dimensions = self.parent.column_dimensions left_anchor = 0 default_width = points_to_pixels(DEFAULT_COLUMN_WIDTH) for col_idx in range(left_columns): letter = get_column_letter(col_idx + 1) if letter in column_dimensions: cdw = column_dimensions.get(letter).width if cdw > 0: left_anchor += points_to_pixels(cdw) continue left_anchor += default_width row_dimensions = self.parent.row_dimensions top_anchor = 0 top_rows = (self.row - 1) default_height = points_to_pixels(DEFAULT_ROW_HEIGHT) for row_idx in range(1, top_rows + 1): if row_idx in row_dimensions: rdh = row_dimensions[row_idx].height if rdh > 0: top_anchor += points_to_pixels(rdh) continue top_anchor += default_height return (left_anchor, top_anchor) openpyxl-1.7.0+ds1/openpyxl/chart.py000066400000000000000000000277101224514475200174260ustar00rootroot00000000000000# Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file import math from openpyxl.style import NumberFormat from openpyxl.drawing import Drawing, Shape from openpyxl.shared.units import pixels_to_EMU, short_color from openpyxl.shared.compat import basestring from openpyxl.cell import get_column_letter class Axis(object): POSITION_BOTTOM = 'b' POSITION_LEFT = 'l' ORIENTATION_MIN_MAX = "minMax" def __init__(self): self.orientation = self.ORIENTATION_MIN_MAX self.number_format = NumberFormat() for attr in ('position', 'tick_label_position', 'crosses', 'auto', 'label_align', 'label_offset', 'cross_between'): setattr(self, attr, None) self.min = 0 self.max = None self.unit = None self.title = '' @classmethod def default_category(cls): """ default values for category axes """ ax = Axis() ax.id = 60871424 ax.cross = 60873344 ax.position = Axis.POSITION_BOTTOM ax.tick_label_position = 'nextTo' ax.crosses = "autoZero" ax.auto = True ax.label_align = 'ctr' ax.label_offset = 100 return ax @classmethod def default_value(cls): """ default values for value axes """ ax = Axis() ax.id = 60873344 ax.cross = 60871424 ax.position = Axis.POSITION_LEFT ax.major_gridlines = None ax.tick_label_position = 'nextTo' ax.crosses = 'autoZero' ax.auto = False ax.cross_between = 'between' return ax class Reference(object): """ a simple wrapper around a serie of reference data """ def __init__(self, sheet, pos1, pos2=None): self.sheet = sheet self.pos1 = pos1 self.pos2 = pos2 def get_type(self): if isinstance(self.values[0], basestring): return 'str' else: return 'num' @property def values(self): """ read data in sheet - to be used at writing time """ if hasattr(self, "_values"): return self._values if self.pos2 is None: cell = self.sheet.cell(row=self.pos1[0], column=self.pos1[1]) self._values = [cell.value] else: self._values = [] for row in range(int(self.pos1[0]), int(self.pos2[0] + 1)): for col in range(int(self.pos1[1]), int(self.pos2[1] + 1)): self._values.append(self.sheet.cell(row=row, column=col).value) return self._values def _get_ref(self): """ legace method """ return str(self) def __str__(self): """ format excel reference notation """ if self.pos2: return "'%s'!$%s$%s:$%s$%s" % (self.sheet.title, get_column_letter(self.pos1[1] + 1), self.pos1[0] + 1, get_column_letter(self.pos2[1] + 1), self.pos2[0] + 1) else: return "'%s'!$%s$%s" % (self.sheet.title, get_column_letter(self.pos1[1] + 1), self.pos1[0] + 1) def _get_cache(self): """ legacy method """ return self.values class Serie(object): """ a serie of data and possibly associated labels """ MARKER_NONE = 'none' def __init__(self, values, labels=None, legend=None, color=None, xvalues=None): self.marker = Serie.MARKER_NONE self.values = values self.xvalues = xvalues self.labels = labels self.legend = legend self.error_bar = None def _get_color(self): return getattr(self, "_color", None) def _set_color(self, color): if color is None: raise ValueError("Colors must be strings of the format XXXXX") self._color = short_color(color) color = property(_get_color, _set_color) def _get_values(self): """Return values from underlying reference""" return self._values def _set_values(self, reference): """Assign values from reference to serie""" if reference is not None: if not isinstance(reference, Reference): raise TypeError("Series values must be a Reference instance") self._values = reference.values else: self._values = None self.reference = reference values = property(_get_values, _set_values) def _get_xvalues(self): """Return xvalues""" return self._xvalues def _set_xvalues(self, reference): if reference is not None: if not isinstance(reference, Reference): raise TypeError("Series xvalues must be a Reference instance") self._xvalues = reference.values else: self._xvalues = None self.xreference = reference xvalues = property(_get_xvalues, _set_xvalues) def mymax(self, values): return max([x for x in values]) def get_min_max(self): if self.error_bar: err_cache = self.error_bar.values vals = [v + err_cache[i] \ for i, v in enumerate(self.values)] else: vals = self.values return min(vals), self.mymax(vals) def __len__(self): return len(self.values) class Legend(object): def __init__(self): self.position = 'r' self.layout = None class ErrorBar(object): PLUS = 1 MINUS = 2 PLUS_MINUS = 3 def __init__(self, _type, values): self.type = _type self.values = values def _get_values(self): """Return values from underlying reference""" return self._values def _set_values(self, reference): """Assign values from reference to serie""" if reference is not None: if not isinstance(reference, Reference): raise TypeError("Errorbar values must be a Reference instance") self._values = reference.values else: self._values = None self.reference = reference values = property(_get_values, _set_values) class Chart(object): """ raw chart class """ GROUPING_CLUSTERED = 'clustered' GROUPING_STANDARD = 'standard' BAR_CHART = 1 LINE_CHART = 2 SCATTER_CHART = 3 def mymax(self, values): return max([x for x in values if x]) def __init__(self, _type, grouping): self._series = [] # public api self.type = _type self.grouping = grouping self.x_axis = Axis.default_category() self.y_axis = Axis.default_value() self.legend = Legend() self.show_legend = True self.lang = 'fr-FR' self.title = '' self.print_margins = dict(b=.75, l=.7, r=.7, t=.75, header=0.3, footer=.3) # the containing drawing self.drawing = Drawing() # the offset for the plot part in percentage of the drawing size self.width = .6 self.height = .6 self.margin_top = self._get_max_margin_top() self.margin_left = 0 # the user defined shapes self._shapes = [] def add_serie(self, serie): serie.id = len(self._series) self._series.append(serie) self._compute_min_max() if not None in [s.xvalues for s in self._series]: self._compute_xmin_xmax() def add_shape(self, shape): shape._chart = self self._shapes.append(shape) def get_x_units(self): """ calculate one unit for x axis in EMU """ return self.mymax([len(s.values) for s in self._series]) def get_y_units(self): """ calculate one unit for y axis in EMU """ dh = pixels_to_EMU(self.drawing.height) return (dh * self.height) / self.y_axis.max def get_y_chars(self): """ estimate nb of chars for y axis """ _max = max([self.mymax(s.values) for s in self._series]) return len(str(int(_max))) def _compute_min_max(self): """ compute y axis limits and units """ maxi = max([self.mymax(s.values) for s in self._series if s.values]) mul = None if maxi < 1: s = str(maxi).split('.')[1] mul = 10.0 for x in s: if x == '0': mul *= 10.0 else: break maxi = maxi * mul maxi = math.ceil(float(maxi) * 1.1) sz = len(str(int(maxi))) - 1 unit = math.ceil(math.ceil(float(maxi) / pow(10.0, sz)) * pow(10.0, sz - 1)) maxi = math.ceil(float(maxi) / unit) * unit if mul is not None: maxi = maxi / mul unit = unit / mul if maxi / unit > 9: # no more that 10 ticks unit *= 2 self.y_axis.max = maxi self.y_axis.unit = unit def _compute_xmin_xmax(self): """ compute x axis limits and units """ maxi = max([self.mymax(s.xvalues) for s in self._series]) mul = None if maxi < 1: s = str(maxi).split('.')[1] mul = 10 for x in s: if x == '0': mul *= 10 else: break maxi = maxi * mul maxi = math.ceil(maxi * 1.1) sz = len(str(int(maxi))) - 1 unit = math.ceil(math.ceil(maxi / pow(10, sz)) * pow(10, sz - 1)) maxi = math.ceil(maxi / unit) * unit if mul is not None: maxi = maxi / mul unit = unit / mul if maxi / unit > 9: # no more that 10 ticks unit *= 2 self.x_axis.max = maxi self.x_axis.unit = unit def _get_max_margin_top(self): mb = Shape.FONT_HEIGHT + Shape.MARGIN_BOTTOM plot_height = self.drawing.height * self.height return float(self.drawing.height - plot_height - mb) / self.drawing.height def _get_min_margin_left(self): ml = (self.get_y_chars() * Shape.FONT_WIDTH) + Shape.MARGIN_LEFT return float(ml) / self.drawing.width def _get_margin_top(self): """ get margin in percent """ return min(self.margin_top, self._get_max_margin_top()) def _get_margin_left(self): return max(self._get_min_margin_left(), self.margin_left) class BarChart(Chart): def __init__(self): super(BarChart, self).__init__(Chart.BAR_CHART, Chart.GROUPING_CLUSTERED) class LineChart(Chart): def __init__(self): super(LineChart, self).__init__(Chart.LINE_CHART, Chart.GROUPING_STANDARD) class ScatterChart(Chart): def __init__(self): super(ScatterChart, self).__init__(Chart.SCATTER_CHART, Chart.GROUPING_STANDARD) openpyxl-1.7.0+ds1/openpyxl/datavalidation.py000066400000000000000000000216121224514475200213040ustar00rootroot00000000000000# file openpyxl/datavalidations.py # Copyright (c) 2010-2012 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file from itertools import groupby from openpyxl.shared.compat import OrderedDict from openpyxl.cell import coordinate_from_string def collapse_cell_addresses(cells, input_ranges=()): """ Collapse a collection of cell co-ordinates down into an optimal range or collection of ranges. E.g. Cells A1, A2, A3, B1, B2 and B3 should have the data-validation object applied, attempt to collapse down to a single range, A1:B3. Currently only collapsing contiguous vertical ranges (i.e. above example results in A1:A3 B1:B3). More work to come. """ keyfunc = lambda x: x[0] # Get the raw coordinates for each cell given raw_coords = [coordinate_from_string(cell) for cell in cells] # Group up as {column: [list of rows]} grouped_coords = OrderedDict((k, [c[1] for c in g]) for k, g in groupby(sorted(raw_coords, key=keyfunc), keyfunc)) ranges = list(input_ranges) # For each column, find contiguous ranges of rows for column in grouped_coords: rows = sorted(grouped_coords[column]) grouped_rows = [[r[1] for r in list(g)] for k, g in groupby(enumerate(rows), lambda x: x[0] - x[1])] for rows in grouped_rows: if len(rows) == 0: pass elif len(rows) == 1: ranges.append("%s%d" % (column, rows[0])) else: ranges.append("%s%d:%s%d" % (column, rows[0], column, rows[-1])) return " ".join(ranges) """ """ default_attr_map = { "showInputMessage": "1", "showErrorMessage": "1", } class DataValidation(object): def __init__(self, validation_type, operator=None, formula1=None, formula2=None, allow_blank=False, attr_map=None): self.validation_type = validation_type self.operator = operator self.formula1 = str(formula1) self.formula2 = str(formula2) self.allow_blank = allow_blank self.attr_map = {} self.cells = [] self.ranges = [] if not attr_map: self.attr_map.update(default_attr_map) def add_cell(self, cell): """Adds a openpyxl.cell to this validator""" self.cells.append(cell.get_coordinate()) def set_error_message(self, error, error_title="Validation Error"): """Creates a custom error message, displayed when a user changes a cell to an invalid value""" self.attr_map['errorTitle'] = error_title self.attr_map['error'] = error def set_prompt_message(self, prompt, prompt_title="Validation Prompt"): """Creates a custom prompt message""" self.attr_map['promptTitle'] = prompt_title self.attr_map['prompt'] = prompt def generate_attributes_map(self): self.attr_map['type'] = self.validation_type self.attr_map['allowBlank'] = '1' if self.allow_blank else '0' if self.operator: self.attr_map['operator'] = self.operator # Update the sqref to ensure it points at all cells we're interested in self.attr_map['sqref'] = collapse_cell_addresses(self.cells, self.ranges) return self.attr_map class ValidationType(object): NONE = "none" WHOLE = "whole" DECIMAL = "decimal" LIST = "list" DATE = "date" TIME = "time" TEXT_LENGTH = "textLength" CUSTOM = "custom" class ValidationOperator(object): BETWEEN = "between" NOT_BETWEEN = "notBetween" EQUAL = "equal" NOT_EQUAL = "notEqual" LESS_THAN = "lessThan" LESS_THAN_OR_EQUAL = "lessThanOrEqual" GREATER_THAN = "greaterThan" GREATER_THAN_OR_EQUAL = "greaterThanOrEqual" class ValidationErrorStyle(object): STOP = "stop" WARNING = "warning" INFORMATION = "information" openpyxl-1.7.0+ds1/openpyxl/drawing.py000066400000000000000000000307241224514475200177570ustar00rootroot00000000000000# Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file import math from openpyxl.style import Color from openpyxl.shared.units import pixels_to_EMU, EMU_to_pixels, short_color class Shadow(object): SHADOW_BOTTOM = 'b' SHADOW_BOTTOM_LEFT = 'bl' SHADOW_BOTTOM_RIGHT = 'br' SHADOW_CENTER = 'ctr' SHADOW_LEFT = 'l' SHADOW_TOP = 't' SHADOW_TOP_LEFT = 'tl' SHADOW_TOP_RIGHT = 'tr' def __init__(self): self.visible = False self.blurRadius = 6 self.distance = 2 self.direction = 0 self.alignment = self.SHADOW_BOTTOM_RIGHT self.color = Color(Color.BLACK) self.alpha = 50 class Drawing(object): """ a drawing object - eg container for shapes or charts we assume user specifies dimensions in pixels; units are converted to EMU in the drawing part """ count = 0 def __init__(self): self.name = '' self.description = '' self.coordinates = ((1, 2), (16, 8)) self.left = 0 self.top = 0 self._width = EMU_to_pixels(200000) self._height = EMU_to_pixels(1828800) self.resize_proportional = False self.rotation = 0 # self.shadow = Shadow() def _set_width(self, w): if self.resize_proportional and w: ratio = self._height / self._width self._height = round(ratio * w) self._width = w def _get_width(self): return self._width width = property(_get_width, _set_width) def _set_height(self, h): if self.resize_proportional and h: ratio = self._width / self._height self._width = round(ratio * h) self._height = h def _get_height(self): return self._height height = property(_get_height, _set_height) def set_dimension(self, w=0, h=0): xratio = w / self._width yratio = h / self._height if self.resize_proportional and w and h: if (xratio * self._height) < h: self._height = math.ceil(xratio * self._height) self._width = w else: self._width = math.ceil(yratio * self._width) self._height = h def get_emu_dimensions(self): """ return (x, y, w, h) in EMU """ return (pixels_to_EMU(self.left), pixels_to_EMU(self.top), pixels_to_EMU(self._width), pixels_to_EMU(self._height)) class Shape(object): """ a drawing inside a chart coordiantes are specified by the user in the axis units """ MARGIN_LEFT = 6 + 13 + 1 MARGIN_BOTTOM = 17 + 11 FONT_WIDTH = 7 FONT_HEIGHT = 8 ROUND_RECT = 'roundRect' RECT = 'rect' # other shapes to define : ''' "line" "lineInv" "triangle" "rtTriangle" "diamond" "parallelogram" "trapezoid" "nonIsoscelesTrapezoid" "pentagon" "hexagon" "heptagon" "octagon" "decagon" "dodecagon" "star4" "star5" "star6" "star7" "star8" "star10" "star12" "star16" "star24" "star32" "roundRect" "round1Rect" "round2SameRect" "round2DiagRect" "snipRoundRect" "snip1Rect" "snip2SameRect" "snip2DiagRect" "plaque" "ellipse" "teardrop" "homePlate" "chevron" "pieWedge" "pie" "blockArc" "donut" "noSmoking" "rightArrow" "leftArrow" "upArrow" "downArrow" "stripedRightArrow" "notchedRightArrow" "bentUpArrow" "leftRightArrow" "upDownArrow" "leftUpArrow" "leftRightUpArrow" "quadArrow" "leftArrowCallout" "rightArrowCallout" "upArrowCallout" "downArrowCallout" "leftRightArrowCallout" "upDownArrowCallout" "quadArrowCallout" "bentArrow" "uturnArrow" "circularArrow" "leftCircularArrow" "leftRightCircularArrow" "curvedRightArrow" "curvedLeftArrow" "curvedUpArrow" "curvedDownArrow" "swooshArrow" "cube" "can" "lightningBolt" "heart" "sun" "moon" "smileyFace" "irregularSeal1" "irregularSeal2" "foldedCorner" "bevel" "frame" "halfFrame" "corner" "diagStripe" "chord" "arc" "leftBracket" "rightBracket" "leftBrace" "rightBrace" "bracketPair" "bracePair" "straightConnector1" "bentConnector2" "bentConnector3" "bentConnector4" "bentConnector5" "curvedConnector2" "curvedConnector3" "curvedConnector4" "curvedConnector5" "callout1" "callout2" "callout3" "accentCallout1" "accentCallout2" "accentCallout3" "borderCallout1" "borderCallout2" "borderCallout3" "accentBorderCallout1" "accentBorderCallout2" "accentBorderCallout3" "wedgeRectCallout" "wedgeRoundRectCallout" "wedgeEllipseCallout" "cloudCallout" "cloud" "ribbon" "ribbon2" "ellipseRibbon" "ellipseRibbon2" "leftRightRibbon" "verticalScroll" "horizontalScroll" "wave" "doubleWave" "plus" "flowChartProcess" "flowChartDecision" "flowChartInputOutput" "flowChartPredefinedProcess" "flowChartInternalStorage" "flowChartDocument" "flowChartMultidocument" "flowChartTerminator" "flowChartPreparation" "flowChartManualInput" "flowChartManualOperation" "flowChartConnector" "flowChartPunchedCard" "flowChartPunchedTape" "flowChartSummingJunction" "flowChartOr" "flowChartCollate" "flowChartSort" "flowChartExtract" "flowChartMerge" "flowChartOfflineStorage" "flowChartOnlineStorage" "flowChartMagneticTape" "flowChartMagneticDisk" "flowChartMagneticDrum" "flowChartDisplay" "flowChartDelay" "flowChartAlternateProcess" "flowChartOffpageConnector" "actionButtonBlank" "actionButtonHome" "actionButtonHelp" "actionButtonInformation" "actionButtonForwardNext" "actionButtonBackPrevious" "actionButtonEnd" "actionButtonBeginning" "actionButtonReturn" "actionButtonDocument" "actionButtonSound" "actionButtonMovie" "gear6" "gear9" "funnel" "mathPlus" "mathMinus" "mathMultiply" "mathDivide" "mathEqual" "mathNotEqual" "cornerTabs" "squareTabs" "plaqueTabs" "chartX" "chartStar" "chartPlus" ''' def __init__(self, coordinates=((0, 0), (1, 1)), text=None, scheme="accent1"): self.coordinates = coordinates # in axis unit self.text = text self.scheme = scheme self.style = Shape.RECT self._border_width = 3175 # in EMU self._border_color = Color.BLACK[2:] # "F3B3C5" self._color = Color.WHITE[2:] self._text_color = Color.BLACK[2:] def _get_border_color(self): return self._border_color def _set_border_color(self, color): self._border_color = short_color(color) border_color = property(_get_border_color, _set_border_color) def _get_color(self): return self._color def _set_color(self, color): self._color = short_color(color) color = property(_get_color, _set_color) def _get_text_color(self): return self._text_color def _set_text_color(self, color): self._text_color = short_color(color) text_color = property(_get_text_color, _set_text_color) def _get_border_width(self): return EMU_to_pixels(self._border_width) def _set_border_width(self, w): self._border_width = pixels_to_EMU(w) border_width = property(_get_border_width, _set_border_width) def get_coordinates(self): """ return shape coordinates in percentages (left, top, right, bottom) """ (x1, y1), (x2, y2) = self.coordinates drawing_width = pixels_to_EMU(self._chart.drawing.width) drawing_height = pixels_to_EMU(self._chart.drawing.height) plot_width = drawing_width * self._chart.width plot_height = drawing_height * self._chart.height margin_left = self._chart._get_margin_left() * drawing_width xunit = plot_width / self._chart.get_x_units() margin_top = self._chart._get_margin_top() * drawing_height yunit = self._chart.get_y_units() x_start = (margin_left + (float(x1) * xunit)) / drawing_width y_start = ((margin_top + plot_height - (float(y1) * yunit)) / drawing_height) x_end = (margin_left + (float(x2) * xunit)) / drawing_width y_end = ((margin_top + plot_height - (float(y2) * yunit)) / drawing_height) def _norm_pct(pct): """ force shapes to appear by truncating too large sizes """ if pct > 1: pct = 1 elif pct < 0: pct = 0 return pct # allow user to specify y's in whatever order # excel expect y_end to be lower if y_end < y_start: y_end, y_start = y_start, y_end return (_norm_pct(x_start), _norm_pct(y_start), _norm_pct(x_end), _norm_pct(y_end)) def bounding_box(bw, bh, w, h): """ Returns a tuple (new_width, new_height) which has the property that it fits within box_width and box_height and has (close to) the same aspect ratio as the original size """ new_width, new_height = w, h if bw and new_width > bw: new_width = bw new_height = new_width / (float(w) / h) if bh and new_height > bh: new_height = bh new_width = new_height * (float(w) / h) return (new_width, new_height) class Image(object): """ Raw Image class """ def _import_image(self, img): try: try: import Image as PILImage except ImportError: from PIL import Image as PILImage except ImportError: raise ImportError('You must install PIL to fetch image objects') if not isinstance(img, PILImage.Image): img = PILImage.open(img) return img def __init__(self, img, coordinates=((0, 0), (1, 1)), size=(None, None), nochangeaspect=True, nochangearrowheads=True): self.image = self._import_image(img) self.nochangeaspect = nochangeaspect self.nochangearrowheads = nochangearrowheads # the containing drawing self.drawing = Drawing() self.drawing.coordinates = coordinates newsize = bounding_box(size[0], size[1], self.image.size[0], self.image.size[1]) size = newsize self.drawing.width = size[0] self.drawing.height = size[1] def anchor(self, cell): """ anchors the image to the given cell """ self.drawing.left, self.drawing.top = cell.anchor return ((cell.column, cell.row), cell.parent.point_pos(self.drawing.top + self.drawing.height, self.drawing.left + self.drawing.width)) openpyxl-1.7.0+ds1/openpyxl/namedrange.py000066400000000000000000000064061224514475200204250ustar00rootroot00000000000000# file openpyxl/namedrange.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Track named groups of cells in a worksheet""" # Python stdlib imports import re # package imports from openpyxl.shared.compat import unicode from openpyxl.shared.exc import NamedRangeException # constants NAMED_RANGE_RE = re.compile("^(('(?P([^']|'')*)')|(?P[^']*))!(?P(\$([A-Za-z]+))?(\$([0-9]+))?(:(\$([A-Za-z]+))?(\$([0-9]+))?)?)") SPLIT_NAMED_RANGE_RE = re.compile(r"((?:[^,']|'(?:[^']|'')*')+)") class NamedRange(object): """A named group of cells Scope is a worksheet object or None for workbook scope names (the default) """ __slots__ = ('name', 'destinations', 'scope') str_format = unicode('%s!%s') repr_format = unicode('<%s "%s">') def __init__(self, name, destinations, scope=None): self.name = name self.destinations = destinations self.scope = scope def __str__(self): return ','.join([self.str_format % (sheet, name) for sheet, name in self.destinations]) def __repr__(self): return self.repr_format % (self.__class__.__name__, str(self)) class NamedRangeContainingValue(object): """A named value""" __slots__ = ('name', 'value', 'scope') def __init__(self, name, value): self.name = name self.value = value self.scope = None def split_named_range(range_string): """Separate a named range into its component parts""" destinations = [] for range_string in SPLIT_NAMED_RANGE_RE.split(range_string)[1::2]: # Skip first and from there every second item match = NAMED_RANGE_RE.match(range_string) if not match: raise NamedRangeException('Invalid named range string: "%s"' % range_string) else: match = match.groupdict() sheet_name = match['quoted'] or match['notquoted'] xlrange = match['range'] sheet_name = sheet_name.replace("''", "'") # Unescape ' destinations.append((sheet_name, xlrange)) return destinations def refers_to_range(range_string): return range_string and bool(NAMED_RANGE_RE.match(range_string)) openpyxl-1.7.0+ds1/openpyxl/reader/000077500000000000000000000000001224514475200172065ustar00rootroot00000000000000openpyxl-1.7.0+ds1/openpyxl/reader/__init__.py000066400000000000000000000026751224514475200213310ustar00rootroot00000000000000# file openpyxl/reader/__init__.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Imports for the openpyxl.reader namespace.""" # package imports from openpyxl.reader import excel from openpyxl.reader import strings from openpyxl.reader import style from openpyxl.reader import workbook from openpyxl.reader import worksheet openpyxl-1.7.0+ds1/openpyxl/reader/excel.py000066400000000000000000000167511224514475200206720ustar00rootroot00000000000000# file openpyxl/reader/excel.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Read an xlsx file into Python""" # Python stdlib imports from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile from sys import exc_info import warnings # compatibility imports from openpyxl.shared.compat import file # package imports from openpyxl.shared.exc import OpenModeError, InvalidFileException from openpyxl.shared.ooxml import (ARC_SHARED_STRINGS, ARC_CORE, ARC_WORKBOOK, PACKAGE_WORKSHEETS, ARC_STYLE, ARC_THEME, ARC_CONTENT_TYPES) from openpyxl.shared.compat import unicode, file, BytesIO, StringIO from openpyxl.workbook import Workbook, DocumentProperties from openpyxl.reader.strings import read_string_table from openpyxl.reader.style import read_style_table from openpyxl.reader.workbook import (read_sheets_titles, read_named_ranges, read_properties_core, read_excel_base_date, get_sheet_ids, read_content_types) from openpyxl.reader.worksheet import read_worksheet from openpyxl.reader.iter_worksheet import unpack_worksheet # Use exc_info for Python 2 compatibility with "except Exception[,/ as] e" VALID_WORKSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" VALID_CHARTSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml" WORK_OR_CHART_TYPE = [VALID_WORKSHEET, VALID_CHARTSHEET] CENTRAL_DIRECTORY_SIGNATURE = '\x50\x4b\x05\x06' def repair_central_directory(zipFile, is_file_instance): ''' trims trailing data from the central directory code taken from http://stackoverflow.com/a/7457686/570216, courtesy of Uri Cohen ''' f = zipFile if is_file_instance else open(zipFile, 'r+b') data = f.read() if hasattr(data, "decode"): data = data.decode("utf-8") pos = data.find(CENTRAL_DIRECTORY_SIGNATURE) # End of central directory signature if (pos > 0): sio = StringIO(data) sio.seek(pos + 22) # size of 'ZIP end of central directory record' sio.truncate() sio.seek(0) return sio f.seek(0) return f def load_workbook(filename, use_iterators=False, keep_vba=False, guess_types=True): """Open the given filename and return the workbook :param filename: the path to open or a file-like object :type filename: string or a file-like object open in binary mode c.f., :class:`zipfile.ZipFile` :param use_iterators: use lazy load for cells :type use_iterators: bool :param keep_vba: preseve vba content (this does NOT mean you can use it) :type keep_vba: bool :param guess_types: guess cell content type and do not read it from the file :type guess_types: bool :rtype: :class:`openpyxl.workbook.Workbook` .. note:: When using lazy load, all worksheets will be :class:`openpyxl.reader.iter_worksheet.IterableWorksheet` and the returned workbook will be read-only. """ is_file_instance = isinstance(filename, file) if is_file_instance: # fileobject must have been opened with 'rb' flag # it is required by zipfile if 'b' not in filename.mode: raise OpenModeError("File-object must be opened in binary mode") try: archive = ZipFile(filename, 'r', ZIP_DEFLATED) except BadZipfile: try: f = repair_central_directory(filename, is_file_instance) archive = ZipFile(f, 'r', ZIP_DEFLATED) except BadZipfile: e = exc_info()[1] raise InvalidFileException(unicode(e)) except (BadZipfile, RuntimeError, IOError, ValueError): e = exc_info()[1] raise InvalidFileException(unicode(e)) wb = Workbook(guess_types=guess_types) if use_iterators: wb._set_optimized_read() if not guess_types: warnings.warn('please note that data types are not guessed ' 'when using iterator reader, so you do not need ' 'to use guess_types=False') try: _load_workbook(wb, archive, filename, use_iterators, keep_vba) except KeyError: e = exc_info()[1] raise InvalidFileException(unicode(e)) if not keep_vba: archive.close() return wb def _load_workbook(wb, archive, filename, use_iterators, keep_vba): valid_files = archive.namelist() # If are going to preserve the vba then attach the archive to the # workbook so that is available for the save. if keep_vba: wb.vba_archive = archive # get workbook-level information try: wb.properties = read_properties_core(archive.read(ARC_CORE)) wb.read_workbook_settings(archive.read(ARC_WORKBOOK)) except KeyError: wb.properties = DocumentProperties() try: string_table = read_string_table(archive.read(ARC_SHARED_STRINGS)) except KeyError: string_table = {} try: wb.loaded_theme = archive.read(ARC_THEME) # some writers don't output a theme, live with it (fixes #160) except KeyError: assert wb.loaded_theme == None, "even though the theme information is missing there is a theme object ?" style_table = read_style_table(archive.read(ARC_STYLE)) wb.properties.excel_base_date = read_excel_base_date(xml_source=archive.read(ARC_WORKBOOK)) # get worksheets wb.worksheets = [] # remove preset worksheet content_types = read_content_types(archive.read(ARC_CONTENT_TYPES)) sheet_types = [(sheet, contyp) for sheet, contyp in content_types if contyp in WORK_OR_CHART_TYPE] sheet_names = read_sheets_titles(archive.read(ARC_WORKBOOK)) worksheet_names = [worksheet for worksheet, sheet_type in zip(sheet_names, sheet_types) if sheet_type[1] == VALID_WORKSHEET] for i, sheet_name in enumerate(worksheet_names): sheet_codename = 'sheet%d.xml' % (i + 1) worksheet_path = '%s/%s' % (PACKAGE_WORKSHEETS, sheet_codename) if not worksheet_path in valid_files: continue if not use_iterators: new_ws = read_worksheet(archive.read(worksheet_path), wb, sheet_name, string_table, style_table, keep_vba=keep_vba) else: xml_source = unpack_worksheet(archive, worksheet_path) new_ws = read_worksheet(xml_source, wb, sheet_name, string_table, style_table, filename, sheet_codename) wb.add_sheet(new_ws, index=i) wb._named_ranges = read_named_ranges(archive.read(ARC_WORKBOOK), wb) openpyxl-1.7.0+ds1/openpyxl/reader/iter_worksheet.py000066400000000000000000000306161224514475200226240ustar00rootroot00000000000000# file openpyxl/reader/iter_worksheet.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """ Iterators-based worksheet reader *Still very raw* """ import warnings import operator from itertools import groupby from openpyxl.worksheet import Worksheet from openpyxl.cell import (coordinate_from_string, get_column_letter, Cell, column_index_from_string) from openpyxl.reader.style import read_style_table, NumberFormat from openpyxl.shared.date_time import SharedDate from openpyxl.reader.worksheet import read_dimension from openpyxl.shared.compat import unicode from openpyxl.shared.ooxml import (MIN_COLUMN, MAX_COLUMN, PACKAGE_WORKSHEETS, MAX_ROW, MIN_ROW, ARC_STYLE) from openpyxl.shared.compat import iterparse, xrange from zipfile import ZipFile import re import tempfile import zlib import zipfile import struct TYPE_NULL = Cell.TYPE_NULL MISSING_VALUE = None RE_COORDINATE = re.compile('^([A-Z]+)([0-9]+)$') SHARED_DATE = SharedDate() _COL_CONVERSION_CACHE = dict((get_column_letter(i), i) for i in xrange(1, 18279)) def column_index_from_string(str_col, _col_conversion_cache=_COL_CONVERSION_CACHE): # we use a function argument to get indexed name lookup return _col_conversion_cache[str_col] del _COL_CONVERSION_CACHE RAW_ATTRIBUTES = ['row', 'column', 'coordinate', 'internal_value', 'data_type', 'style_id', 'number_format'] try: from collections import namedtuple BaseRawCell = namedtuple('RawCell', RAW_ATTRIBUTES) except ImportError: warnings.warn("""Unable to import 'namedtuple' module, this may cause memory issues when using optimized reader. Please upgrade your Python installation to 2.6+""") class BaseRawCell(object): def __init__(self, *args): assert len(args) == len(RAW_ATTRIBUTES) for attr, val in zip(RAW_ATTRIBUTES, args): setattr(self, attr, val) def _replace(self, **kwargs): self.__dict__.update(kwargs) return self formatter = NumberFormat() class RawCell(BaseRawCell): """Optimized version of the :class:`openpyxl.cell.Cell`, using named tuples. Useful attributes are: * row * column * coordinate * internal_value You can also access if needed: * data_type * number_format """ @property def is_date(self): return formatter.is_date_format(self.number_format) def iter_rows(workbook_name, sheet_name, xml_source, shared_date, string_table, range_string='', row_offset=0, column_offset=0): archive = get_archive_file(workbook_name) source = xml_source if range_string: min_col, min_row, max_col, max_row = get_range_boundaries(range_string, row_offset, column_offset) else: min_col, min_row, max_col, max_row = read_dimension(xml_source=source) min_col = column_index_from_string(min_col) max_col = column_index_from_string(max_col) + 1 max_row += 6 style_table = read_style_table(archive.read(ARC_STYLE)) source.seek(0) p = iterparse(source) return get_squared_range(p, min_col, min_row, max_col, max_row, string_table, style_table, shared_date) def get_rows(p, min_column=MIN_COLUMN, min_row=MIN_ROW, max_column=MAX_COLUMN, max_row=MAX_ROW): return groupby(get_cells(p, min_row, min_column, max_row, max_column), operator.attrgetter('row')) def get_cells(p, min_row, min_col, max_row, max_col, _re_coordinate=RE_COORDINATE): for _event, element in p: if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c': coord = element.get('r') column_str, row = _re_coordinate.match(coord).groups() row = int(row) column = column_index_from_string(column_str) if min_col <= column <= max_col and min_row <= row <= max_row: data_type = element.get('t', 'n') style_id = element.get('s') value = element.findtext('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v') yield RawCell(row, column_str, coord, value, data_type, style_id, None) if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v': continue element.clear() def get_range_boundaries(range_string, row=0, column=0): if ':' in range_string: min_range, max_range = range_string.split(':') min_col, min_row = coordinate_from_string(min_range) max_col, max_row = coordinate_from_string(max_range) min_col = column_index_from_string(min_col) + column max_col = column_index_from_string(max_col) + column min_row += row max_row += row else: min_col, min_row = coordinate_from_string(range_string) min_col = column_index_from_string(min_col) max_col = min_col + 1 max_row = min_row return (min_col, min_row, max_col, max_row) def get_archive_file(archive_name): return ZipFile(archive_name, 'r') def get_xml_source(archive_file, sheet_name): return archive_file.read('%s/%s' % (PACKAGE_WORKSHEETS, sheet_name)) def get_missing_cells(row, columns): return dict([(column, RawCell(row, column, '%s%s' % (column, row), MISSING_VALUE, TYPE_NULL, None, None)) for column in columns]) def get_squared_range(p, min_col, min_row, max_col, max_row, string_table, style_table, shared_date): expected_columns = [get_column_letter(ci) for ci in xrange(min_col, max_col)] current_row = min_row for row, cells in get_rows(p, min_row=min_row, max_row=max_row, min_column=min_col, max_column=max_col): full_row = [] if current_row < row: for gap_row in xrange(current_row, row): dummy_cells = get_missing_cells(gap_row, expected_columns) yield tuple([dummy_cells[column] for column in expected_columns]) current_row = row temp_cells = list(cells) retrieved_columns = dict([(c.column, c) for c in temp_cells]) missing_columns = list(set(expected_columns) - set(retrieved_columns.keys())) replacement_columns = get_missing_cells(row, missing_columns) for column in expected_columns: if column in retrieved_columns: cell = retrieved_columns[column] if cell.style_id is not None: style = style_table[int(cell.style_id)] cell = cell._replace(number_format=style.number_format.format_code) #pylint: disable-msg=W0212 if cell.internal_value is not None: if cell.data_type in Cell.TYPE_STRING: cell = cell._replace(internal_value=unicode(string_table[int(cell.internal_value)])) #pylint: disable-msg=W0212 elif cell.data_type == Cell.TYPE_BOOL: cell = cell._replace(internal_value=cell.internal_value == '1') elif cell.is_date: cell = cell._replace(internal_value=shared_date.from_julian(float(cell.internal_value))) elif cell.data_type == Cell.TYPE_NUMERIC: cell = cell._replace(internal_value=float(cell.internal_value)) elif cell.data_type in(Cell.TYPE_INLINE, Cell.TYPE_FORMULA_CACHE_STRING): cell = cell._replace(internal_value=unicode(cell.internal_value)) full_row.append(cell) else: full_row.append(replacement_columns[column]) current_row = row + 1 yield tuple(full_row) #------------------------------------------------------------------------------ class IterableWorksheet(Worksheet): def __init__(self, parent_workbook, title, workbook_name, sheet_codename, xml_source, string_table): Worksheet.__init__(self, parent_workbook, title) self._workbook_name = workbook_name self._sheet_codename = sheet_codename self._xml_source = xml_source self._string_table = string_table min_col, min_row, max_col, max_row = read_dimension(xml_source=xml_source) self._max_row = max_row self._max_column = max_col self._dimensions = '%s%s:%s%s' % (min_col, min_row, max_col, max_row) self._shared_date = SharedDate(base_date=parent_workbook.excel_base_date) def iter_rows(self, range_string='', row_offset=0, column_offset=0): """ Returns a squared range based on the `range_string` parameter, using generators. :param range_string: range of cells (e.g. 'A1:C4') :type range_string: string :param row: row index of the cell (e.g. 4) :type row: int :param column: column index of the cell (e.g. 3) :type column: int :rtype: generator """ return iter_rows(workbook_name=self._workbook_name, sheet_name=self._sheet_codename, xml_source=self._xml_source, range_string=range_string, row_offset=row_offset, column_offset=column_offset, shared_date=self._shared_date, string_table=self._string_table) def cell(self, *args, **kwargs): raise NotImplementedError("use 'iter_rows()' instead") def range(self, *args, **kwargs): raise NotImplementedError("use 'iter_rows()' instead") def calculate_dimension(self): return self._dimensions def get_highest_column(self): return column_index_from_string(self._max_column) def get_highest_row(self): return self._max_row def unpack_worksheet(archive, filename): temp_file = tempfile.TemporaryFile(mode='rb+', prefix='openpyxl.', suffix='.unpack.temp') zinfo = archive.getinfo(filename) if zinfo.compress_type == zipfile.ZIP_STORED: decoder = None elif zinfo.compress_type == zipfile.ZIP_DEFLATED: decoder = zlib.decompressobj(-zlib.MAX_WBITS) else: raise zipfile.BadZipFile("Unrecognized compression method") archive.fp.seek(_get_file_offset(archive, zinfo)) bytes_to_read = zinfo.compress_size while True: buff = archive.fp.read(min(bytes_to_read, 102400)) if not buff: break bytes_to_read -= len(buff) if decoder: buff = decoder.decompress(buff) temp_file.write(buff) return temp_file def _get_file_offset(archive, zinfo): try: return zinfo.file_offset except AttributeError: # From http://stackoverflow.com/questions/3781261/how-to-simulate-zipfile-open-in-python-2-5 # Seek over the fixed size fields to the "file name length" field in # the file header (26 bytes). Unpack this and the "extra field length" # field ourselves as info.extra doesn't seem to be the correct length. archive.fp.seek(zinfo.header_offset + 26) file_name_len, extra_len = struct.unpack(" 1: raise TypeError('expected at most 1 arguments, got %d' % len(args)) try: self.__root except AttributeError: self.__root = root = [] # sentinel node root[:] = [root, root, None] self.__map = {} self.__update(*args, **kwds) def __setitem__(self, key, value, dict_setitem=dict.__setitem__): 'od.__setitem__(i, y) <==> od[i]=y' # Setting a new item creates a new link which goes at the end of the linked # list, and the inherited dictionary is updated with the new key/value pair. if key not in self: root = self.__root last = root[0] last[1] = root[0] = self.__map[key] = [last, root, key] dict_setitem(self, key, value) def __delitem__(self, key, dict_delitem=dict.__delitem__): 'od.__delitem__(y) <==> del od[y]' # Deleting an existing item uses self.__map to find the link which is # then removed by updating the links in the predecessor and successor nodes. dict_delitem(self, key) link_prev, link_next, key = self.__map.pop(key) link_prev[1] = link_next link_next[0] = link_prev def __iter__(self): 'od.__iter__() <==> iter(od)' root = self.__root curr = root[1] while curr is not root: yield curr[2] curr = curr[1] def __reversed__(self): 'od.__reversed__() <==> reversed(od)' root = self.__root curr = root[0] while curr is not root: yield curr[2] curr = curr[0] def clear(self): 'od.clear() -> None. Remove all items from od.' try: for node in self.__map.itervalues(): del node[:] root = self.__root root[:] = [root, root, None] self.__map.clear() except AttributeError: pass dict.clear(self) def popitem(self, last=True): '''od.popitem() -> (k, v), return and remove a (key, value) pair. Pairs are returned in LIFO order if last is true or FIFO order if false. ''' if not self: raise KeyError('dictionary is empty') root = self.__root if last: link = root[0] link_prev = link[0] link_prev[1] = root root[0] = link_prev else: link = root[1] link_next = link[1] root[1] = link_next link_next[0] = root key = link[2] del self.__map[key] value = dict.pop(self, key) return key, value # -- the following methods do not depend on the internal structure -- def keys(self): 'od.keys() -> list of keys in od' return list(self) def values(self): 'od.values() -> list of values in od' return [self[key] for key in self] def items(self): 'od.items() -> list of (key, value) pairs in od' return [(key, self[key]) for key in self] def iterkeys(self): 'od.iterkeys() -> an iterator over the keys in od' return iter(self) def itervalues(self): 'od.itervalues -> an iterator over the values in od' for k in self: yield self[k] def iteritems(self): 'od.iteritems -> an iterator over the (key, value) items in od' for k in self: yield (k, self[k]) def update(*args, **kwds): '''od.update(E, **F) -> None. Update od from dict/iterable E and F. If E is a dict instance, does: for k in E: od[k] = E[k] If E has a .keys() method, does: for k in E.keys(): od[k] = E[k] Or if E is an iterable of items, does: for k, v in E: od[k] = v In either case, this is followed by: for k, v in F.items(): od[k] = v ''' if len(args) > 2: raise TypeError('update() takes at most 2 positional ' 'arguments (%d given)' % (len(args),)) elif not args: raise TypeError('update() takes at least 1 argument (0 given)') self = args[0] # Make progressively weaker assumptions about "other" other = () if len(args) == 2: other = args[1] if isinstance(other, dict): for key in other: self[key] = other[key] elif hasattr(other, 'keys'): for key in other.keys(): self[key] = other[key] else: for key, value in other: self[key] = value for key, value in kwds.items(): self[key] = value __update = update # let subclasses override update without breaking __init__ __marker = object() def pop(self, key, default=__marker): '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. If key is not found, d is returned if given, otherwise KeyError is raised. ''' if key in self: result = self[key] del self[key] return result if default is self.__marker: raise KeyError(key) return default def setdefault(self, key, default=None): 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' if key in self: return self[key] self[key] = default return default def __repr__(self, _repr_running={}): 'od.__repr__() <==> repr(od)' call_key = id(self), _get_ident() if call_key in _repr_running: return '...' _repr_running[call_key] = 1 try: if not self: return '%s()' % (self.__class__.__name__,) return '%s(%r)' % (self.__class__.__name__, self.items()) finally: del _repr_running[call_key] def __reduce__(self): 'Return state information for pickling' items = [[k, self[k]] for k in self] inst_dict = vars(self).copy() for k in vars(OrderedDict()): inst_dict.pop(k, None) if inst_dict: return (self.__class__, (items,), inst_dict) return self.__class__, (items,) def copy(self): 'od.copy() -> a shallow copy of od' return self.__class__(self) @classmethod def fromkeys(cls, iterable, value=None): '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S and values equal to v (which defaults to None). ''' d = cls() for key in iterable: d[key] = value return d def __eq__(self, other): '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive while comparison to a regular mapping is order-insensitive. ''' if isinstance(other, OrderedDict): return len(self) == len(other) and self.items() == other.items() return dict.__eq__(self, other) def __ne__(self, other): return not self == other # -- the following methods are only used in Python 2.7 -- def viewkeys(self): "od.viewkeys() -> a set-like object providing a view on od's keys" return KeysView(self) def viewvalues(self): "od.viewvalues() -> an object providing a view on od's values" return ValuesView(self) def viewitems(self): "od.viewitems() -> a set-like object providing a view on od's items" return ItemsView(self) # # end of http://code.activestate.com/recipes/576693/ }}} openpyxl-1.7.0+ds1/openpyxl/shared/compat/strings.py000066400000000000000000000027711224514475200225470ustar00rootroot00000000000000# Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file import sys VER = sys.version_info if VER[0] == 3: basestring = str unicode = str from io import BufferedReader file = BufferedReader else: basestring = basestring unicode = unicode file = file if VER[0] == 3: from io import BytesIO, StringIO else: from StringIO import StringIO BytesIO = StringIO openpyxl-1.7.0+ds1/openpyxl/shared/compat/tempnamedfile.py000066400000000000000000000030301224514475200236550ustar00rootroot00000000000000# Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file import tempfile import os def NamedTemporaryFile(mode, suffix, prefix, delete=False): try: return tempfile.NamedTemporaryFile(mode=mode, suffix=suffix, prefix=prefix, delete=delete) except TypeError: handle, filename = tempfile.mkstemp(suffix=suffix, prefix=prefix) os.close(handle) fobj = open(filename, mode) return fobj openpyxl-1.7.0+ds1/openpyxl/shared/date_time.py000066400000000000000000000152621224514475200215250ustar00rootroot00000000000000# file openpyxl/shared/date_time.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Manage Excel date weirdness.""" # Python stdlib imports from __future__ import division from math import floor import calendar import datetime import time import re # constants CALENDAR_WINDOWS_1900 = 1900 CALENDAR_MAC_1904 = 1904 W3CDTF_FORMAT = '%Y-%m-%dT%H:%M:%SZ' RE_W3CDTF = '(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(.(\d{2}))?Z?' EPOCH = datetime.datetime.utcfromtimestamp(0) def datetime_to_W3CDTF(dt): """Convert from a datetime to a timestamp string.""" return datetime.datetime.strftime(dt, W3CDTF_FORMAT) def W3CDTF_to_datetime(formatted_string): """Convert from a timestamp string to a datetime object.""" match = re.match(RE_W3CDTF,formatted_string) digits = map(int, match.groups()[:6]) return datetime.datetime(*digits) class SharedDate(object): """Date formatting utilities for Excel with shared state. Excel has a two primary date tracking schemes: Windows - Day 1 == 1900-01-01 Mac - Day 1 == 1904-01-01 SharedDate stores which system we are using and converts dates between Python and Excel accordingly. """ datetime_object_type = 'DateTime' def __init__(self,base_date=CALENDAR_WINDOWS_1900): if int(base_date)==CALENDAR_MAC_1904: self.excel_base_date = CALENDAR_MAC_1904 elif int(base_date)==CALENDAR_WINDOWS_1900: self.excel_base_date = CALENDAR_WINDOWS_1900 else: raise ValueError("base_date:%s invalid"%base_date) def datetime_to_julian(self, date): """Convert from python datetime to excel julian date representation.""" if isinstance(date, datetime.datetime): return self.to_julian(date.year, date.month, date.day, \ hours=date.hour, minutes=date.minute, seconds=date.second + date.microsecond * 1.0e-6) elif isinstance(date, datetime.date): return self.to_julian(date.year, date.month, date.day) elif isinstance(date, datetime.time): return self.time_to_julian(hours=date.hour, minutes=date.minute, seconds=date.second + date.microsecond * 1.0e-6) elif isinstance(date, datetime.timedelta): return self.time_to_julian(hours=0, minutes=0, seconds=date.seconds + date.days * 3600 * 24) def time_to_julian(self, hours, minutes, seconds): return ((hours * 3600) + (minutes * 60) + seconds) / 86400 def to_julian(self, year, month, day, hours=0, minutes=0, seconds=0): """Convert from Python date to Excel JD.""" # explicitly disallow bad years # Excel 2000 treats JD=0 as 1/0/1900 (buggy, disallow) # Excel 2000 treats JD=2958466 as a bad date (Y10K bug!) if year < 1900 or year > 10000: msg = 'Year not supported by Excel: %s' % year raise ValueError(msg) if self.excel_base_date == CALENDAR_WINDOWS_1900: # Fudge factor for the erroneous fact that the year 1900 is # treated as a Leap Year in MS Excel. This affects every date # following 28th February 1900 if year == 1900 and month <= 2: excel_1900_leap_year = False else: excel_1900_leap_year = True excel_base_date = 2415020 elif self.excel_base_date == CALENDAR_MAC_1904: excel_base_date = 2416481 excel_1900_leap_year = False else: raise NotImplementedError('base date supported.') # Julian base date adjustment if month > 2: month = month - 3 else: month = month + 9 year -= 1 # Calculate the Julian Date, then subtract the Excel base date # JD 2415020 = 31 - Dec - 1899 -> Excel Date of 0 century, decade = int(str(year)[:2]), int(str(year)[2:]) excel_date = floor(146097 * century / 4) + \ floor((1461 * decade) / 4) + floor((153 * month + 2) / 5) + \ day + 1721119 - excel_base_date if excel_1900_leap_year: excel_date += 1 # check to ensure that we exclude 2/29/1900 as a possible value if self.excel_base_date == CALENDAR_WINDOWS_1900 \ and excel_date == 60: msg = 'Error: Excel believes 1900 was a leap year' raise ValueError(msg) excel_time = self.time_to_julian(hours, minutes, seconds) return excel_date + excel_time def from_julian(self, value=0): """Convert from the Excel JD back to a date""" if self.excel_base_date == CALENDAR_WINDOWS_1900: excel_base_date = 25569 if value < 60: excel_base_date -= 1 elif value == 60: msg = 'Error: Excel believes 1900 was a leap year' raise ValueError(msg) elif self.excel_base_date == CALENDAR_MAC_1904: excel_base_date = 24107 else: raise NotImplementedError('base date supported.') if value >= 1: utc_days = value - excel_base_date return EPOCH + datetime.timedelta(days=utc_days) elif value >= 0: hours = floor(value * 24) mins = floor(value * 24 * 60) - floor(hours * 60) secs = floor(value * 24 * 60 * 60) - floor(hours * 60 * 60) - \ floor(mins * 60) return datetime.time(int(hours), int(mins), int(secs)) else: msg = 'Negative dates (%s) are not supported' % value raise ValueError(msg) openpyxl-1.7.0+ds1/openpyxl/shared/exc.py000066400000000000000000000045701224514475200203510ustar00rootroot00000000000000# file openpyxl/shared/exc.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Definitions for openpyxl shared exception classes.""" class CellCoordinatesException(Exception): """Error for converting between numeric and A1-style cell references.""" class ColumnStringIndexException(Exception): """Error for bad column names in A1-style cell references.""" class DataTypeException(Exception): """Error for any data type inconsistencies.""" class NamedRangeException(Exception): """Error for badly formatted named ranges.""" class SheetTitleException(Exception): """Error for bad sheet names.""" class InsufficientCoordinatesException(Exception): """Error for partially specified cell coordinates.""" class OpenModeError(Exception): """Error for fileobj opened in non-binary mode.""" class InvalidFileException(Exception): """Error for trying to open a non-ooxml file.""" class ReadOnlyWorkbookException(Exception): """Error for trying to modify a read-only workbook""" class MissingNumberFormat(Exception): """Error when a referenced number format is not in the stylesheet""" class WorkbookAlreadySaved(Exception): """Error when attempting to perform operations on a dump workbook while it has already been dumped once""" openpyxl-1.7.0+ds1/openpyxl/shared/ooxml.py000066400000000000000000000052171224514475200207270ustar00rootroot00000000000000# file openpyxl/shared/ooxml.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Constants for fixed paths in a file and xml namespace urls.""" MIN_ROW = 0 MIN_COLUMN = 0 MAX_COLUMN = 16384 MAX_ROW = 1048576 # constants PACKAGE_PROPS = 'docProps' PACKAGE_XL = 'xl' PACKAGE_RELS = '_rels' PACKAGE_THEME = PACKAGE_XL + '/' + 'theme' PACKAGE_WORKSHEETS = PACKAGE_XL + '/' + 'worksheets' PACKAGE_DRAWINGS = PACKAGE_XL + '/' + 'drawings' PACKAGE_CHARTS = PACKAGE_XL + '/' + 'charts' PACKAGE_IMAGES = PACKAGE_XL + '/' + 'media' ARC_CONTENT_TYPES = '[Content_Types].xml' ARC_ROOT_RELS = PACKAGE_RELS + '/.rels' ARC_WORKBOOK_RELS = PACKAGE_XL + '/' + PACKAGE_RELS + '/workbook.xml.rels' ARC_CORE = PACKAGE_PROPS + '/core.xml' ARC_APP = PACKAGE_PROPS + '/app.xml' ARC_WORKBOOK = PACKAGE_XL + '/workbook.xml' ARC_STYLE = PACKAGE_XL + '/styles.xml' ARC_THEME = PACKAGE_THEME + '/theme1.xml' ARC_SHARED_STRINGS = PACKAGE_XL + '/sharedStrings.xml' ARC_VBA = ('xl/vba', 'xl/activeX', 'xl/drawings', 'xl/media', 'xl/ctrlProps', 'xl/worksheets/_rels', 'customUI', '_rels') NAMESPACES = { 'cp': 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties', 'dc': 'http://purl.org/dc/elements/1.1/', 'dcterms': 'http://purl.org/dc/terms/', 'dcmitype': 'http://purl.org/dc/dcmitype/', 'xsi': 'http://www.w3.org/2001/XMLSchema-instance', 'vt': 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes', 'xml': 'http://www.w3.org/XML/1998/namespace', 'main': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main' } openpyxl-1.7.0+ds1/openpyxl/shared/password_hasher.py000066400000000000000000000035041224514475200227620ustar00rootroot00000000000000# file openpyxl/shared/password_hasher.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Basic password hashing.""" def hash_password(plaintext_password=''): """Create a password hash from a given string. This method is based on the algorithm provided by Daniel Rentz of OpenOffice and the PEAR package Spreadsheet_Excel_Writer by Xavier Noguer . """ password = 0x0000 i = 1 for char in plaintext_password: value = ord(char) << i rotated_bits = value >> 15 value &= 0x7fff password ^= (value | rotated_bits) i += 1 password ^= len(plaintext_password) password ^= 0xCE4B return str(hex(password)).upper()[2:] openpyxl-1.7.0+ds1/openpyxl/shared/units.py000066400000000000000000000041631224514475200207320ustar00rootroot00000000000000# file openpyxl/shared/units.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file import math def cm_to_pixels(value): return int(value * 44.6) def pixels_to_cm(value): return (1 / cm_to_pixels(value)) def pixels_to_EMU(value): return int(round(value * 9525)) def EMU_to_pixels(value): if not value: return 0 else: return round(value / 9525.) def EMU_to_cm(value): if not value: return 0 else: return (EMU_to_pixels(value) * 2.57 / 96) def pixels_to_points(value): return value * 0.67777777 def points_to_pixels(value): if not value: return 0 else: return int(math.ceil(value * 1.333333333)) def degrees_to_angle(value): return int(round(value * 60000)) def angle_to_degrees(value): if not value: return 0 else: return round(value / 60000.) def short_color(color): """ format a color to its short size """ if len(color) > 6: return color[2:] else: return color openpyxl-1.7.0+ds1/openpyxl/shared/xmltools.py000066400000000000000000000116071224514475200214520ustar00rootroot00000000000000# Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # register_namespace function under a different licence: # The ElementTree toolkit is # # Copyright (c) 1999-2008 by Fredrik Lundh # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, # and will comply with the following terms and conditions: # # Permission to use, copy, modify, and distribute this software and # its associated documentation for any purpose and without fee is # hereby granted, provided that the above copyright notice appears in # all copies, and that both that copyright notice and this permission # notice appear in supporting documentation, and that the name of # Secret Labs AB or the author not be used in advertising or publicity # pertaining to distribution of the software without specific, written # prior permission. # # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE # OF THIS SOFTWARE. # -------------------------------------------------------------------- # Licensed to PSF under a Contributor Agreement. # See http://www.python.org/psf/license for licensing details. """Shared xml tools. Shortcut functions taken from: http://lethain.com/entry/2009/jan/22/handling-very-large-csv-and-xml-files-in-python/ """ # Python stdlib imports from openpyxl import __name__ as prefix from openpyxl.shared.compat import OrderedDict from xml.sax.saxutils import XMLGenerator from xml.sax.xmlreader import AttributesNSImpl from xml.etree.ElementTree import (ElementTree, Element, SubElement, QName, fromstring, tostring) try: from xml.etree.ElementTree import register_namespace except: from openpyxl.shared.compat.elementtree import register_namespace def get_document_content(xml_node): """Print nicely formatted xml to a string.""" pretty_indent(xml_node) return tostring(xml_node, 'utf-8') def pretty_indent(elem, level=0): """Format xml with nice indents and line breaks.""" i = "\n" + level * " " if len(elem): if not elem.text or not elem.text.strip(): elem.text = i + " " if not elem.tail or not elem.tail.strip(): elem.tail = i for elem in elem: pretty_indent(elem, level + 1) if not elem.tail or not elem.tail.strip(): elem.tail = i else: if level and (not elem.tail or not elem.tail.strip()): elem.tail = i def start_tag(doc, name, attr=None, body=None, namespace=None): """Wrapper to start an xml tag.""" if attr is None: attr = {} dct_type = dict elif isinstance(attr, OrderedDict): dct_type = OrderedDict else: dct_type = dict attr_vals = dct_type() attr_keys = dct_type() for key, val in attr.items(): key_tuple = (namespace, key) attr_vals[key_tuple] = val attr_keys[key_tuple] = key attr2 = AttributesNSImpl(attr_vals, attr_keys) doc.startElementNS((namespace, name), name, attr2) if body: doc.characters(body) def end_tag(doc, name, namespace=None): """Wrapper to close an xml tag.""" doc.endElementNS((namespace, name), name) def tag(doc, name, attr=None, body=None, namespace=None): """Wrapper to print xml tags and comments.""" if attr is None: attr = {} start_tag(doc, name, attr, body, namespace) end_tag(doc, name, namespace) openpyxl-1.7.0+ds1/openpyxl/style.py000066400000000000000000000311371224514475200174630ustar00rootroot00000000000000# file openpyxl/style.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Style and formatting option tracking.""" # Python stdlib imports import re try: from hashlib import md5 except ImportError: from md5 import md5 from copy import deepcopy from openpyxl.shared.compat import any class HashableObject(object): """Define how to hash property classes.""" __fields__ = None __leaf__ = False def __repr__(self): return ':'.join([repr(getattr(self, x)) for x in self.__fields__]) def __hash__(self): # return int(md5(repr(self)).hexdigest(), 16) return hash(repr(self)) class Color(HashableObject): """Named colors for use in styles.""" BLACK = 'FF000000' WHITE = 'FFFFFFFF' RED = 'FFFF0000' DARKRED = 'FF800000' BLUE = 'FF0000FF' DARKBLUE = 'FF000080' GREEN = 'FF00FF00' DARKGREEN = 'FF008000' YELLOW = 'FFFFFF00' DARKYELLOW = 'FF808000' __fields__ = ('index',) __slots__ = __fields__ __leaf__ = True def __init__(self, index): super(Color, self).__init__() self.index = index class Font(HashableObject): """Font options used in styles.""" UNDERLINE_NONE = 'none' UNDERLINE_DOUBLE = 'double' UNDERLINE_DOUBLE_ACCOUNTING = 'doubleAccounting' UNDERLINE_SINGLE = 'single' UNDERLINE_SINGLE_ACCOUNTING = 'singleAccounting' __fields__ = ('name', 'size', 'bold', 'italic', 'superscript', 'subscript', 'underline', 'strikethrough', 'color') __slots__ = __fields__ def __init__(self): super(Font, self).__init__() self.name = 'Calibri' self.size = 11 self.bold = False self.italic = False self.superscript = False self.subscript = False self.underline = self.UNDERLINE_NONE self.strikethrough = False self.color = Color(Color.BLACK) class Fill(HashableObject): """Area fill patterns for use in styles.""" FILL_NONE = 'none' FILL_SOLID = 'solid' FILL_GRADIENT_LINEAR = 'linear' FILL_GRADIENT_PATH = 'path' FILL_PATTERN_DARKDOWN = 'darkDown' FILL_PATTERN_DARKGRAY = 'darkGray' FILL_PATTERN_DARKGRID = 'darkGrid' FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal' FILL_PATTERN_DARKTRELLIS = 'darkTrellis' FILL_PATTERN_DARKUP = 'darkUp' FILL_PATTERN_DARKVERTICAL = 'darkVertical' FILL_PATTERN_GRAY0625 = 'gray0625' FILL_PATTERN_GRAY125 = 'gray125' FILL_PATTERN_LIGHTDOWN = 'lightDown' FILL_PATTERN_LIGHTGRAY = 'lightGray' FILL_PATTERN_LIGHTGRID = 'lightGrid' FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal' FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis' FILL_PATTERN_LIGHTUP = 'lightUp' FILL_PATTERN_LIGHTVERTICAL = 'lightVertical' FILL_PATTERN_MEDIUMGRAY = 'mediumGray' __fields__ = ('fill_type', 'rotation', 'start_color', 'end_color') __slots__ = __fields__ def __init__(self): super(Fill, self).__init__() self.fill_type = self.FILL_NONE self.rotation = 0 self.start_color = Color(Color.WHITE) self.end_color = Color(Color.BLACK) class Border(HashableObject): """Border options for use in styles.""" BORDER_NONE = 'none' BORDER_DASHDOT = 'dashDot' BORDER_DASHDOTDOT = 'dashDotDot' BORDER_DASHED = 'dashed' BORDER_DOTTED = 'dotted' BORDER_DOUBLE = 'double' BORDER_HAIR = 'hair' BORDER_MEDIUM = 'medium' BORDER_MEDIUMDASHDOT = 'mediumDashDot' BORDER_MEDIUMDASHDOTDOT = 'mediumDashDotDot' BORDER_MEDIUMDASHED = 'mediumDashed' BORDER_SLANTDASHDOT = 'slantDashDot' BORDER_THICK = 'thick' BORDER_THIN = 'thin' __fields__ = ('border_style', 'color') __slots__ = __fields__ def __init__(self): super(Border, self).__init__() self.border_style = self.BORDER_NONE self.color = Color(Color.BLACK) class Borders(HashableObject): """Border positioning for use in styles.""" DIAGONAL_NONE = 0 DIAGONAL_UP = 1 DIAGONAL_DOWN = 2 DIAGONAL_BOTH = 3 __fields__ = ('left', 'right', 'top', 'bottom', 'diagonal', 'diagonal_direction', 'all_borders', 'outline', 'inside', 'vertical', 'horizontal') __slots__ = __fields__ def __init__(self): super(Borders, self).__init__() self.left = Border() self.right = Border() self.top = Border() self.bottom = Border() self.diagonal = Border() self.diagonal_direction = self.DIAGONAL_NONE self.all_borders = Border() self.outline = Border() self.inside = Border() self.vertical = Border() self.horizontal = Border() class Alignment(HashableObject): """Alignment options for use in styles.""" HORIZONTAL_GENERAL = 'general' HORIZONTAL_LEFT = 'left' HORIZONTAL_RIGHT = 'right' HORIZONTAL_CENTER = 'center' HORIZONTAL_CENTER_CONTINUOUS = 'centerContinuous' HORIZONTAL_JUSTIFY = 'justify' VERTICAL_BOTTOM = 'bottom' VERTICAL_TOP = 'top' VERTICAL_CENTER = 'center' VERTICAL_JUSTIFY = 'justify' __fields__ = ('horizontal', 'vertical', 'text_rotation', 'wrap_text', 'shrink_to_fit', 'indent') __slots__ = __fields__ __leaf__ = True def __init__(self): super(Alignment, self).__init__() self.horizontal = self.HORIZONTAL_GENERAL self.vertical = self.VERTICAL_BOTTOM self.text_rotation = 0 self.wrap_text = False self.shrink_to_fit = False self.indent = 0 class NumberFormat(HashableObject): """Numer formatting for use in styles.""" FORMAT_GENERAL = 'General' FORMAT_TEXT = '@' FORMAT_NUMBER = '0' FORMAT_NUMBER_00 = '0.00' FORMAT_NUMBER_COMMA_SEPARATED1 = '#,##0.00' FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-' FORMAT_PERCENTAGE = '0%' FORMAT_PERCENTAGE_00 = '0.00%' FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd' FORMAT_DATE_YYYYMMDD = 'yy-mm-dd' FORMAT_DATE_DDMMYYYY = 'dd/mm/yy' FORMAT_DATE_DMYSLASH = 'd/m/y' FORMAT_DATE_DMYMINUS = 'd-m-y' FORMAT_DATE_DMMINUS = 'd-m' FORMAT_DATE_MYMINUS = 'm-y' FORMAT_DATE_XLSX14 = 'mm-dd-yy' FORMAT_DATE_XLSX15 = 'd-mmm-yy' FORMAT_DATE_XLSX16 = 'd-mmm' FORMAT_DATE_XLSX17 = 'mmm-yy' FORMAT_DATE_XLSX22 = 'm/d/yy h:mm' FORMAT_DATE_DATETIME = 'd/m/y h:mm' FORMAT_DATE_TIME1 = 'h:mm AM/PM' FORMAT_DATE_TIME2 = 'h:mm:ss AM/PM' FORMAT_DATE_TIME3 = 'h:mm' FORMAT_DATE_TIME4 = 'h:mm:ss' FORMAT_DATE_TIME5 = 'mm:ss' FORMAT_DATE_TIME6 = 'h:mm:ss' FORMAT_DATE_TIME7 = 'i:s.S' FORMAT_DATE_TIME8 = 'h:mm:ss@' FORMAT_DATE_TIMEDELTA = '[hh]:mm:ss' FORMAT_DATE_YYYYMMDDSLASH = 'yy/mm/dd@' FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-' FORMAT_CURRENCY_USD = '$#,##0_-' FORMAT_CURRENCY_EUR_SIMPLE = '[$EUR ]#,##0.00_-' _BUILTIN_FORMATS = { 0: 'General', 1: '0', 2: '0.00', 3: '#,##0', 4: '#,##0.00', 9: '0%', 10: '0.00%', 11: '0.00E+00', 12: '# ?/?', 13: '# ??/??', 14: 'mm-dd-yy', 15: 'd-mmm-yy', 16: 'd-mmm', 17: 'mmm-yy', 18: 'h:mm AM/PM', 19: 'h:mm:ss AM/PM', 20: 'h:mm', 21: 'h:mm:ss', 22: 'm/d/yy h:mm', 37: '#,##0 (#,##0)', 38: '#,##0 [Red](#,##0)', 39: '#,##0.00(#,##0.00)', 40: '#,##0.00[Red](#,##0.00)', 41: '_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)', 42: '_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)', 43: '_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)', 44: '_("$"* #,##0.00_)_("$"* \(#,##0.00\)_("$"* "-"??_)_(@_)', 45: 'mm:ss', 46: '[h]:mm:ss', 47: 'mmss.0', 48: '##0.0E+0', 49: '@', } _BUILTIN_FORMATS_REVERSE = dict( [(value, key) for key, value in _BUILTIN_FORMATS.items()]) __fields__ = ('_format_code', '_format_index') __slots__ = __fields__ __leaf__ = True DATE_INDICATORS = 'dmyhs' BAD_DATE_RE = re.compile(r'(\[|").*[dmhys].*(\]|")') #def __eq__(self, other): #return self.format_code == other.format_code #def __ne__(self, other): #return self.format_code != other.format_code #def __hash__(self): #return super(NumberFormat, self).__hash__() def __init__(self): super(NumberFormat, self).__init__() self._format_code = self.FORMAT_GENERAL self._format_index = 0 def _set_format_code(self, format_code = FORMAT_GENERAL): """Setter for the format_code property.""" self._format_code = format_code self._format_index = self.builtin_format_id(format = format_code) def _get_format_code(self): """Getter for the format_code property.""" return self._format_code format_code = property(_get_format_code, _set_format_code) def builtin_format_code(self, index): """Return one of the standard format codes by index.""" return self._BUILTIN_FORMATS[index] def is_builtin(self, format = None): """Check if a format code is a standard format code.""" if format is None: format = self._format_code return format in self._BUILTIN_FORMATS.values() def builtin_format_id(self, format): """Return the id of a standard style.""" return self._BUILTIN_FORMATS_REVERSE.get(format, None) def is_date_format(self, format = None): """Check if the number format is actually representing a date.""" if format is None: format = self._format_code if any([x in format for x in self.DATE_INDICATORS]): if self.BAD_DATE_RE.search(format) is None: return True return False class Protection(HashableObject): """Protection options for use in styles.""" PROTECTION_INHERIT = 'inherit' PROTECTION_PROTECTED = 'protected' PROTECTION_UNPROTECTED = 'unprotected' __fields__ = ('locked', 'hidden') __slots__ = __fields__ __leaf__ = True def __init__(self): super(Protection, self).__init__() self.locked = self.PROTECTION_INHERIT self.hidden = self.PROTECTION_INHERIT class Style(HashableObject): """Style object containing all formatting details.""" __fields__ = ('font', 'fill', 'borders', 'alignment', 'number_format', 'protection') __slots__ = __fields__ def __init__(self, static=False): super(Style, self).__init__() self.static = static self.font = Font() self.fill = Fill() self.borders = Borders() self.alignment = Alignment() self.number_format = NumberFormat() self.protection = Protection() def copy(self): new_style = Style() new_style.font = deepcopy(self.font) new_style.fill = deepcopy(self.fill) new_style.borders = deepcopy(self.borders) new_style.alignment = deepcopy(self.alignment) new_style.number_format = deepcopy(self.number_format) new_style.protection = deepcopy(self.protection) return new_style DEFAULTS = Style() openpyxl-1.7.0+ds1/openpyxl/tests/000077500000000000000000000000001224514475200171065ustar00rootroot00000000000000openpyxl-1.7.0+ds1/openpyxl/tests/__init__.py000066400000000000000000000023051224514475200212170ustar00rootroot00000000000000# file openpyxl/tests/__init__.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file openpyxl-1.7.0+ds1/openpyxl/tests/helper.py000066400000000000000000000067731224514475200207540ustar00rootroot00000000000000# file openpyxl/tests/helper.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # Python stdlib imports import os import os.path import shutil import difflib from pprint import pprint from tempfile import gettempdir from sys import version_info # package imports from openpyxl.shared.compat import BytesIO, unicode, StringIO from openpyxl.shared.xmltools import fromstring, ElementTree from openpyxl.shared.xmltools import pretty_indent # constants DATADIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test_data')) TMPDIR = os.path.join(gettempdir(), 'openpyxl_test_temp') def make_tmpdir(): try: os.makedirs(TMPDIR) except OSError: pass def clean_tmpdir(): if os.path.isdir(TMPDIR): shutil.rmtree(TMPDIR, ignore_errors = True) def assert_equals_file_content(reference_file, fixture, filetype = 'xml'): if os.path.isfile(fixture): fixture_file = open(fixture) try: fixture_content = fixture_file.read() finally: fixture_file.close() else: fixture_content = fixture expected_file = open(reference_file) try: expected_content = expected_file.read() finally: expected_file.close() if filetype == 'xml': fixture_content = fromstring(fixture_content) pretty_indent(fixture_content) temp = BytesIO() ElementTree(fixture_content).write(temp) fixture_content = temp.getvalue() expected_content = fromstring(expected_content) pretty_indent(expected_content) temp = BytesIO() ElementTree(expected_content).write(temp) expected_content = temp.getvalue() fixture_lines = unicode(fixture_content).split('\n') expected_lines = unicode(expected_content).split('\n') differences = list(difflib.unified_diff(expected_lines, fixture_lines)) if differences: temp = StringIO() pprint(differences, stream = temp) assert False, 'Differences found : %s' % temp.getvalue() def get_xml(xml_node): io = BytesIO() if version_info[0] >= 3 and version_info[1] >= 2: ElementTree(xml_node).write(io, encoding='UTF-8', xml_declaration=True) ret = str(io.getvalue(), 'utf-8') ret = ret.replace('utf-8', 'UTF-8', 1) else: ElementTree(xml_node).write(io, encoding='UTF-8') ret = io.getvalue() io.close() return ret.replace('\n', '') openpyxl-1.7.0+ds1/openpyxl/tests/long/000077500000000000000000000000001224514475200200455ustar00rootroot00000000000000openpyxl-1.7.0+ds1/openpyxl/tests/long/dump_writer_performance.py000066400000000000000000000006221224514475200253410ustar00rootroot00000000000000import openpyxl import tempfile def test_large_append(): wb = openpyxl.Workbook(optimized_write=True) ws = wb.create_sheet() row = ('this is some text', 3.14) total_rows = int(2e4) for idx in xrange(total_rows): if not idx % 10000: print "%.2f%%" % (100 * (float(idx) / float(total_rows))) ws.append(row) wb.save(tempfile.TemporaryFile(mode='wb')) openpyxl-1.7.0+ds1/openpyxl/tests/long/long_dump_thread.py000066400000000000000000000032311224514475200237310ustar00rootroot00000000000000# Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file import threading from openpyxl.workbook import Workbook from openpyxl.shared.compat import StringIO def test_thread_safe_dump(): def dump_workbook(): wb = Workbook(optimized_write=True) ws = wb.create_sheet() ws.append(range(30)) wb.save(filename=StringIO()) for thread_idx in range(400): thread = threading.Thread(target=dump_workbook) thread.start() print("starting thread %d" % thread_idx) openpyxl-1.7.0+ds1/openpyxl/tests/test_cell.py000066400000000000000000000163311224514475200214420ustar00rootroot00000000000000# file openpyxl/tests/test_cell.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # Python stdlib imports from datetime import time, datetime, timedelta # 3rd party imports from nose.tools import eq_, raises, assert_raises #pylint: disable=E0611 # package imports from openpyxl.worksheet import Worksheet from openpyxl.workbook import Workbook from openpyxl.shared.exc import ColumnStringIndexException, \ CellCoordinatesException, DataTypeException from openpyxl.shared.date_time import CALENDAR_WINDOWS_1900 from openpyxl.cell import column_index_from_string, \ coordinate_from_string, get_column_letter, Cell, absolute_coordinate import decimal def build_dummy_worksheet(): class Ws(object): class Wb(object): excel_base_date = CALENDAR_WINDOWS_1900 encoding = 'utf-8' parent = Wb() title = "Dummy Worksheet" return Ws() def test_coordinates(): column, row = coordinate_from_string('ZF46') eq_("ZF", column) eq_(46, row) @raises(CellCoordinatesException) def test_invalid_coordinate(): coordinate_from_string('AAA') @raises(CellCoordinatesException) def test_zero_row(): coordinate_from_string('AQ0') def test_absolute(): eq_('$ZF$51', absolute_coordinate('ZF51')) def test_absolute_multiple(): eq_('$ZF$51:$ZF$53', absolute_coordinate('ZF51:ZF$53')) def test_column_index(): eq_(10, column_index_from_string('J')) eq_(270, column_index_from_string('jJ')) eq_(7030, column_index_from_string('jjj')) def test_bad_column_index(): @raises(ColumnStringIndexException) def _check(bad_string): column_index_from_string(bad_string) bad_strings = ('JJJJ', '', '$', '1',) for bad_string in bad_strings: yield _check, bad_string def test_column_letter_boundries(): assert_raises(ColumnStringIndexException, get_column_letter, 0) assert_raises(ColumnStringIndexException, get_column_letter, 18279) def test_column_letter(): eq_('ZZZ', get_column_letter(18278)) eq_('JJJ', get_column_letter(7030)) eq_('AB', get_column_letter(28)) eq_('AA', get_column_letter(27)) eq_('Z', get_column_letter(26)) def test_initial_value(): ws = build_dummy_worksheet() cell = Cell(ws, 'A', 1, value='17.5') eq_(cell.TYPE_NUMERIC, cell.data_type) class TestCellValueTypes(object): @classmethod def setup_class(cls): ws = build_dummy_worksheet() cls.cell = Cell(ws, 'A', 1) def test_1st(self): eq_(self.cell.TYPE_NULL, self.cell.data_type) def test_null(self): self.cell.value = None eq_(self.cell.TYPE_NULL, self.cell.data_type) def test_numeric(self): def check_numeric(value): self.cell.value = value eq_(self.cell.TYPE_NUMERIC, self.cell.data_type) values = (42, '4.2', '-42.000', '0', 0, 0.0001, '0.9999', '99E-02', 1e1, '4', '-1E3', 4, decimal.Decimal('3.14')) for value in values: yield check_numeric, value def test_string(self): self.cell.value = 'hello' eq_(self.cell.TYPE_STRING, self.cell.data_type) def test_single_dot(self): self.cell.value = '.' eq_(self.cell.TYPE_STRING, self.cell.data_type) def test_formula(self): self.cell.value = '=42' eq_(self.cell.TYPE_FORMULA, self.cell.data_type) self.cell.value = '=if(A1<4;-1;1)' eq_(self.cell.TYPE_FORMULA, self.cell.data_type) def test_boolean(self): self.cell.value = True eq_(self.cell.TYPE_BOOL, self.cell.data_type) self.cell.value = False eq_(self.cell.TYPE_BOOL, self.cell.data_type) def test_leading_zero(self): self.cell.value = '0800' eq_(self.cell.TYPE_STRING, self.cell.data_type) def test_error_codes(self): def check_error(cell): eq_(cell.TYPE_ERROR, cell.data_type) for error_string in self.cell.ERROR_CODES.keys(): self.cell.value = error_string yield check_error, self.cell def test_data_type_check(): ws = build_dummy_worksheet() cell = Cell(ws, 'A', 1) cell.bind_value(None) eq_(Cell.TYPE_NULL, cell._data_type) cell.bind_value('.0e000') eq_(Cell.TYPE_NUMERIC, cell._data_type) cell.bind_value('-0.e-0') eq_(Cell.TYPE_NUMERIC, cell._data_type) cell.bind_value('1E') eq_(Cell.TYPE_STRING, cell._data_type) @raises(DataTypeException) def test_set_bad_type(): ws = build_dummy_worksheet() cell = Cell(ws, 'A', 1) cell.set_value_explicit(1, 'q') def test_time(): def check_time(raw_value, coerced_value): cell.value = raw_value eq_(cell.value, coerced_value) eq_(cell.TYPE_NUMERIC, cell.data_type) wb = Workbook() ws = Worksheet(wb) cell = Cell(ws, 'A', 1) values = (('03:40:16', time(3, 40, 16)), ('03:40', time(3, 40)),) for raw_value, coerced_value in values: yield check_time, raw_value, coerced_value def test_timedelta(): wb = Workbook() ws = Worksheet(wb) cell = Cell(ws, 'A', 1) cell.value = timedelta(days=1, hours=3) eq_(cell.value, 1.125) eq_(cell.TYPE_NUMERIC, cell.data_type) def test_date_format_on_non_date(): wb = Workbook() ws = Worksheet(wb) cell = Cell(ws, 'A', 1) cell.value = datetime.now() cell.value = 'testme' eq_('testme', cell.value) def test_set_get_date(): today = datetime(2010, 1, 18, 14, 15, 20, 1600) wb = Workbook() ws = Worksheet(wb) cell = Cell(ws, 'A', 1) cell.value = today eq_(today, cell.value) def test_repr(): wb = Workbook() ws = Worksheet(wb) cell = Cell(ws, 'A', 1) eq_(repr(cell), '', 'Got bad repr: %s' % repr(cell)) def test_is_date(): wb = Workbook() ws = Worksheet(wb) cell = Cell(ws, 'A', 1) cell.value = datetime.now() eq_(cell.is_date(), True) cell.value = 'testme' eq_('testme', cell.value) eq_(cell.is_date(), False) def test_is_not_date_color_format(): wb = Workbook() ws = Worksheet(wb) cell = Cell(ws, 'A', 1) cell.value = -13.5 cell.style.number_format.format_code = '0.00_);[Red]\(0.00\)' eq_(cell.is_date(), False) openpyxl-1.7.0+ds1/openpyxl/tests/test_chart.py000066400000000000000000000523601224514475200216260ustar00rootroot00000000000000# file openpyxl/tests/test_chart.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file from nose.tools import eq_, assert_raises, assert_true from openpyxl.tests.helper import get_xml from openpyxl.shared.xmltools import Element from openpyxl.writer.charts import ChartWriter from openpyxl.workbook import Workbook from openpyxl.chart import Chart, BarChart, ScatterChart, Serie, Reference from openpyxl.style import Color from re import sub from openpyxl.drawing import Image def test_safe_string(): from openpyxl.writer.charts import safe_string v = safe_string('s') eq_(v, 's') v = safe_string(2.0/3) eq_(v, '0.666666666666667') v = safe_string(1) eq_(v, '1') v = safe_string(None) eq_(v, 'None') class TestReference(object): def setup(self): wb = Workbook() ws = wb.get_active_sheet() ws.title = 'reference' for i in range(10): ws.cell(row=i, column=0).value = i self.sheet = ws self.cell = Reference(self.sheet, (0, 0)) self.range = Reference(self.sheet, (0, 0), (9, 0)) def test_single_cell_ctor(self): eq_(self.cell.pos1, (0, 0)) eq_(self.cell.pos2, None) def test_range_ctor(self): eq_(self.range.pos1, (0, 0)) eq_(self.range.pos2, (9, 0)) def test_get_type(self): eq_(self.cell.get_type(), 'num') def test_caching_cell(self): eq_(self.cell._get_cache(), [0]) def test_caching_range(self): eq_(self.range._get_cache(), [0, 1, 2, 3, 4, 5, 6, 7, 8 , 9]) def test_ref_cell(self): eq_(str(self.cell), "'reference'!$A$1") eq_(self.cell._get_ref(), "'reference'!$A$1") def test_ref_range(self): eq_(str(self.range), "'reference'!$A$1:$A$10") eq_(self.range._get_ref(), "'reference'!$A$1:$A$10") class TestErrorBar(object): def setup(self): wb = Workbook() ws = wb.get_active_sheet() for i in range(10): ws.cell(row=i, column=0).value = i self.range = Reference(ws, (0, 0), (9, 0)) def test_ctor(self): from openpyxl.chart import ErrorBar assert_raises(TypeError, ErrorBar, None, range(10)) class TestSerie(object): def setup(self): wb = Workbook() ws = wb.get_active_sheet() for i in range(10): ws.cell(row=i, column=0).value = i self.cell = Reference(ws, (0, 0)) self.range = Reference(ws, (0, 0), (9, 0)) def test_ctor(self): series = Serie(self.cell) eq_(series.values, [0]) eq_(series.color, None) eq_(series.error_bar, None) eq_(series.xvalues, None) eq_(series.labels, None) eq_(series.legend, None) def test_color(self): series = Serie(self.cell) eq_(series.color, None) series.color = "blue" eq_(series.color, "blue") assert_raises(ValueError, setattr, series, 'color', None) def test_min_max(self): series = Serie(self.cell) eq_(series.get_min_max(), (0, 0)) def test_len(self): series = Serie(self.cell) eq_(len(series), 1) def test_error_bar(self): series = Serie(self.cell) from openpyxl.chart import ErrorBar series.error_bar = ErrorBar(None, self.cell) eq_(series.get_min_max(), (0, 0)) class TestChart(object): def setup(self): wb = Workbook() ws = wb.get_active_sheet() for i in range(10): ws.cell(row=i, column=0).value = 1 self.range = Reference(ws, (0, 0), (0, 9)) def test_ctor(self): from openpyxl.chart import Axis, Legend from openpyxl.drawing import Drawing c = Chart(None, None) eq_(c.type, None) eq_(c.grouping, None) assert_true(isinstance(c.x_axis, Axis)) assert_true(isinstance(c.y_axis, Axis)) assert_true(isinstance(c.legend, Legend)) eq_(c.show_legend, True) eq_(c.lang, 'fr-FR') eq_(c.title, '') eq_(c.print_margins, {'b':.75, 'l':.7, 'r':.7, 't':.75, 'header':0.3, 'footer':.3} ) assert_true(isinstance(c.drawing, Drawing)) eq_(c.width, .6) eq_(c.height, .6) eq_(c.margin_top, c._get_max_margin_top()) eq_(c.margin_left, 0) eq_(c._shapes, []) def test_mymax(self): c = Chart(None, None) eq_(c.mymax(range(10)), 9) from string import ascii_letters eq_(c.mymax(list(ascii_letters)), "z") # eq_(c.mymax(range(-10, 1)), 0) # eq_(c.mymax([""]*10), None) def test_get_x_unit(self): c = Chart(None, None) c._series.append(self.range) eq_(c.get_x_units(), 10) def test_get_y_unit(self): c = Chart(None, None) c._series.append(self.range) c.y_axis.max = 10 eq_(c.get_y_units(), 109728.0) def test_get_y_char(self): c = Chart(None, None) c._series.append(self.range) eq_(c.get_y_chars(), 1) def test_compute_min_max(self): c = Chart(None, None) c._series.append(self.range) c._compute_min_max() eq_(c.y_axis.max, 2.0) eq_(c.y_axis.unit, 1.0) def test_computer_xmin_xmax(self): c = Chart(None, None) s = Serie(self.range, xvalues=self.range) c._series.append(s) c._compute_xmin_xmax() eq_(c.x_axis.max, 2.0) eq_(c.x_axis.unit, 1.0) def test_get_margin_top(self): c = Chart(None, None) eq_(c._get_margin_top(), 0.21250000000000005) def test_get_margin_left(self): c = Chart(None, None) c._series.append(self.range) eq_(c._get_margin_left(), 1.2857142857142858) def test_get_max_margin_top(self): c = Chart(None, None) eq_(c._get_max_margin_top(), 0.21250000000000005) def test_get_min_margin_left(self): c = Chart(None, None) c._series.append(self.range) eq_(c._get_min_margin_left(), 1.2857142857142858) class TestChartWriter(object): def setup(self): wb = Workbook() ws = wb.get_active_sheet() ws.title = 'data' for i in range(10): ws.cell(row=i, column=0).value = i self.chart = BarChart() self.chart.title = 'TITLE' self.chart.add_serie(Serie(Reference(ws, (0, 0), (10, 0)))) self.chart._series[-1].color = Color.GREEN self.cw = ChartWriter(self.chart) self.root = Element('test') def test_write_title(self): self.cw._write_title(self.root) eq_(get_xml(self.root), 'TITLE') def test_write_xaxis(self): self.cw._write_axis(self.root, self.chart.x_axis, 'c:catAx') eq_(get_xml(self.root), '') def test_write_yaxis(self): self.cw._write_axis(self.root, self.chart.y_axis, 'c:valAx') eq_(get_xml(self.root), '') def test_write_series(self): self.cw._write_series(self.root) eq_(get_xml(self.root), '\'data\'!$A$1:$A$11General0123456789None') def test_write_legend(self): self.cw._write_legend(self.root) eq_(get_xml(self.root), '') def test_no_write_legend(self): wb = Workbook() ws = wb.get_active_sheet() ws.title = 'data' for i in range(10): ws.cell(row=i, column=0).value = i ws.cell(row=i, column=1).value = i scatterchart = ScatterChart() scatterchart.add_serie(Serie(Reference(ws, (0, 0), (10, 0)), xvalues=Reference(ws, (0, 1), (10, 1)))) cw = ChartWriter(scatterchart) root = Element('test') scatterchart.show_legend = False cw._write_legend(root) eq_(get_xml(root), '') def test_write_print_settings(self): self.cw._write_print_settings(self.root) eq_(get_xml(self.root), '') def test_write_chart(self): self.cw._write_chart(self.root) # Truncate floats because results differ with Python >= 3.2 and <= 3.1 test_xml = sub('([0-9][.][0-9]{4})[0-9]*', '\\1', get_xml(self.root)) eq_(test_xml, 'TITLE\'data\'!$A$1:$A$11General0123456789None') class TestScatterChartWriter(object): def setup(self): wb = Workbook() ws = wb.get_active_sheet() ws.title = 'data' for i in range(10): ws.cell(row=i, column=0).value = i ws.cell(row=i, column=1).value = i self.scatterchart = ScatterChart() self.scatterchart.add_serie(Serie(Reference(ws, (0, 0), (10, 0)), xvalues=Reference(ws, (0, 1), (10, 1)))) self.cw = ChartWriter(self.scatterchart) self.root = Element('test') def test_write_xaxis(self): self.scatterchart.x_axis.title = 'test x axis title' self.cw._write_axis(self.root, self.scatterchart.x_axis, 'c:valAx') eq_(get_xml(self.root), 'test x axis title') def test_write_yaxis(self): self.scatterchart.y_axis.title = 'test y axis title' self.cw._write_axis(self.root, self.scatterchart.y_axis, 'c:valAx') eq_(get_xml(self.root), 'test y axis title') def test_write_series(self): self.cw._write_series(self.root) eq_(get_xml(self.root), '\'data\'!$B$1:$B$11General0123456789None\'data\'!$A$1:$A$11General0123456789None') def test_write_legend(self): self.cw._write_legend(self.root) eq_(get_xml(self.root), '') def test_write_print_settings(self): self.cw._write_print_settings(self.root) eq_(get_xml(self.root), '') def test_write_chart(self): self.cw._write_chart(self.root) # Truncate floats because results differ with Python >= 3.2 and <= 3.1 test_xml = sub('([0-9][.][0-9]{4})[0-9]*', '\\1', get_xml(self.root)) eq_(test_xml, '\'data\'!$B$1:$B$11General0123456789None\'data\'!$A$1:$A$11General0123456789None') class TestAnchoring(object): def _get_dummy_class(self): class DummyImg(object): def __init__(self): self.size = (200, 200) class DummyImage(Image): def _import_image(self, img): return DummyImg() return DummyImage def test_cell_anchor(self): wb = Workbook() ws = wb.get_active_sheet() eq_(ws.cell('A1').anchor, (0, 0)) eq_(ws.cell('D32').anchor, (210, 620)) def test_image_anchor(self): DummyImage = self._get_dummy_class() wb = Workbook() ws = wb.get_active_sheet() cell = ws.cell('D32') img = DummyImage(None) img.anchor(cell) eq_((img.drawing.top, img.drawing.left), (620, 210)) def test_image_end(self): DummyImage = self._get_dummy_class() wb = Workbook() ws = wb.get_active_sheet() cell = ws.cell('A1') img = DummyImage(None) img.drawing.width, img.drawing.height = (50, 50) end = img.anchor(cell) eq_(end[1], ('A', 3)) openpyxl-1.7.0+ds1/openpyxl/tests/test_data/000077500000000000000000000000001224514475200210565ustar00rootroot00000000000000openpyxl-1.7.0+ds1/openpyxl/tests/test_data/genuine/000077500000000000000000000000001224514475200225105ustar00rootroot00000000000000openpyxl-1.7.0+ds1/openpyxl/tests/test_data/genuine/NameWithValueBug.xlsx000066400000000000000000000175251224514475200266110ustar00rootroot00000000000000PK!ٌg[Content_Types].xml (AN0EH!%n@5(0ؖ-$4(D*&Qdތ3_+ v6gl)m9{߼,(Y -7&Tm1ges%T3IB%"}-Bl26`ZA!|Hj>d9-E`/W5bw B@(,beV؛<@ =ZQe>0a&.>]JM'}K@[ӬGz5tiޛ1"o^~F?Ġ'R*ifWqvA8ߙgx?[|PK!U0#L _rels/.rels (N0 HCnHLH!T$$@Jc?[iTb/Nú(A3b{jxVb"giaWl_xb#b4O r0Qahѓeܔ=P-<4Mox/}bN@;vCf ۨBI"c&\O8q"KH<ߊs@.h<⧄MdaT_PK!=f?xl/_rels/workbook.xml.rels (j0{-9r-ymȒЪ?~: bzYvck#TE q#0JҵzF$4WW2%&)ŠsRIq) ^|w4gl n luFWt̅9Pz@ĀdVVFHX_Xwɬȅ^M3!wi\f ayvWg#}+w7^(7sJs/k}Y,0}iAG< 4$*Tw뛚y,^5O#:81 V-pS);4x{I 1$}f2^t2%QD5LSNQPK!%Sxl/theme/theme1.xmlYOo6w tom'uرMniXS@I}úa0l+t&[HJKՇD"|#uڃC"$q۫]z>8h{wK cxLޜH]ś*$A>J%aACMʈJ&M;4Be tY>c~4$ &^ L1bma]ut(gZ[Wvr2u{`M,EF,2nQ%[NJeD >֗f}{7vtd%|JYw2Oڡ~J=L8-o|(<4 ժX}.@'d}.Fbo\C\ҼMT0 zSώt--g.—~?~xY'y92h!ы/ɋ>%mGEFD[t3q%'#qSgv 9feqwW@(^wdbh a8g.J pC*Xx8rbV`|XƻcǵYU3 Jݐ8b3+(QuK>QELKM2#'vi~ vlwu8+zHHJ:) ~L\E\O*t@G1lm~C*uG.R(:-ys^Di7QR8,b?SQ*q7C;+}ݧ;4pDZ K(NhwŘQ6㶷 [SYJ(p»g>X_xwu{\>k]Xy}钣M26PsFnJ'K,}䇦$Ǵ;@` >*8i"LI%\ xӕ=6u= r2f 3c (:jZ3sLs*UܚЅ ]M8kp6x"]$C<&>'eb. vJ|yXɾ8Ȯ]7R /=,.&'Qk5q&p(Kaݐ Sd›L17 jpSaS! 35'+ZzQ H )7 5)kdB|UtvaDξp|Fl&0_*3n'LE/pm&]8fIrS4d 7y` nίI R3U~cnrF:_*P}-p Tpl rۜ4LZéO !PLB]$K *++65vꦚeNƟf(MN1ߜ6&3(adE,Uz<{EUϲV)9Z[4^kd5!J?Q3qBoC~M m<.vpIYӦZY_p=al-Y}Nc͙ŋ4vjavl'S&A8|*~x1%M0g%<ҭPK!6xl/worksheets/sheet2.xmlQMK0 0wTY%",mm6VӖ|7Eo>^0DKUdocProps/core.xml (QO0MK߷v65< 1i/иvM[{ >x97{UF`t҄4E3-XYiW7Wmez . $(79yo(Ǝ@1⦲P-6-!wXgy`lz":"˖-@p %(4I*V8v:޽w7u6FȟKj,us+-V t%s~ m" !h THdk2dBon?7:'ql@< _|PK!!( docProps/app.xml (SMo0 tOdP!òMڞ5ڒ F_?ɮ_@E>=>>S|_WI+32$-M.#73x:ѐ<9' $Ph.(Zi(P)QSJ…5h4Fas'472}RR`tƛ˽quNaSFGe 慨<0zH+ѴPs%^ dN AV.G%zFOv;)w ~Z|ŗ[D [Z8|ŔcS:i%M`|ɌOjxn:pKS[[>atOݚ 0qmJ k6 vUdYƿ34 _CÃPK-!ٌg[Content_Types].xmlPK-!U0#L s_rels/.relsPK-!=f?_xl/_rels/workbook.xml.relsPK-!;xl/workbook.xmlPK-!Įd xl/styles.xmlPK-!%Sc xl/theme/theme1.xmlPK-!6xl/worksheets/sheet2.xmlPK-!ɮxl/worksheets/sheet1.xmlPK-!OFVxl/calcChain.xmlPK-!ЙA>UdocProps/core.xmlPK-!!( docProps/app.xmlPK }openpyxl-1.7.0+ds1/openpyxl/tests/test_data/genuine/empty-no-string.xlsx000066400000000000000000000172421224514475200265120ustar00rootroot00000000000000PK!XVƏ`[Content_Types].xml (̔MN0H!%nJ?Kؓƪc[gJD5f|MقM KJ]u޲Jg`@_^䫃Lb(+f΃҅ZD k܈5drålLc ZA!>|Hj=+l &7ZHΪo+K-A9+kŮ!ƃm>PXdJ51yz 3ʶ}avL]ؼ9k* s-MoGN TIBpbM ގyu5NC zr\o/xvB8ޝgx_PK!U0#L _rels/.rels (N0 HCnHLH!T$$@Jc?[iTb/Nú(A3b{jxVb"giaWl_xb#b4O r0Qahѓeܔ=P-<4Mox/}bN@;vCf ۨBI"c&\O8q"KH<ߊs@.h<⧄MdaT_PK!DGxl/_rels/workbook.xml.rels (j0{Ӗ"R !$} n\E3^}PY Y"[ % #1z#g],khE˖Édva0ehЛd8h{wK cxLޜH]ś*$A>J%aACMʈJ&M;4Be tY>c~4$ &^ L1bma]ut(gZ[Wvr2u{`M,EF,2nQ%[NJeD >֗f}{7vtd%|JYw2Oڡ~J=L8-o|(<4 ժX}.@'d}.Fbo\C\ҼMT0 zSώt--g.—~?~xY'y92h!ы/ɋ>%mGEFD[t3q%'#qSgv 9feqwW@(^wdbh a8g.J pC*Xx8rbV`|XƻcǵYU3 Jݐ8b3+(QuK>QELKM2#'vi~ vlwu8+zHHJ:) ~L\E\O*t@G1lm~C*uG.R(:-ys^Di7QR8,b?SQ*q7C;+}ݧ;4pDZ K(NhwŘQ6㶷 [SYJ(p»g>X_xwu{\>k]Xy}钣M26PsFnJ'K,}䇦$Ǵ;@` >*8i"LI%\ xӕ=6u= r2f 3c (:jZ3sLs*UܚЅ ]M8kp6x"]$C<&>'eb. vJ|yXɾ8Ȯ]7R /=,.&'Qk5q&p(Kaݐ Sd›L17 jpSaS! 35'+ZzQ H )7 5)kdB|UtvaDξp|Fl&0_*3n'LE/pm&]8fIrS4d 7y` nίI R3U~cnrF:_*P}-p Tpl rۜ4LZéO !PLB]$K *++65vꦚeNƟf(MN1ߜ6&3(adE,Uz<{EUϲV)9Z[4^kd5!J?Q3qBoC~M m<.vpIYӦZY_p=al-Y}Nc͙ŋ4vjavl'S&A8|*~x1%M0g%<ҭPK!Įd xl/styles.xmlSj0BF mCa!@Ыl^hdgd;SXOO3ofAlL.`)),68J]DMR$%Jy$*:݇"!zMJ}ڤA6CYm@J XHI3[  g#9WGZ3hC̤%%qX &D67bݘeOU VE__}lKl4W =@<tPCNc7VL)yֹ /YodkYeŹg+7"ϻJzp@o볦9_Z ]Yp'mtXKy/ָX^)$Q ~Γ>LSNQPK!JK-xl/worksheets/sheet3.xmlPJ0 CMWY",m'm$YW޴eyyo^jĘ E }K} o۫{%5|cusyQ)Ӏ";TVRv@RA}f4E8"nZrV^txV?iqC$UO 3Ȧ0xLf1G⩫v;xC_8W{ғ C4KXd9 w%|oADVU<Eq="c =26=:3Ч4t2U;y~+2fUimPwy hcoUњ Ȫ~>,rAΐح|F@¥ay-t*Iv4>v 7ps6s8!Ux1d5cu, '` ѕ_^E=F_д21I>fmYS%p ֫hCʦې0>>T0%pY;y3Pp]8L $\#V-AE ͒xdx'r,^HGioi"]jl".(w_U ͡Iո*y\'E٪5p/ qOv0ٷkyl9qi PK-!XVƏ`[Content_Types].xmlPK-!U0#L k_rels/.relsPK-!DGWxl/_rels/workbook.xml.relsPK-!kL^pxl/workbook.xmlPK-!%S xl/theme/theme1.xmlPK-!Įd xl/styles.xmlPK-!JK-xl/worksheets/sheet3.xmlPK-!JK-xl/worksheets/sheet2.xmlPK-!_8%xl/worksheets/sheet1.xmlPK-!n[ ?]ydocProps/core.xmlPK-!-RdocProps/app.xmlPK openpyxl-1.7.0+ds1/openpyxl/tests/test_data/genuine/empty-with-styles.xlsx000066400000000000000000000165271224514475200270730ustar00rootroot00000000000000PK!zғb[Content_Types].xml (TN0#(q!ԴG|7UǶ=VJ${24.[CB|)Hdu0/Jߊ Iy\P-M//&oRDNJ54 *FJ/ht#urj1t5Ug08Is\0*Fg"V.aCUY &U\Evբ d* W;Qaz8]g >~PK!U0#L _rels/.rels (N0 HCnHLH!T$$@Jc?[iTb/Nú(A3b{jxVb"giaWl_xb#b4O r0Qahѓeܔ=P-<4Mox/}bN@;vCf ۨBI"c&\O8q"KH<ߊs@.h<⧄MdaT_PK!>xl/_rels/workbook.xml.rels (J0nӮ""Ej}Lm2㟾ۅef2k&WP%&w ޚ[= &$W4KH"xR㣔dNҨ9ɨAw(7ey/O ނhm| }Dg"$4FY.2#59鳔Y]bd @%s"ݚ0tB)[ȓPK!x˒Q'xl/workbook.xmln0EKJ(B$HU[MT k7 ǎlC;1Jiw]:{X[Nh4dC]! \ .`Y-zc_+Gl5Ujc[)up oU4jp%eϦ:bQqOFvE-nwgLq_( (4=IctjG2zk+OSQ>4l%BvI-L_@ٗ1JXJ;)|Clv˽7Yf/| 'A`3-r+Dw;t+8VqUicPK!vxpxl/sharedStrings.xml4A 0Ew.D$MRq@3 0&*%(. yswS$ ] W0G18.⌔GLI4hGD ޕ^ T.x56V]kcXiPK!Sbus xl/styles.xmlTj0Bk6ΡHB![Ud`$iَ=%b3ofߟ A;[jǙڶ}Y@agU}KprT AP#bw$>*#•딥?F m}+!CL2dmbg1('IgHDdXaSa0ɝEh3:I,9/4B Q@Tif0tTR7&1֋!n6 X+%u{U$^Ǹ[9DgȐZ 3y͘ :N^4 >ȂӬc^M:lNx&o& 5,X-Lt ϽߏRq z~4L!:Q…x [|@k@m Ly^UFQcTHՈ,j?){cOGcz;^)PK!%Sxl/theme/theme1.xmlYOo6w tom'uرMniXS@I}úa0l+t&[HJKՇD"|#uڃC"$q۫]z>8h{wK cxLޜH]ś*$A>J%aACMʈJ&M;4Be tY>c~4$ &^ L1bma]ut(gZ[Wvr2u{`M,EF,2nQ%[NJeD >֗f}{7vtd%|JYw2Oڡ~J=L8-o|(<4 ժX}.@'d}.Fbo\C\ҼMT0 zSώt--g.—~?~xY'y92h!ы/ɋ>%mGEFD[t3q%'#qSgv 9feqwW@(^wdbh a8g.J pC*Xx8rbV`|XƻcǵYU3 Jݐ8b3+(QuK>QELKM2#'vi~ vlwu8+zHHJ:) ~L\E\O*t@G1lm~C*uG.R(:-ys^Di7QR8,b?SQ*q7C;+}ݧ;4pDZ K(NhwŘQ6㶷 [SYJ(p»g>X_xwu{\>k]Xy}钣M26PsFnJ'K,}䇦$Ǵ;@` >*8i"LI%\ xӕ=6u= r2f 3c (:jZ3sLs*UܚЅ ]M8kp6x"]$C<&>'eb. vJ|yXɾ8Ȯ]7R /=,.&'Qk5q&p(Kaݐ Sd›L17 jpSaS! 35'+ZzQ H )7 5)kdB|UtvaDξp|Fl&0_*3n'LE/pm&]8fIrS4d 7y` nίI R3U~cnrF:_*P}-p Tpl rۜ4LZéO !PLB]$K *++65vꦚeNƟf(MN1ߜ6&3(adE,Uz<{EUϲV)9Z[4^kd5!J?Q3qBoC~M m<.vpIYӦZY_p=al-Y}Nc͙ŋ4vjavl'S&A8|*~x1%M0g%<ҭPK!7xl/worksheets/sheet1.xmlSn0?GˉCR`45C3M,"k;.p[*@ܙQ#8ixSfo_7o8(L'5_CU}Ȉ:Ͻ`>:s?9]:_]> exbX}$yit47B}mz1Jt6U-3\HҞr[CbYt(S,eYb |D14@,N?zeT2lw|Gضm1_QS]q@U!8X0*1 HC2ꖓu/kBh:֏B_9I*|N.V PK!'8ĈdocProps/app.xml (n0 `@VQzذI&ӱPYDHmqɟ=v!E) 6.*zEH&8[ ,"`%Zm ˁ& c8 ѾvH#AIobr\u>OK;k_;#Ɔ\TLt!e3l䆩8y>u`0pw4[90N8os7>oz罉]2[ͅ|Jxo.]k2Լ~)Gdɦ5oaXr(;?הePK-!zғb[Content_Types].xmlPK-!U0#L o_rels/.relsPK-!>[xl/_rels/workbook.xml.relsPK-!x˒Q'xl/workbook.xmlPK-!vxp xl/sharedStrings.xmlPK-!Sbus xl/styles.xmlPK-!%S xl/theme/theme1.xmlPK-!7xl/worksheets/sheet1.xmlPK-! ]1*docProps/core.xmlPK-!'8ĈdocProps/app.xmlPK openpyxl-1.7.0+ds1/openpyxl/tests/test_data/genuine/empty.xlsx000066400000000000000000000253751224514475200246020ustar00rootroot00000000000000PK!̂[Content_Types].xml (U_O07;,}5[1꣒7еM |{oh8%[s?m x,I00˔},^aF m lȦb#6<9Ju`$^;!Wb |4siMPaBA4>xrC.KE}UE2 )~Q6 .(-)P#H0i =xjvGA(BGOs%uyYvջt0gt{.t$TYGy.%T9;jtzjJI ; H z9жk MK-s1::) =7-0Yǟh.ji=p\PK!P|NL _rels/.rels (J1Ͷ4ۋ?n&$Ӻ}{|͏lw8Ա7.JP-7ϫPI;ؓ3%U7WPrSjTd Z'҈@>G|h{lHo^ fLwޭA!OuYzb{=d iw ]6׻IСibN)Jsql_u$tw|phe% H@ PK!8$xl/_rels/workbook.xml.rels (j0 }qvuzu`%M`{3ٖP !vյmb%)TBoXU[v[%+,\@;nݣJ;\y4O 5K (N4ɾd` a}uU5xP3-6G+F`JY:VV'zf}au6&/TƐgbdnhC`bMT[(yqLDbڍbJKaГߩPK!IKxl/workbook.xmlSMo0 to E,X(=+2 Շ!ɵG)m!; Gͨ$y%f)%>~{yk&7O`g +i}N[PL#yt1qV+iz(&4=eXaFp+ĂdwFHx<1"¾GId t\ؾ eIA{(܃CI*Q΀I %8w/KbIԾ ;cqy~Py(LYbhDfaRF] <]DA:&|#n{q|_\ p<*&EaH3qD>K<8_hߤ -)q3nTrʏ'0rj\UnCQ"qhX; jTPK!xl/worksheets/sheet4.xmlT]k0}?='N&v))eeke:Շ'N_+9RtϹ~(9(IN]dJ hnjwbILL }Goʏ/+h}%-(&'y]: NJ&tz(&42 4ÝG yߵs'4} nT¿F3P(B n3>b<Dae$!Z6n=`Ԑ}e+)pS4f>ǔETfp+)ZP1b),[ς=> ػ?ijj ,3JPЎSjY\b(]^ ) <#er,(uVgQ@54k=ZA7Kc&!d9OYzIIߋL 7D,x<+sk,XY6r` ւj9]@Q{|>//rr$d]D4Fah>`9 ;Р1Iƛ.GqhNrc?m[}G:ց݊7$+p`C--0N&:QEZƌhuU8'C|7He|15J~հF[TG0OZ PK!3{33xl/worksheets/sheet2.xml]o6@?zKlEnER,h[$j~lj"PK^~\;UsٕdorQa*:ŚU;w_Y 9Wnj$#Y7-o f ^!ZYaUB.YĖaO5oH^1wDz.:\멽EbWVnWLOɆae.E'Jjg}]QglW$ս)y8Rg۵]UK͔=(ͨXud.J^H&~$ kߺ  _> +k|_xUmg #\n3aXD,v {v+\Vs opTd{3=2Ŷk)"i^\>~KIiC铮X:K]88!ue9F r>B҅r1\bԕ= #$Lo #ʰ,4A^w)n;=҅r9\rԕ/,ş凛Zﻉ2}F}ʗ>`ǹ0J~yo#!]!bSiB1#QM}u+!\3H?YPX[ЎX3kb8ԃ(3ή# BsXjKqՎ8,ϋclŖDj$ ï"nXsY`D> 8Ws4f6) x@Ck)spH|=XB|oAXF5\MWl9_܌?+]1za5fXk]3XkSނLȷNh߫,/pQ1.t۠਻6ڇ1i=??e;xdv'#jT:~ܛ6FxoeՠΦK'Y 8xu} 8rgu~<]dnɣxy5l8y,9OF;O?PK!ɸxl/sharedStrings.xmlljADή̈v[w`g ;.E*ns$vخϧ'h>~fEj:~#@9Ȫ5)v' DY7͇!2BWfVm_U@UG)p|@kzk MPK!AX xl/styles.xmlTMk0nZb;C lJ= a$9;,Ћ=37z4(ycVMvjtU_Ч}j :w7u#XBق>%m (i#C zI}L$㊖yѣr}?"L &eh qXbbW&xmOb9Or&x?3X26;.e>0 2i@U^R,.dMiaHe^kPn$œctvNK4Zz@39!fi@G?8?5JomAqݜLld6c`3 J;aS)^ςJ`ƅ}]7]=r.uGBl sYzv&bTa-<+eS4񪢎9Etpp\-2 xУifY' AŽX`Ȥ=cޱ~nS / ߡ壼 CPK!%Sxl/theme/theme1.xmlYOo6w tom'uرMniXS@I}úa0l+t&[HJKՇD"|#uڃC"$q۫]z>8h{wK cxLޜH]ś*$A>J%aACMʈJ&M;4Be tY>c~4$ &^ L1bma]ut(gZ[Wvr2u{`M,EF,2nQ%[NJeD >֗f}{7vtd%|JYw2Oڡ~J=L8-o|(<4 ժX}.@'d}.Fbo\C\ҼMT0 zSώt--g.—~?~xY'y92h!ы/ɋ>%mGEFD[t3q%'#qSgv 9feqwW@(^wdbh a8g.J pC*Xx8rbV`|XƻcǵYU3 Jݐ8b3+(QuK>QELKM2#'vi~ vlwu8+zHHJ:) ~L\E\O*t@G1lm~C*uG.R(:-ys^Di7QR8,b?SQ*q7C;+}ݧ;4pDZ K(NhwŘQ6㶷 [SYJ(p»g>X_xwu{\>k]Xy}钣M26PsFnJ'K,}䇦$Ǵ;@` >*8i"LI%\ xӕ=6u= r2f 3c (:jZ3sLs*UܚЅ ]M8kp6x"]$C<&>'eb. vJ|yXɾ8Ȯ]7R /=,.&'Qk5q&p(Kaݐ Sd›L17 jpSaS! 35'+ZzQ H )7 5)kdB|UtvaDξp|Fl&0_*3n'LE/pm&]8fIrS4d 7y` nίI R3U~cnrF:_*P}-p Tpl rۜ4LZéO !PLB]$K *++65vꦚeNƟf(MN1ߜ6&3(adE,Uz<{EUϲV)9Z[4^kd5!J?Q3qBoC~M m<.vpIYӦZY_p=al-Y}Nc͙ŋ4vjavl'S&A8|*~x1%M0g%<ҭPK!=2?xl/worksheets/sheet1.xmlSn0}_iJ lUJ6KlچIh}s\|&>*IzNdSJ]N(qI!/uSv0vOA6޷i9ހbnnZxRG"Z`U)-2RLh:2#;ڏ$$XkDlN17ERH_D紳:=5S[LLO{%)QUR5 Lp 0Wb2:t"!1 8$eB8qބoRvb!)Oh:$EgPK!REdocProps/app.xml (n0 ,@V1 zXq{We:&KDɞ~$P(7Qݩ!2Ӿ0|;YD e!;@@Q 3^!6$Z9EJjt ėpu[d8!YsMȇ#7iusCR|mkBR>|%GC+d,Ĺgd|{,l, Ch~.9{Q:U0!v۶Zc-p I8{s6+dL]S YT==+\ic+pXG[_D[ڛ֪7T+R+d_ m|(ww~ŧ&],NTldS)wyvyxhr wPK-!̂[Content_Types].xmlPK-!P|NL _rels/.relsPK-!8$xl/_rels/workbook.xml.relsPK-!IKxl/workbook.xmlPK-! xl/worksheets/sheet4.xmlPK-!3{33 xl/worksheets/sheet2.xmlPK-!TuPEKxl/worksheets/sheet3.xmlPK-!ɸxl/sharedStrings.xmlPK-!AX xl/styles.xmlPK-!%Sxl/theme/theme1.xmlPK-!=2?xl/worksheets/sheet1.xmlPK-!i3!xl/calcChain.xmlPK-!Wf!docProps/core.xmlPK-!REi$docProps/app.xmlPKW'openpyxl-1.7.0+ds1/openpyxl/tests/test_data/genuine/empty_libre.xlsx000066400000000000000000000176531224514475200257570ustar00rootroot00000000000000PKrc? _rels/.relsJ1}{wDdЛH}a70u}{ZI~7CfGFoZ+{kW#VJ$cʪl n0\QX^:`dd{m]_dhVFw^F9W-(F/3ODSUNl/w{N([qTuɂy͙Jt:锼&}35滁*1_O} 7)W]Ш[O,_s}DtqClb e' A_\sWF"C~4?uyYB#h=7`a| 0v۱*}XZp/8$M.J. t95OsҬ#lly*?H!$NKҢJJ8ވhsG /p25:eªgոx+=s ~֩Gkx[ T!Kp[u\eWpv#W7^<&gŝ{єWkF pR6t"=-!mđN P 8xOsϏeE UN+ZPVc +Kbj0+D -z<,tM4iptf#+B%pv&_^ILJN*Rm "&u/9PKkdPKrc?xl/worksheets/sheet4.xmlUn8}߯^K-J`v-,"%);wHʲE69ù9}|Dmsr7'_W2K%BI#q)l&M^BE@ QS(]QWM2oTx$Ӹ\FLJ* Ýʛ N4j1}SڐGxQY1Lj دG:^x1c#8T" Ŝܦ7Խp0s+lNc1(h#_ω H]!)a6WBg}%+P9V[%^iQ ^AN`vr fU2ZL9uALh懤sU*yQT iҿ.{zT0h]$Dc672}Ԃ[?&"]` ۜ1߸VU<ݦre8B88׍u>sn-7\MW$ˆ~Qy ˦ë$Β&I|δ》GZiux#\|B ܺ*1I16 |Raչkb8͒,޻Wj|j0&u 2N5v]{J_d3,p)A;u4QRDVgE3;dp5iit&KO2$MdPHo{: ?JmmZq#La=,6ȗ$i{YcW@lVSn6`}H~F$/6ueNmzp]} t!Nߕ2b(oz]'^3<׋b,|pzg<Aܝ}?PKPKrc?xl/worksheets/sheet1.xmlUMo8xo$u"Y uP[7ZYD(RKRv_R7 =Ԁ 9yQF,X4Bm-_X<ׂ+`p{WhW2M]YC݅iA2vQIN.5.0U%K6e׀щ=wl[C[TRy@W-mߙ[Tbs!1U%P}~aJ7…%HNJG+x+ m,r[c1dj_b^H +ԭƨ'/f>bcwfPxY҃T%@WeɒS0MEW,K-Œi6S8Σ1ͺ g\KӅF+uHC1/An!_G!G]ttzr:ۧ1}xog<äe Kx1}k) d]K"b&nsq_ #=oo=Ң%Hn,ƪfz\,9jyЛ^fRdڱӎvO;>K}N>t-Ca8N>zKF}"FFwэƖO8I}hX'>pڧ}jiOi_:/->CjJm=z`\`AB 0! \b 1l>B@܂-C.D. ] >B@.C/D/ _ >B@/Eo?_>b C]_ ~/E~1 ~]_ ~/E~1 ~]_ ~/E~1 ~]_w`;p;;~}&+Jy_5ś(DWLdg eE[E#炽R↖^NTHqS^Ls[iR[/oj,7/KP՞U.~LRsZ(n^j]~)W9镍LRO~eN_m]ZVLZLi:R&u1cGEeOcN{M=PQb>J=ښZЃ>i*mavSIeV])ͻu>~?뼘,k`ڪؚvߙveſPKV&PKrc?xl/sharedStrings.xmlmA1E[QI""8=@.@RiOąyONMU@ܕ>r^~ C*LIVD+3 1k:pPH7P*#qmneA+Nw#DHs2ٙ"U@MG)F5OsMPKPKrc?xl/workbook.xmlMo0 ?iaݰfYX,"&E=z˛F8y29#m.?9g)r߬>, k]D54Ol N*][56:Jx5B~",0lU) k+ @RkzZVJ!&^4$VhɣիoBvC9@ #5g@Hl(a*JGGvއLP99[)w sSAZ]w*'5Trޅb") jWS:rOĚ=3)$j(uf6| m b[x^w%ú UW&mnݕik و^LL]@ٻ@k1d(_I4Iۥ~(/$'2|zx `q:~PKт FPKrc? xl/styles.xmlX[o0~߯,q5Pn7IZ*m`'ؑmZqBq&T"/q>srL,iÕQ #&=sxTT(x >|;.b"`#kB#0 J£I4qN ZMb%4 OMVBw߾ dqcծn%i{\pU;['Qէ|H>A/Pr9& 1CT|" = h<J#N `;^$2ҙTd=q!5p z i9J#ehR-֡zUrHowͳ\ JRq* x,AOaV%đXb,<\茹@x q:G} HgI"g2fz R<@CF+<. Eu/,n!jcztEJg0t dSii"X -e*K3`垣ѓ>"CO(t\BgLT %^NAL.qKļexJMs46y.&E(m2ZUhl[^ȹQ=)f{bξ #Z:(K(L\taw4XK,C54Ǜi.TӢ5Zki;Р_?U{-Us Tk&yYٍiީ2Bk2,`PKXqPKrc?docProps/app.xml1 0ཿBklR$R:wCrjIrM)Խ{]-#ű(Eh:jnEd5Z=B-6mhbJhF: +dy*˳-|+V Z2}mssR=(hPKÙ PKrc?docProps/core.xmlmj0E =vuB!)t+#b=L+M!=snة8c$]/:qc/(r{qESU>[#"*ZRGDT~h%GRX[RK˰ŏRUZNh1AS5Z׀w/~1Z f%/dVjjd.ח}ji2B1,DylU-ePz Ep1C[ơ5MB%BCHȮ^Y BcO.@7zNAȯIf{x~QpjH+fVeIAAΌog#18AB0wс3aۧq\VY⺁3 @1ÊL0#FI+e^PKa8=PKrc?f; _rels/.relsPKrc?o+xl/_rels/workbook.xml.relsPKrc?kdHxl/worksheets/sheet3.xmlPKrc?xl/worksheets/sheet4.xmlPKrc?(IT xl/worksheets/sheet1.xmlPKrc?V&] xl/worksheets/sheet2.xmlPKrc?xl/sharedStrings.xmlPKrc?т Fxl/workbook.xmlPKrc?Xq xl/styles.xmlPKrc?Ù docProps/app.xmlPKrc?}docProps/core.xmlPKrc?a8=ontent_Types].xmlPK openpyxl-1.7.0+ds1/openpyxl/tests/test_data/genuine/empty_no_dimensions.xlsx000066400000000000000000000240741224514475200275210ustar00rootroot00000000000000PK!̂o[Content_Types].xmlUT PPux ŕN0M|f+10@Tj{)o LpfוIVP;~c X锶}^`V ,lƣlR[Ye ypnÜ{!b|slX{VLE 32cdsFO"6_Y'2uE%Dz"IkR*P'KQԞpp,EG@(,be_;'(Fݮ_.,>[tNcV mp0.;2R6`g oێ?)qciLO$GmolZ/݅80rRf8۲KaPK=n3o3oPK |? docProps/UT 8UN8UNux PK!REdocProps/app.xmlUT PPux SM0#,[BrB]=Pٽ{Icؑ=Z~=Tͦ,p 癗7wƱ#dbqބCΟg / r~wrC -$F>FlB$SCӜҞ2UF:ƃUe 5Q,죀/ (e0\[Ss:k4RњR=jc=T/'N)ML-U&(F;.oNۘ<Cd.9{ zc9?hG~]vm¨Y%07Y3vV-QZ-,e߫竩x`3VD6oKb|ڸ>iK#kE{4ZomBjO#j6(PY@͈ojP^9o>_Z,}]cR>) PK!IfdocProps/core.xmlUT PPux }j0{KMʘl& ]H֤!>ϱ[[*]|9lQe J( @JH|ޣy++ 9ڂC&‹ X/I;MV+PE pQY|S%6%;3<í04Je'C w8b|b=X6tÞ8كu]Guڡ1=vR Lp-0_b 2|V+ q[<Ϯ Sg\Ra DLE;t4"!1 0$yB0qq#$1NpDsQPts_PK |?_rels/UT 8UN8UNux PK!P|NL _rels/.relsUT PPux J1Ͷ4Kz0&?&Qoo+1of(偃eU` 3X܁ʂā (æZ?фRr?Ĭ %dH:۞<#r(:юؑ^N?1H;?Dm;Xڲ}D_#10O-ͯ j[|ߊ V={F.W$[Sޭ&Yґ6Ia_p0{#$VY4T8sG1Ĉ( 9lY 5C\6:IcrGABi',!싅 5oRh8يaiPKd|?txl/worksheets/sheet2.xmlUT <]N<]Nux n8@ ?z\ f,.`miHJҎH  PbRIHx{jȥDaoK2뻛r: #g6l g/Qf{ϑ[N̑i0rnr#^A1RciFa 5+|ʁsBB;pA(asBIք0 gB: ށ/a8 tf>vF` no@89 C9ta a!sܫRΡsCw ps:sH< dJdHHd$P2%CB2$$C'/!!:0 }0Q;H89 y0fgS0 |ܓ/LVE5ߚtdXS֢-sJBk\z{Spq /NO^i}N k02ù1%HSmE+jќogᶰ/%0'תΔx?PKn|?t42xl/worksheets/sheet3.xmlUT O]NO]Nux k04Ӵ݂]FBYºϲ|EÓN,'Ya/e$_nF|Pr: ty7YpPJhg!x.9 2"ؐM$Sׂy#RIBATd>}JPԿZIX98B "tEUؐYHq4\LW4H%g+ȼ1:9idar5..l¾u$^|XdI]d}AcO%=D:imCE%Iw,ࡥ{tx~l`jR:~̏ctΜ< ~Ȯ8)!.!`O~c˳dɘ2xO{yEf= ݾ[Xc$wT:]?PKu|?͟xl/worksheets/sheet4.xmlUT ^]N^]Nux TQk0~?='Nڦ&N))eekeJ&~g })cIw;zo4keOFcΠvm;sPm 9?@˯_;_C eIdFuP&ydt=B?aRI1PcA C\88#kΤ5 " yl83Jzl]LfZ932֋BSL-On*йo|Mx5VI`޽u)sBOP)Qla۲ `55\c4PJ=^:tzKSĂFst3?o[omH;y:{bT1Z, ΂(!X[v5h#lgɪU b wioDxȲrzj!ic|Ix+fy#`]/+FM{WaJԼS6s:_E%;}? PK |? xl/_rels/UT 8UN8UNux PK!8xl/_rels/workbook.xml.relsUT PPux Խj0wײ4 !rB6}!-[2ᷯH@'q'phi)dI 05\1`Wm߰>7O޺ǃ_&v$^4 H`0N<˵V_hvUc LSM.sRPK!i3xl/calcChain.xmlUT PPux = !{;[PP;#Qo??|fT%FPľ̑ }w% yT,|I`rۍ5`d @e(+q?R3uѲVYQIsTpPK |? xl/theme/UT 8UN8UNux PK!%Sxl/theme/theme1.xmlUT PPux YMD#F;ͮ6٤vnZęӌ=d( 7J\ʯY("/#x3fE9$~CDH\Y>8h[ - If<&mkFue.M &n[RɦmKý1V=F̮jM;4P#`{k<>A5gc+nLD"Î&N#g:Ĭm?B K7V-Xe{ATF>]A03: Nݸ_z^,e`u-3穁UݚWsxctĻ+VnKxwVlw[+[gxN㹈2xkKeWN\=.Ȃ%d}uq4&ڝ|˗+[,$}Aն>N0Tُ/=A/=>~/?@x ǁN/Sד^<ʌ:>/@?<2D7f@liTXޜafuHyw4^I׃PL5Q 9pa4z*K7gfb1>4mo@&SnHJj16HLJ !K}%+tF P23).fpfbCH(L, +**5ӑ7 MJ̄_rT0z#"斘ԽþfQ)70:rO!4uGr)WF%xB5ǕC:[YߦAhNT]#֌nh2\6<ヒ}ksq/ǔ5cZG}b&OB,ĕp5\}BUx8@.$`UΎXQφ 6*F`]aKo&ɁkJs<4TiMӓӬ!c0#9yX=D2#R14t[^Ӥm4L:AŹsRm%Jj9BGW,ma(~2m@qUa+tjD$B,Ü*5u/{n1ЍӢrC-쓡%1UrYSEA8:BC6vQ ό|!B"ʕ_TW4Eu`I--9<^萭4 _Ӕ9⽻ ckc` 9ڶP!. 2YHUB,}J}+7 T4@BS dOvSןsFEY+wH LP8ɠ٦xq+&ǃ ,5}Qf*Q[7[\~&p@4n*|o|%D*o9[q)wZU>5g7*}wgwLZ8(Mۤp2>tPK!ɱxl/sharedStrings.xmlUT PPux mjADή̈v[w`g ;.E*ns$vخϧ'h>~fEj:~#@9Ȫ5)v' DY7͇!2BWfVm_U@UG)p|@kzk MPK!̂o[Content_Types].xmlUTPux PK |? AdocProps/UT8UNux PK!REdocProps/app.xmlUTPux PK!IfdocProps/core.xmlUTPux PK |?A_rels/UT8UNux PK!P|NL _rels/.relsUTPux PK |?Axl/UT8UNux PK w|?A4xl/worksheets/UTa]Nux PKN|?4RS)|xl/worksheets/sheet1.xmlUT]Nux PKd|?t xl/worksheets/sheet2.xmlUT<]Nux PKn|?t42 xl/worksheets/sheet3.xmlUTO]Nux PKu|?͟xl/worksheets/sheet4.xmlUT^]Nux PK!AX sxl/styles.xmlUTPux PK |? Axl/_rels/UT8UNux PK!8xl/_rels/workbook.xml.relsUTPux PK!IKSxl/workbook.xmlUTPux PK!i3[xl/calcChain.xmlUTPux PK |? A/xl/theme/UT8UNux PK!%Srxl/theme/theme1.xmlUTPux PK!ɱq xl/sharedStrings.xmlUTPux PKp!openpyxl-1.7.0+ds1/openpyxl/tests/test_data/genuine/empty_with_no_properties.xlsx000066400000000000000000000224041224514475200305730ustar00rootroot00000000000000PK!̂[Content_Types].xml (U_O07;,}5[1꣒7еM |{oh8%[s?m x,I00˔},^aF m lȦb#6<9Ju`$^;!Wb |4siMPaBA4>xrC.KE}UE2 )~Q6 .(-)P#H0i =xjvGA(BGOs%uyYvջt0gt{.t$TYGy.%T9;jtzjJI ; H z9жk MK-s1::) =7-0Yǟh.ji=p\PK!P|NL _rels/.rels (J1Ͷ4ۋ?n&$Ӻ}{|͏lw8Ա7.JP-7ϫPI;ؓ3%U7WPrSjTd Z'҈@>G|h{lHo^ fLwޭA!OuYzb{=d iw ]6׻IСibN)Jsql_u$tw|phe% H@ PK!8$xl/_rels/workbook.xml.rels (j0 }qvuzu`%M`{3ٖP !vյmb%)TBoXU[v[%+,\@;nݣJ;\y4O 5K (N4ɾd` a}uU5xP3-6G+F`JY:VV'zf}au6&/TƐgbdnhC`bMT[(yqLDbڍbJKaГߩPK!IKxl/workbook.xmlSMo0 to E,X(=+2 Շ!ɵG)m!; Gͨ$y%f)%>~{yk&7O`g +i}N[PL#yt1qV+iz(&4=eXaFp+ĂdwFHx<1"¾GId t\ؾ eIA{(܃CI*Q΀I %8w/KbIԾ ;cqy~Py(LYbhDfaRF] <]DA:&|#n{q|_\ p<*&EaH3qD>K<8_hߤ -)q3nTrʏ'0rj\UnCQ"qhX; jTPK!xl/worksheets/sheet4.xmlT]k0}?='N&v))eeke:Շ'N_+9RtϹ~(9(IN]dJ hnjwbILL }Goʏ/+h}%-(&'y]: NJ&tz(&42 4ÝG yߵs'4} nT¿F3P(B n3>b<Dae$!Z6n=`Ԑ}e+)pS4f>ǔETfp+)ZP1b),[ς=> ػ?ijj ,3JPЎSjY\b(]^ ) <#er,(uVgQ@54k=ZA7Kc&!d9OYzIIߋL 7D,x<+sk,XY6r` ւj9]@Q{|>//rr$d]D4Fah>`9 ;Р1Iƛ.GqhNrc?m[}G:ց݊7$+p`C--0N&:QEZƌhuU8'C|7He|15J~հF[TG0OZ PK!3{33xl/worksheets/sheet2.xml]o6@?zKlEnER,h[$j~lj"PK^~\;UsٕdorQa*:ŚU;w_Y 9Wnj$#Y7-o f ^!ZYaUB.YĖaO5oH^1wDz.:\멽EbWVnWLOɆae.E'Jjg}]QglW$ս)y8Rg۵]UK͔=(ͨXud.J^H&~$ kߺ  _> +k|_xUmg #\n3aXD,v {v+\Vs opTd{3=2Ŷk)"i^\>~KIiC铮X:K]88!ue9F r>B҅r1\bԕ= #$Lo #ʰ,4A^w)n;=҅r9\rԕ/,ş凛Zﻉ2}F}ʗ>`ǹ0J~yo#!]!bSiB1#QM}u+!\3H?YPX[ЎX3kb8ԃ(3ή# BsXjKqՎ8,ϋclŖDj$ ï"nXsY`D> 8Ws4f6) x@Ck)spH|=XB|oAXF5\MWl9_܌?+]1za5fXk]3XkSނLȷNh߫,/pQ1.t۠਻6ڇ1i=??e;xdv'#jT:~ܛ6FxoeՠΦK'Y 8xu} 8rgu~<]dnɣxy5l8y,9OF;O?PK!ɸxl/sharedStrings.xmlljADή̈v[w`g ;.E*ns$vخϧ'h>~fEj:~#@9Ȫ5)v' DY7͇!2BWfVm_U@UG)p|@kzk MPK!AX xl/styles.xmlTMk0nZb;C lJ= a$9;,Ћ=37z4(ycVMvjtU_Ч}j :w7u#XBق>%m (i#C zI}L$㊖yѣr}?"L &eh qXbbW&xmOb9Or&x?3X26;.e>0 2i@U^R,.dMiaHe^kPn$œctvNK4Zz@39!fi@G?8?5JomAqݜLld6c`3 J;aS)^ςJ`ƅ}]7]=r.uGBl sYzv&bTa-<+eS4񪢎9Etpp\-2 xУifY' AŽX`Ȥ=cޱ~nS / ߡ壼 CPK!%Sxl/theme/theme1.xmlYOo6w tom'uرMniXS@I}úa0l+t&[HJKՇD"|#uڃC"$q۫]z>8h{wK cxLޜH]ś*$A>J%aACMʈJ&M;4Be tY>c~4$ &^ L1bma]ut(gZ[Wvr2u{`M,EF,2nQ%[NJeD >֗f}{7vtd%|JYw2Oڡ~J=L8-o|(<4 ժX}.@'d}.Fbo\C\ҼMT0 zSώt--g.—~?~xY'y92h!ы/ɋ>%mGEFD[t3q%'#qSgv 9feqwW@(^wdbh a8g.J pC*Xx8rbV`|XƻcǵYU3 Jݐ8b3+(QuK>QELKM2#'vi~ vlwu8+zHHJ:) ~L\E\O*t@G1lm~C*uG.R(:-ys^Di7QR8,b?SQ*q7C;+}ݧ;4pDZ K(NhwŘQ6㶷 [SYJ(p»g>X_xwu{\>k]Xy}钣M26PsFnJ'K,}䇦$Ǵ;@` >*8i"LI%\ xӕ=6u= r2f 3c (:jZ3sLs*UܚЅ ]M8kp6x"]$C<&>'eb. vJ|yXɾ8Ȯ]7R /=,.&'Qk5q&p(Kaݐ Sd›L17 jpSaS! 35'+ZzQ H )7 5)kdB|UtvaDξp|Fl&0_*3n'LE/pm&]8fIrS4d 7y` nίI R3U~cnrF:_*P}-p Tpl rۜ4LZéO !PLB]$K *++65vꦚeNƟf(MN1ߜ6&3(adE,Uz<{EUϲV)9Z[4^kd5!J?Q3qBoC~M m<.vpIYӦZY_p=al-Y}Nc͙ŋ4vjavl'S&A8|*~x1%M0g%<ҭPK!=2?xl/worksheets/sheet1.xmlSn0}_iJ lUJ6KlچIh}s\|&>*IzNdSJ]N(qI!/uSv0vOA6޷i9ހbnnZxRG"Z`U)-2RLh:2#;ڏ$$XkDlN17ERH_D紳:=5S[LLO{%)Q0tkWEwigmUI[+DhVEZ ͤ90wÁe<0H[_#@I0ĝ0p{>yo4 [3 pǾ]xNfG =GG|2.7<6h$7MoPK!}T  _rels/.rels (MN0H} PnRwLibv!=ECU=͛f0={E tJFkZ$H6zLQl,(M?peKc<\ٻ`0chGaC|Uw<ԀjɶJ@ت` %TKhC& Ig/P|^{-Ƀ!x4$<z?GO)8.t;9,WjfgQ#)Sx|'KY}PK!s?xl/_rels/workbook.xml.rels (j0 }05{R>@D2߾og0O׊/ x KR/W+(^oAkW;T0 !ڿa9~"$#NS{tSi2Բk4}a8 ±Q }LWUcś_C5?DF6:`!nwI,o ctknܼm É,"S`v[pZAF)wbggPK!xl/workbook.xmlRMo0W|g fMmC]MΎ1F)DU{覩z)0oޛv-!W8YI#l̹߿|5|dnB6xHv@NOw&sW28ΈaֵroE -@7kʇE]k=܇C iW^i*N5Ɔ_aXvg5HNЃ3%v΃3=Le?GU0F~}Xc:1܉i5ZY49m8T%Wr- v2>5wfor }(sw??0>(;і(Re#ekF]6p:dY6 rrf\i-?+3rPK!h7xl/sharedStrings.xmldAj0EA̾;NJ { UI.<{iAS`MԯNDJ++2a,/$nECA6٥D-ʵ? dXiJF8,nx (MKoP(\HbWϿ})zg'8yV#x'˯?oOz3?^?O?~B,z_=yǿ~xPiL$M>7Ck9I#L nꎊ)f>\<|HL|3.ŅzI2O.&e>Ƈ8qBۙ5toG1sD1IB? }J^wi(#SKID ݠ1eBp{8yC]$f94^c>Y[XE>#{Sq c8 >;-&~ ..R(zy s^Fvԇ$*cߓqrB3' }'g7t4Kf"߇ފAV_] 2H7Hk;hIf;ZX_Fڲe}NM;SIvưõ[H5Dt(?]oQ|fNL{d׀O&kNa4%d8?L_H-Ak1h fx-jWBxlB -6j>},khxd׺rXg([x?eޓϲكkS1'|^=aѱnRvPK!r'D$ xl/styles.xmlMo0 AV,aŀf,9BaHr׏;AMM>WΟNZ#w^ZSlĈfkic55U܇^Bhxvm/:M,]C|8}LҊG4x l5{fuKO,4>7:Z)();^K欷",GUnȆ̅5#f;W;lߌkvStQe#UYaR*P@aY*Y9jR38-h1:Ɨ$T*2h} IqD7EIye] ™qv"@N6geC.y-ic U`2pƕz#'Lw:<&M8ha}}f}Nb_eg[m[տtnn(+{0qG `֤e_8\qҨ Lsu4+u/J?i8HTO ZZY"sH^5IJ XjQкbZvG7On$b%=(Ho֭6|ՠ}<oF[]1@3zFJ G\UBh/ lIOb/2wIێ/%NVƋޚ-Z!"qVaeobecn RGB profilGeneri ki RGB profilPerfil RGB genricPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Obecn RGB profil RGB Allgemeines RGB-Profilltalnos RGB profilfn RGB cϏeNN, RGB 000000Profil RGB generic  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&ltExifMM*>F(iNHHCC }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?5SGOYtu;9tM`U:[|?Тյ7Se?׃?S x3 /<)ozeς:}6]O 'Y h p5ܰ%T>J@ W;5Cs|V CoFg8|ƶڞkۅݸ('Ə i>&xVjg< 1P¶^?&1Io]b/!S x3 /?׃?<?z95/xZK?W,.N|cZ018`LG (ƪ oS x3 /c0~Ox%x7 W$5}WE:]uiu [y ʹ]mfDvBq}N#O 7Ꮜ+Z#ZE񅗊~燼1;]'ºt&=Cçſ:vS x3 / T#~ #3"?ր<:o :y7~纻{yFgGgbI&=/¯'^ wKj'ogo >4kV[Jk=oHԴvϩ^YYGn€=7z_|#;\^C=$_ <)?jml]5|?7x_ W;5x/K^:މ |sƗjZfg/o-ID u)]X~0!(6Ml+im%߇x 2{AGrX/O<9=W_}CKVxV#\x@>'ĶY v/%oda῅|O^ MOKe4^[8o-6[6mn y#[oKYxL€~-|.3vg$7<W? ;8Ӽ& xKҵG>*|9ⓣW0 ~*>bf`|\S_g=o'P'T6+/_jZ- \4[m,.=;M7Tڇou #TA˳fRo&j o; 2WƽVa}wC.})>3šfMOź&k߅xs\Ѧ.|?Aeډt=7>ѵ]O] :>x+_ׁ$j߲o-լOw:¯2YƱ-f C,LmCMB]Y;ǟAU x^u}.>376qKjzm&kxZKmkJӵktH|CyUs([6O5׿x?]RP _X,<_ڥǎm}r׾!&m ď)-"D*{s%+XG|ki? =3 aBOjzvᤀM|'_hzCS_|%0-4v^[[i&GiDQ"4,wH??{]<9%ckzgZ^1֎ixB=+M[uߴb庖4hE5XAKk_K~Z|:(M+_x> d^(![+Ʊiy_iw0_/ƭ/zώ߆5{o&u?}ᙵ)5[ ?L֮K᛫ O77/n~xǶ?|ecGƚ͝K{]Z_-u("H|eg 7(ykopfAGW<ş;Ǭ/?_OZO :Q֯tt𕶵 xLҴ;-ӧY@5}cR  bo׊fh5]go[i!k?<'s?g /hkOkV? |1!Ѵ{ҦIKf=Gm\xg4ox}_r޼|m|Bpjf0aceT`QYVBOGm\"#Y;iQi<9_Vz|?i]NwzY۟>Y1/KyIltFqϋ<,Lj[V]t3r|A]]iuF"*>=¾,i.oMᠲx2>"|4k:?5֞ͦoC>i\x6vKon ?9ˠ|ZEI?b|QރǞ!]cP? G¾ t{_ .G|oׄ x >֯⯆;Oiږ;E-k>+Z4Y&`kl,.uK{;<L|*w^oKuSyuj^%.<=;h{k Rյ 2f?-mxG١o<3[*Nc|oω.|ZdI]x*HEiM|&Ծ>|^?/F_7F/l hrZ}ծkvqMww׶6z@j|7>|<xoOxP4;6V^ EˋcEiF?CB>o >G_oZqxc^uߵoE'дxckfj~kP+ogw.ch[Qkz?U#f%QlQቼE/Yea_炗ķphui2CcnZtR Ÿ|Jxzox]uhzvuox\&=_/|3)էKRR G\׮5P~|Lu6xVW~m.Asmqn<2\MF`@4K/m ״k2| >A,t"_SExĚNj|kдM?Oҭ$>>!Y𥮍 kqS8:dhDž+WxosmT-4Yf?ᾇ=~=oÚD&E^tjՐ [+៌?Oߌ9W}஽=c\/¯MqxDSK^w>ڍ_EkR Ũ~Qi0Qi0Qi0\xL[ҌkOF}jgTrG#(Z k~-ʞ#m߂S0׼U.#Ӽ=]|9?OÍkZ_h6z0_/N톭[xK}QWX8xn3=+?i+uߴ0[_XG Y|@KkBm]wE9~'㻯T5Oeǯ|NEgo_4d>$Ud>0Г_Ӽ3sC^VD`H>9ꟴWW>no'|5㿇?xŞ/iDŽ7Y+ỿxEuA2{w]CRƾ;um?<7kO?_ [?k;? _x^K֭ۧo56moN@?W_ÿgZ< 29? .|Syxgw8>'fN|7IŗZόe{}n.a'Uݯ_(-HS3Yn=HQi0Qi0-ᗏZow$>pBP b@@ | ^wn|#Osuu"{mI[7YdbY䑙݉f$h#.q_xX'q7^G73h)ǩjYҴoMq5;-7R֯S6gN@>3V\o<5'дY^]i GhGd$dF#*￱ikƨ? HV6jm#Zo?ikƨtNt4~"rb\kX`P?ikƨ? HV6jm#Zo?ikƨ4(tݝ5~=KɡIrV3@Eʼn|yϏPд3u~rv&jZ[j. _i:36hϤMEMq`Ah_m!xCUoLA<ҦkMēEfbY{aL*H'QPR :?I@wiJ?eCNP/t%&撀ӣ)74i-IS](|+YF _!0@K;_5Be܀WwiJ?eCNP/t%&撀ӣ)74gx IOIZ|W%ҖQ|ed6w^ SAْA!!LgwiJ?eCNP/t%&撀ӣ)74gx IOZ0Jxde27BYG$p]bdY/t%&撀ӣ)74gx I;_4JM%5|H<)08$}VwZQBњ8ӭʣK ~aQ-7c;8?wiJ?eCNP/t%&撀ӣ)74|UZ|4xK_ k<xj[y&iIο8rS ݱ|6EfKTtfXt]YO2 0 \"[[k19 US WyB?@%w)x!WyB?@%w)x!WyB?@ljte"M^cռ2E !@4'G%w)x!WyB?@%w)x!WyB?@%w)x!^&__EDX=/2/.0yoS@W%w)x!WyB?@%w)x!WyB?@%w)x!xGķP x_2_&&;XC.t&d2zW ] ^%HХ_ ] ^%HХ__W?/*F~ @dn7)8K?+KĿ K?+KĿ  ⟉.~ݼ3 ezmq=BCi+ X;J'Xb[)uM:9cwH侶IL (ee9 3@,[;9xHvk!<x\j<;3k7Akh~2?oxhn#?j\Jf*w+弌x[ !!A` g?v/*9+ON|%0O2u 38@olP?T}~_Umo@lP!^Z]"na,swmm>g?v/*ݷmeݠsun3[ ܂9vmo@lP?T}~_U2K?.Oo/-WxoE[-AzU?T}~_Umo@lP\onmٛ DђIn0 I&<:ßj Zt;۝"[> gVMK,ĖbI4o읯|N_~6f;wZ)o_ 3ö>"-:-r]_.4w:ͯb4jK׃o4OgLĒM=k9 FC NFѺ4xWnD<+B_7@"wF} л]ѿ_kG4Fž!Ƒ5 *9_/dU ;@:D<+B_7@"wF} л]ѿ_kC¿./t>x64qE bd.Tyxԁ? 7 xWnD<+B_7@"wF} л% j[]lˠ2obBV8cQUQ:D<+B_7@"wF} л]ѿ_kxC¡;}??΀9xS3x7’ˠiK/ti$-NfwcYfcĒI&:D<+B_7@"wF} л]ѿ_k+_v }qoiPOa+t)Ndc +# |6/[߇ -KƋ=oc৷eEecF̓ı_ xT/;DDnG|O|g??q?f{PφC>n9[ }&/KnG|O|g W,qxwt%gx@w?wH \C>nG|O|g??q?f{PφC>n9/WZDxqmoc"5tVZ"@[C 7@#ڇ>'|3j83t=?_G?cxgn9W;²XExsˈ:܈<=$|H7;6I?q?f{PφC>nG|O|g>+hw|3\~םDm1J` q4Rm'ly?kxi?x_D~&N4#M &dxbg4hIt/ BV_6_b{[Foi֐x-g[8duj$܅@=Bĭ()gCҭ-5(ͼREܖa7QL`eUpSOUZw-hOUZw-hOUZw-hOUZw-hOUZw-hm?>m?>m?>m?d6}Q~< sn<n xkD2Ǒn7_QUoGPoGPoGPoGP\og?*I.8vI=??~~[DIt%g<ǫ14b?׋On(4$= EΙ;|Y~!u[V_^ 5]{O񥞗i(uK=&XX^~|!eo/|?M9$y-eψ<6d##uQ@g?,@!xS_Oi Pg?,@DŽ|0:C<)@ /c ? 4(3Ÿ? KX€C<)@ /c / kt=5x1)kXXn4}@G` KX€C<)@ /c ? 4(3Ÿ? r4 /!XǤ}p}O|1?+4$wFI2[;dI'I' 4(3Ÿ? KX€C<)@ /c կ?t3-zXfHM:D`2+Ac/w>$͡hC^յҬ4xΝ˫jUYo6yfVc |El.|9E"~ |?/ |G.oH yn5}Vc,oMov:%׾*ZI?hkCt ן$# )FT_Zg@?p?"??WC&E4\i^'IC|-Y4kxd<{qY|@:E4c-38iM+Lh_Zg@xŪ,+"E2_M8a||{9=(_Zg@?p?"??WC&E4GJ;^WA|YaYJI\1z(!G:E4c-38iM+Lhx+OHtx+<x/J<MMo-nV= 6bYh_Zg@?p?"??WC&8_bk.|Iw|C}:K$m%&i.JŭZvZU?G.>/OZ$xks&&? xX?k G#NeXqJ_iO%|E?2ğJeϗ?+hK/+/V >^$W_Vźi7C|+ RϫeX2T1Ef "2ğJeϗ?+hK/+/V >^$W_-/|I[@1|WM Bξ/gx̌iHPdl6:L'"miO%|E?2ğJeϗ?+hK/+/V'A<@YG~P]f1N`q$LDQX@g&Z_x _L'"miO%|E?2ğJQ&JW]H? k^-`wa{?ú21 2WN̒!Uљa -/|I[@&Z_x _L'"miO%|E>*Nih5 k#OvgӮgOHaWTAf@>&KZ ׯu1kj^H oTY@93M3~|c|+MDȺ>e_ |co H/~&xckϤZO[NQEp?lsW (9+wq9 @mP@q?2_E{@P@q Zkۗ|( d(rJ6;H4x?h|:m (:$!e4m)|p|?.iijZ~Qg3}Նbx>V,|C؛XV߃8J:Ň_cj_".x;~־5x~(m?V:MDk\}]\xko_ xwO&X[ͷET0Wp;Q?j?@ >YG ?]OgKZ5ҭƘ|&en$zQmYd-O >YG ?]OgK ,vt?.9M+xIYG ?]rG.-uM,c_ O@mf8ڠrrrI$:@|'@ڏ%;Q?j?@ >YG/ sxVݝ.71' tOgK ,vt?.@|'@ڏ%S^k˻m8qm=zhde=:ᑶ#m`VSс=y>k >Z^~x}{m&+,*>% յbty8TRkX~̾7?fX>[|to 7čV\oEC⯷xZ5+++kXSIof5Vξp\Zk]s) KŖwI{׈RL6KQY"HкΫ }_4}_4}_4}_4}_4AOMuO V3+G vRA*ȭV@:?:?:?:?:?OIƟx&q0þs6l ߗIT 0Y[Ok[Ok[Ok[Ok[Ok'x~xO^7z7uǘ/n#RѰ  j?eޯS_m5ă@WJ>iZcqLoi+m^D`OWwkگM~<5;OZ~$߯K-;}#Ǒ_&^FЭr>*cVJB \(*N+@;h^&Lx2%.ƹ`l$w(/Y㚲 KpSGq 끈Hdi $(gq%HN9pw9b06M5YOsW5Tf:m;#XpO9VԺG~;ʞڕ%kvs\2,/4]&\}~}3LF(PK-!&xD[Content_Types].xmlPK-!}T  _rels/.relsPK-!s?xl/_rels/workbook.xml.relsPK-!1 xl/workbook.xmlPK-!h7) xl/sharedStrings.xmlPK-!0k xl/theme/theme1.xmlPK-!r'D$ Kxl/styles.xmlPK-!oxl/worksheets/sheet1.xmlPK- !5+TT?docProps/thumbnail.jpegPK-!N"ldocProps/app.xmlPK-!~u\oxl/calcChain.xmlPK-!;FgpdocProps/core.xmlPK ropenpyxl-1.7.0+ds1/openpyxl/tests/test_data/genuine/libreoffice_nrt.xlsx000066400000000000000000000106661224514475200265750ustar00rootroot00000000000000PKwZdB _rels/.relsJ1}{wDdЛH}a70u}{ZI~7CfGFoZ+{kW#VJ$cʪl n0\QX^:`dd{m]_dhVFw^F9W-(F/3ODSUNl/w{N([qTu GwQ9K@1o7gዀ=yp(X#ݟF3  lKL`wB=&qmj}>"X˱o078݀M&@ =3. ׇw0Rl~)qlM$:{J߱gx [/r CZuw^ͨ#0om·BRfKR]@Ar7_RTkjKv\sw&ᣠz&Zb"m,Yr1gocYj:]N,xtHwauV|&ji'c`I,yl;끝FePnU{ JYTawZPs[j#^rL.P E9w"ZNP<†oiXT`\xlC^zFQMl%˟nZ C=p"a$@lD6BHAj {[A[mm-x$FhXl60HYv2>T?XZ!l K>S[)`n[*BZ"*cz?Cf漽w/ovF=VbcZo4PE$>aXi4PKSEPKwZdBxl/sharedStrings.xml]MK1amV)^jkvɚT<3~ >J*z!'>9<<60&_$WVD!-}LY1GAe&nz22W D'suw&$oR'Vi? )yF5gB C(m|.ۢw_̴aPKqHxPKwZdBxl/workbook.xmlRMO0+,iU"BpycoSǎm ׿uxvf[kC4ޕRS^,TH4Xﰔ`aUy|R6D,ˢj8:>@\ecHy>Z0Nf7K-:ڑ@>6r1!]w-˾d7 j]15؈l?+*bG`x|H^;lp?})|lmB>T}0#*xkKIa|7~m52'S(xr"8h b45Г1GI7ʾ8Jo6¥@f6&ʲ03<7z8C׎59KXz̶<ξDK"Gy^$!PKe!7PKwZdB xl/styles.xmlXN0}WX~IJ[`Ji҄iҴ8cG _uDK=sϵU*ͤLjX&Ld3>H"¥3\QϣO693S~< HTTӥ$6^AQ(ż0r) d]\]Ƚ@t((LX=>nsRR0^9pd&_SĽMyg; Kb UbԌU F h>ѤP?T l~[F2)*_`ۥk;l(45@XۧgI [1w(S>W*E,yuY& jqF/ h.Uԇݓp!6m-r+fTP]FL$4Թbi!tn2w'[ F/ ksr@&r] N fَ b1A>l3 b1r||PjR3>(5CRs֊ww#{Uwz:==.7L4k\:+a?Ng~4W(HLLAt+RTUtd? ][&na'uDgu{ / PK)5PKwZdBdocProps/app.xml1 0ཿBklR$R:wCrjIrM)Խ{]-#ű(Eh:jnEd5Z=B-6mhbJhF: +dy*˳-|+V Z2}mssR=(hPKÙ PKwZdBdocProps/core.xmlmOO ~{ (iA&&hx툅uҺ,Sv϶lhAPVJۮA:EY*Vó!dIdA8 D~ވJa'`Q([TrQo%1`ƀiAkt; 9Y] 5c1V3e5vZ냚I"ʒ6vLުvxIh[Z2zH?vaSqRrPK# PKwZdB[Content_Types].xmlMO0 U!n$0qF!֨matlH|m%VQYߵwcX.\lPƹ*Qn$]C,=v*W2(ݨJj"! %YUdĬ ZEOF˷r.8AI}yl^o N'pBR Iaari5_;{Z!LjT b4T4DÌqDl ?t4*f;+@{졗Fio{SmAg`2k? Ly&`Cwvݙ|^zv*uJw}\Y%ACB$qskMmQ6-<+ց_ݥ㸋G|g6 PK!U0#L _rels/.rels (N0 HCnHLH!T$$@Jc?[iTb/Nú(A3b{jxVb"giaWl_xb#b4O r0Qahѓeܔ=P-<4Mox/}bN@;vCf ۨBI"c&\O8q"KH<ߊs@.h<⧄MdaT_PK!>xl/_rels/workbook.xml.rels (J0nӮ""Ej}Lm2㟾ۅef2k&WP%&w ޚ[= &$W4KH"xR㣔dNҨ9ɨAw(7ey/O ނhm| }Dg"$4FY.2#59鳔Y]bd @%s"ݚ0tB)[ȓPK!qxl/workbook.xmlMo0 4AǭHܦF"M"hXh %CRߏvI+!ž޹B G!F\ o3Q[|4yk ej/tT4Ahm ֜Fq^  ԜU%J %u0Ekq>Fm`HR &gB;`vNH8?\TڈwZ)N ,|z<֛,xgOPLw4\i m2. /dAH4F-~\_q$O%IR$W#ҵC)|J1J)yr|K䟢_Ie ?z*NzKPK!7(xl/sharedStrings.xml\M 05Յ) mTq!d0Iy0Ьjm_/N,}W:=RY}H9EbAwk}m PK!%Sxl/theme/theme1.xmlYOo6w tom'uرMniXS@I}úa0l+t&[HJKՇD"|#uڃC"$q۫]z>8h{wK cxLޜH]ś*$A>J%aACMʈJ&M;4Be tY>c~4$ &^ L1bma]ut(gZ[Wvr2u{`M,EF,2nQ%[NJeD >֗f}{7vtd%|JYw2Oڡ~J=L8-o|(<4 ժX}.@'d}.Fbo\C\ҼMT0 zSώt--g.—~?~xY'y92h!ы/ɋ>%mGEFD[t3q%'#qSgv 9feqwW@(^wdbh a8g.J pC*Xx8rbV`|XƻcǵYU3 Jݐ8b3+(QuK>QELKM2#'vi~ vlwu8+zHHJ:) ~L\E\O*t@G1lm~C*uG.R(:-ys^Di7QR8,b?SQ*q7C;+}ݧ;4pDZ K(NhwŘQ6㶷 [SYJ(p»g>X_xwu{\>k]Xy}钣M26PsFnJ'K,}䇦$Ǵ;@` >*8i"LI%\ xӕ=6u= r2f 3c (:jZ3sLs*UܚЅ ]M8kp6x"]$C<&>'eb. vJ|yXɾ8Ȯ]7R /=,.&'Qk5q&p(Kaݐ Sd›L17 jpSaS! 35'+ZzQ H )7 5)kdB|UtvaDξp|Fl&0_*3n'LE/pm&]8fIrS4d 7y` nίI R3U~cnrF:_*P}-p Tpl rۜ4LZéO !PLB]$K *++65vꦚeNƟf(MN1ߜ6&3(adE,Uz<{EUϲV)9Z[4^kd5!J?Q3qBoC~M m<.vpIYӦZY_p=al-Y}Nc͙ŋ4vjavl'S&A8|*~x1%M0g%<ҭPK!@]  xl/styles.xmlUK0e'N m)l *0َ=dhOrx\ 3S;]rUgϦuTTh22ϟRZ9fx\5n$3076:8:aI "dHJ+gNp4(Ծ#+A9"FQz5|k7ՑWt;ɕ6^}71rܳ"y$8~$Z(tL,PB9X"O3M@Ig6,uwSo3\+2'Ol JvbeO-"dE(ք|P$Fcu>jS%<rPwui Bi gAB Ca=9PAǿD/`c|?,:U#5 ".Iވ6h x_" |r ٿ|@]XwĊo_$x$듐=#k_ 9~T E2^ʫNk^/2=pt%Yf-|a|GQ%AxIJ~h 2jBoD-Y+7ZD]NfGIl dnHDjam971.԰>3Z2G g8!C*C 2QhH;# 0r Ubt2Xl@1 w `K1|,:t)bv2v< ,0S; )_chA_o2s*6t;g >d.!FOeX5TS ϋx7JǨ_ >! q[Y Is )MIG?>P!x~PK!docProps/app.xml (Sn0 ?7rlYE펅&ӱP[2$H%qv@G=>>Sb[WQ )2$-M&!79:ѐ8r>5 XT"O]BJfF%<=R[ G(K#kH'qA琟7B3Z(ind]s6oJI~J~5-*F RVnj,'慨0zLkPqFNMIK8r F/+.4ɕ} XM+ {;QC{ZL_o4ާ.d +p?)_tzKzi3e=wߗzz|f&oy?PK-!ȣ4v[Content_Types].xmlPK-!U0#L _rels/.relsPK-!>pxl/_rels/workbook.xml.relsPK-!qxl/workbook.xmlPK-!7( xl/sharedStrings.xmlPK-!;m2KB#u xl/worksheets/_rels/sheet1.xml.relsPK-!%Sw xl/theme/theme1.xmlPK-!@]  *xl/styles.xmlPK-!&'kxl/worksheets/sheet1.xmlPK-!'Sxl/printerSettings/printerSettings1.binPK-!l7QdocProps/core.xmlPK-!docProps/app.xmlPK &openpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/000077500000000000000000000000001224514475200223205ustar00rootroot00000000000000openpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/complex-styles.xlsx000066400000000000000000001330151224514475200262330ustar00rootroot00000000000000PK!;H@l[Content_Types].xml (N0EHC-Jܲ@5*Q>ē/y="TTĊskƓl %#T))eFaBɶl6,0l%kcwcՂX8" FD Zht+g#ؘNM'Pㆶw$βݹΪd{* wQޛ@@$xÇA Π$ds07|wnY CZU ]2tkBb .Zy?FAH=<}dbj3W=܊5⠾瑦.鷰.;$!*fq=ۡ{$oޠPK!}T  _rels/.rels (MN0H} PnRwLibv!=ECU=͛f0={E tJFkZ$H6zLQl,(M?peKc<\ٻ`0chGaC|Uw<ԀjɶJ@ت` %TKhC& Ig/P|^{-Ƀ!x4$<z?GO)8.t;9,WjfgQ#)Sx|'KY}PK!nxl/_rels/workbook.xml.rels (j0 }qҍ1F^Ơ-{c+qhbKɠMt\ 'g&WP%&w >׻'[= &$շ774OH"xR㳔dNҨ9ɨAw(7e(3O ރhm| sD"$4DSOUNh9.)k0՚0!!iɹS]٬ `2K9Gyvq/PK!ツxl/workbook.xmlRێ0}N Q\HUekL MaU;@v՗2>sΙ)G7ἴ*Hnki.%m0򁙚)kD_7`+K܆m 턁JcfRw!sվ"hEhfa6`y [y\TqQX}dx #|829v.\߽륂*MII[C\w0+1`V4$=yb`Ovkxy sI֡y{!/mm(\ib(܍nـϓ sggpp:3Sb09}_">PQd4=4)v1uJuvy4Եzۄ,ED\{Y*9ޞ|jnmSPK!)xl/sharedStrings.xmlSj0vmP/uiI/УgmYrqLp-Ex7FnW"4UϺp&4`.yM=sEO4~"8H#$"?:i6hhd(g\e2τ9MDyUdT|vJjV $,z$_d;Bw{I-^W]WUJt]/7jmo!$*4 \I./UןLSڎܙ#yl/U:Du-^rq9vjpyXF^eKZDž5ݼ u/ +y,ԮzMe)Z4V-7)(O?CįPK!0kxl/theme/theme1.xmlYOoE#F{oc'vGuر[hF[x=N3' G$$DA\q@@VR>MԯNDJ++2a,/$nECA6٥D-ʵ? dXiJF8,nx (MKoP(\HbWϿ})zg'8yV#x'˯?oOz3?^?O?~B,z_=yǿ~xPiL$M>7Ck9I#L nꎊ)f>\<|HL|3.ŅzI2O.&e>Ƈ8qBۙ5toG1sD1IB? }J^wi(#SKID ݠ1eBp{8yC]$f94^c>Y[XE>#{Sq c8 >;-&~ ..R(zy s^Fvԇ$*cߓqrB3' }'g7t4Kf"߇ފAV_] 2H7Hk;hIf;ZX_Fڲe}NM;SIvưõ[H5Dt(?]oQ|fNL{d׀O&kNa4%d8?L_H-Ak1h fx-jWBxlB -6j>},khxd׺rXg([x?eޓϲكkS1'|^=aѱnRvPK!^ xl/styles.xmlYKo8/Aн$6=lQ.WZl"|;CJX1rDq>~pH{OTL_|DLN#8t{!2%\IKZGfYD̘>dF).TN%|ɔE)I <z@&}p/6 y)Q"'Mgfi|O$ߧRi2@uѿ"Im_6K*Tf..PY. ifJK\XGeeQXDq=b}lDPp6 3"_f+gY T^plF;`j69ٺb_4#|:eya>WG|O|4ӽK?}Urw­] wޕ:ĺk!MXx/{\i{ڝ.7aFdce,.Qz3`XK!Non-e<$0~$Xz$ ;?I[WC%8OY?Mno@qigĭhc6g0EXRE=΁.p%['8}H"GmYCX8V+-Sq"tArlRh[5dqv"AYXEkCʨAڇ0HN:I5!HCrdt"AIIOW4Z ɉE҉ NC$QOI5 Aڇ0H:I5!HCrjt"AIԼTJsCE !n텘Labf6 ՙM9Dn ǘlQ Nh45l#{"C@22t0>o ! Q:+H-c[J 2D 1jEc%! OLphG㋰;BEfP`:ztbbldȅ NC,Qa4 PB9!b2E`'6u^~5 oтiMݷ G[~-ޣ~u^+>E@d:@XG<.QdgϷFtCl^jREkOwS ]y|*#V<DdXnua=I rlɐ  lo$}BQmI\DiV.dAnР֏hS߽~T4H/(BJdmInx4jE-EmjQԢ6mR'&q9b`uQ0Dd±z S}0D*NZ`e|:XU54jc#(T;Zn|pҝQZ;Zi e|pBBKІ*.]iηWt9<ĪTS=">r5BxB? 0#ǫʞ;^*P,v9Zx63 .²7W/Hx:n&^4Vaeobecn RGB profilGeneri ki RGB profilPerfil RGB genricPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Obecn RGB profil RGB Allgemeines RGB-Profilltalnos RGB profilfn RGB cϏeNN, RGB 000000Profil RGB generic  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&ltExifMM*>F(iNHHsCCs }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?KӠ]Cck\Ť Eeᔸc!-[/u?;? &!Kχg_ kOC]KQԼS=փæ5\5Io O+n.]?]@7+i}[Z7F5χ4ٮ绚hinfy%&W%5z O{O=|!? <=$#@ O{O=||BOg~2?:t?_k OFãIk\j/-sXYwq^i~T@W$#@ O{O=|!? <=$#|FW ]?/ao//Z5XxO P<]FHYPP@@5C|;JL_ef~&kr4泗\[}";7Cj[ 6.$P?g?|_Z03֩ҿ$xH|<ѶaL{ [x6 `݃@;Oq|K7~ 7Wt2hz]ׁ|=w_uu _luVuM>O\1x@~V6?>-6M?YU.`Ӯ泥äXiXR+]SY1k7/nh#I?!x3 ucӼUN^×GOXi,΁{? 階qrZnމZ7z~Ѿ UB{]x_x\kc0 3coC} KkQm#o w|Y_aUelɳS_X|{OAchy=4n|Aχ熾=7c? |;ѼckNWV5wOӼ#q7X>wiM9r3_U:/x,C\?|CI#яs+>~5/P^ #G?qddO~rG[ 6o@ "h9xpQz'y> o@/}3˃àh!Cc:Юsh|<<7~4"'M(|<<7~4"'M({e×߳W~? k/i-R~,O~&n_IԮ4oQh^׵]:/x6_h4t*=S,WN|#/(>Q<_ghӼi|3X_W+Ok-.ƗHt)ϱaybS8i fv7߆|q߉/~ok7[h0^w~@<)xQjW\[M Úߴgok:W K(Ե]& />'Zo ^_е>cS]Mz/Ž jqy0!Tx]+[}B_ޞG:F:y>  L4"'7;EOzp<Lp< ߍ7g$E5{m:xKwU7 7caS6 ؠKBg.~ ~㽹k>xRV\MᨋYZiv[ԵUv7C.YEԉ e>x5~+xGLŵόo['Li:5GIӢt|#oj>z֓i{okz[^jw`+wl|Cghs=?K4S^>|![5O #7~2OBtxj\u<㗌>/0xïqL|#ծj hxOźu? [cx|ZDmў>~~?'l.uxoOIK ]GOJމxy/ƯZη/ i7Vk~(m|UǏx#ƾ{w~4h?K SK=FP i4_K|ox~$Kjw/~>c;e?Y \,xsN/Gχ%? xG|>m'Qu/ DP Cxz~x|폇Ǡi ?m /V<mM8mƽwD^ [|E v( _F¾2Ϫ~33/Ú~Z~W7ΝNվ_vsiS{~Z&uϊ|MzQq/zF/x[—<$7߅W{a&gU_ X4}XkoXJ? |{!h&| /7'\~<WPxGJ6{^wV: ?jGZ7ŷG&>(i&|*OMk=ɾI4s|:>8~'MYt/f^VCs6h񅦍ÿh=~gǓfMj0[4[;M{<7}@k~`ۯ__寃{/ψ5Ÿb~_juOxὯÿuڅέXZ_F/#iZ7?dⱨ=Vk >-ۦewo?Ÿ~sAE{TzwG jmfH7~ǿ:o>^ kG>'.TЬ<:ƿUՓ2}ZkW|xzgx3ދ}KLj_?o_Ol>x%&~>$׵j0OJ3K+O7@N@/?:/4td 3L#u"Ӵԑ<GGK;Pˣ=~e`C)@;ch';?u<=>]Mt+{uTkFXѤ-" þg,|1񕏇 ͚e|N:a H|CFvU4[dN)MC,Oũnw4kD/n j㞏;7t: <9j~/Rh/HÏ;SoƿGWCw?𥦮t?|MO>״5'F ژ0oI.[}k3F߂CO:f{bÿIWᥞ6ugO|Eyv}cJs1~+|S~|\yE#- W5ME)g/'K޾eWk{Ÿ/~|!>'O{7{ Rww>%>"@uOkq_/6z$:.&ə4M^ |5GwI:ms sq6,%͒K$-] G #~r?m9s#𯇣 iVtۙyG-~#~r?m9o6ߜrD|7@o_9@oK; [Ė>'Ҟ0uϊt σ#/<xǺ%u7/{EP'F-|:M2Yx7JOï. ޓl'{x VWv0v/][^zϊ+Ԟ:y&W.tψiWN|'_'c?73@mOn8jZW»5.y+ji[ ETmo6~(WiEo ׼EY_|e xxsK~hKsAӭ>-п~ѐ|K,SK]O×^Yk&/>| ~"S#)xZѴNO, YR/ž>xKzU_@<ic~&ϋi2ߴo~O&r ah_s_Vcdz j2߄i{c>mgijRE|15+'~KS 6h>UTQkxOT?v{]c_SjQxg{Ok΃&y>tAr7->.xᇅ_FςX&wGio'ѡ=>堶{ID|7@o_9@">M/ &P'OnC04W 3@E')N%PuRޙᱮ[xHiOwR Zo`G!|s_,}rUޣ.wUfkDrw45m>[mVy,`bko ~,ᮟ[Gj6ڮkڭxO/5ȣtͨCiiRKxonPM?<8,FyppxeLB8.NU~tѣ+ FP=L_U:KII)7Q/=9|YK|l/_4Ȭ|)cA;gмaGG;Wܚbjq^2mf0~'PW,]JGxc+7f9&;aԝ)y8a'C5U!)'EJ u&k|?mxs:kE5Xx/ i~Z|iRmC[Ap`/%q~ x\NS\> ,VqTͱEbcCV{Zy#z˕Skeu5w7zs`%?so@`P8%5WW񖗬k^A^MMm?zqغ:џt(K+c)b!V2[Wp['i1"7x֣O 5Z<&Zr|d~"|?iை9g]o.5}KQ%>Ӵ?uCǚ|Kn e{oX;.%|' /7x2^9xhO3V/2s8seueVP2(xry˴'.kuwmQk_ŝT׬$xGwkzxF?Iן^U]BtmEup?G0yv&<_`h\%:118nUO+,t*έ<-IƵ;^gqMIZJnXr :E?#hZ: ƀE?#hZ: ƀ?5h?CO?z?ÿź>oԵ15+c`k KVڬnֽ5NMw`h)ᮍK?S-6?xSq,ӧڵ_>'}2%py8*qMibpVx SfX\9]KGB.P ac)^jMWOf8˿u7Rw|;R '9Z¾"M_^0 3Lf׈<iǻC^\.07d_xqGCqu9.YJQe"͝ F/r3)OʊP>_ߙj2wvQ涿&!xK ;Nf]Q-q{xZ'-~bfmWD֊^BeHˤ^π㬾*K[ 9֯,+&LtԪ5heXl/冩S <ϗJ5~uv՗[8N^gMiu/Su .q$lWHmFe<0<_Ze['c?Ğ𽯋5Mr1]YEimtC41w,?^Wo>!Xw>$ĚޚoN}gBXU4nNVѥ)]ı xBImokm̱]swIv@~=,q ei+ ZpGR2TQ-Ȳ>2;cjj2*N79%NsS05jEw}'5=J}^eP4$wM.nE,dщ\(u_-UӒ VJs\Ju1δr_WVpm|'8LEu+1ʐ`}b($ֹFh$ٖs&,4mjgGZ::H+KzMlZjTs<^2[Xwer({V2{Qaqn8}^NUF4+kf3OV8luq1}Qo8.1X4?O6puJ G.*JS:fhBUFrq[n|\Ct${{max;FҭY)$K0EvG>WCN" FG 'Z:0+BƳU*LDҝy%E~~^o&B;C[5iZXaQs)BuVL5?z-qSIrIui""k/%x<RRz\:LisTwPpFzrμ i ':"Gi:\#}$H`Ў"g*szҧ*UNUeS|I$z zmh:uku}3xDt;; JKۣ"ucpx^YUJ NaAlp՚"K)F2So{p5ò5O6XIK>#[4{}bM:y!-LQH7m.!KeR'B9Fe,nkiQ(T3 4eN{kxƥUG5:ԥ%T1N5R'/Fl?V>(?[~8>玦KL^$/BWH :4]FFXeXH ,Y~͖.Dh37o jgNҵU]/ö6 M:=:/*1Go]6OwZω.5t3Ww2jQSu;iWrI.^|ժ~%q'^> TW? jx]&un>ay:^ Cs\OŞqWU<|>6?ۿGW0/O/--^DVCA&"\<& #)>-™|"<;O>X9_)ը?_&ZƟ|7>Hw",]t؎gsk'kxNC鷋sog}ghVonSII w{LwQAi"\,4j:E(N+>gZ%P̚t6e , r NW^Zsb qK]?i4]<mC67^F,~\;}"R aG ѭG((bfRGcŽU͉{l,xxU\F)9ݮOt_7&C Ɲs׆nk^&o5OcO7U)嵱,:k~U45(m-wT=⻽AuΞ+'˃ێj  M<2<7XREdΡ(5=n/ tv]B'/닋;1FoٖejB"{E?QiC8>"?TZG.> j[տ|/>Ow05x7m'I^OWsͥk3ׯ[ӷgxx Ǐq~)f9.plB8a,6C-8K  N-Q>?CSQeW,:uZXj3jџ4ܜRmI# 1c ai&u?'/Rյ: 3LK=Nk^,-'15vX<5ljcOaUK q}kuqUxhR>xgLOۯx>s-6?[JUlt:V\6q3?mʱZR}*?ŧia+JnTeM7;A~V: /^o" a֡6qkzx[ASB+bL:%3ܟD@~<e\A* /|a,dpZLF?:oBPxt0jH~H,*S^Ɨ;ro)8)k{66i)/f|Z/\inoƿe:i> xDahԔ󹺒ˣxSpQSP1Up2$V!}[QrNiŴoK7o{_[R>~.=Qyw>+&9C<\-գl<=c~kVrw첫Għ07u48ކ nf־67%iN(Ʉ+'w*>m/g~wվh_V/kK ={J7~(^AK(-J< о|$@}*\4 }Q?^USrL?O9*[iy2K2),'9ULJ8uwPU5|jOe&wSԬxnl.' kouo3r,n ̎lvJK#/ͫDlCYfZU(ugxGox5j>/yo 3RӼ49t۹ഷc\Y?&>0K|C𶻪]?RЬaee/Wxj3gTk[m"nl CLK6yĉߓ~x>Y֎oaORnceWٗiuX6Woj_D/n"3w<70g 8f7>.s%Q؊)κRӒID%x#00_ܲ,% paYVESKTBnQU)K}/tjSZ#-縷Q-y9]\>.cka20-fV"5RQ(N..gLMМ5(bq>Ҝ'|w~sgŪ[a>N8MrF EiQVpM݋O?hOᯌux<#keMѢo1F|C"{VJ:Z|C:iN-ߴ6e$ VzoMK~ olVZƛ6>emwg[wϨ[iG Y:)!eVabTtܨAQ{ N hesf,U(ʇㄪ.lCyOM{98,M(V㯆FZ,,U~kskZ,Mdw6s hVS0F-%2'XR%Cu)ڥzp*U 9ҩ 51'{ʚ`C b?#}* >2]GNg^8:Pji.hFHǙ?vZx7짅^6ͳL|M_)ԃabiZc?kX ?ȿ-_? ȿ-_:?߶_Ox$ajQagx[$au}qO5w1>T$`88sԸ:k}~;`VԩGqUyB'49krYJ\{O8+Ox0zG#qF'Zq]:ubRq:|oyl!^>.3XZxCT-bNx't;=8/fhl.WJ'N؇KܮMӕ::I1T!trK.*c{4><,Κ*SgG+.hB*#VTL%+Q{xu=ⵅ,w5^cWC8gm$eV٭Լiq yGyU#C`qXU*i:4&QSqIԍ:||#8KW <5H{JXleM1u9ʕ9š穆J2n')G35>\kQwwV.Snⲵ CDb 4Ejƍ_hթN'_1 =PxFKxN*RٍhV'nc<QSZ?ُ0*49կJQFtiYSIkjIR蚬u.!5WWS-o_ѣּa[ c迉˰.#b!e*x8jؙF_pMI +ɦ+cS68*5Qn:4C Xzu E԰ѩ[Л R{k͢j`HѦ}w]?ڦgb[ۭoRd,enE))b![|Js)5O FGzyVBiJ8PGLVkɺJ;ۻ\i{(aV"6񕽇/57ϥ)ú>wZ֬|G{{5Xm^"t4[T<9jO2XZko1YR1yw@RsF*ӝ ~ߒUӣ*'MU-[LX|_ee?A'tN0mNTU8W=JUuJ!м9jis=g±jlҡ ˨\:Ɲq'c{tktAck>0#WR糅lҵ:ujKN&O \]*S*.~ x<~"';XׂS<kbONY ,6)V_.UgS.|?"3ӦT>35 M-At3S>ң5y|a&x}+Q:W]Jxo[?`ijƺj*R:Xuc*%BқI/'g}<:x[|hҔy<~'BWX:1֯K]*jB4FؚVjƽ.A4Ԯ<-e2޶߇7uK7bRYl4۫(S G9#C翡vZ.+Vx))ῴ}4Wb<5LLRQI9J2O"x;p,zK0tgW WVN)WwRnJ4\8V:YZqEuj&e'Ѯu"kOө_ x]W՘{0*ث[a>DMԗ qX;奉q1ʢ{TTzf9xÀB<@e Sr&>Th%JxCexv?W:g;xkZ ᛿B{m/!,Kx#Y<iYC_Ծ&JWmaGZ?esCNO2X//:+immZ@?+u)xnoX>|7A}i_~ _m=rWb46?hA`$wP2Εx ҡJ/-*4*q۴Wm7 q^?b%bLf0SbJiGjo)EZ*)$p >~Ϻx?#x{'Z?}{-3.t"]b C~h\oke|6kCcG{awo}#%.)msli[++[W݌M o+Ji=ckSh: |5/u;O4,MBXM/K5 .PM<~(q ٿ.?_|/ظkWtwv?e_d/x/Ru1X$V<+? g]R|y /4߅ }gl Sƚ_I>ִ̑WڿVx@l-RZ"/uh{-ľ_"ⷽotݿtmwgú6F׼ci;|?ux:?b<6Zn5=I=xC,x_Qޢ}<wK[3_Z;iuݍ_ |->77"\x>­o bkyxQԙ,^l"cC Eu? ?M/{Yj}[խh- +O|]rݭli) >k 47pMkCaG{aw=9_|06+do|+Ukh8,b ]t$PB|W5$9038_PŅ˾K_%Ļ4lZr,EommശUq0|DAoo1 0 $Q#UE€)G0TaQbc$dvZ%iOIΥL:%)rͳ7)Nrs'ܤޮM$Kؤg nFI'*bʼn%I뽳5CaG?/Зsltk.%PAS?|U"/?`od<~(q0,.{K Ub+Gr~0`~^/:|?|CixCvBu-Ye W]G1TaQU఩'&'ogܟv:G jԫbM9湔#A6/H1WٌR[#|p߂^*OOyiD񥆝OVĚe[kiIޝ I8x>?j9?;ǺGxEoҶ3Hu_qhr",P"ZfANѰ?CE)oވ垩`v,€0uqI/:ychmNgc{ZjXk3\jDWV=r,73i2L4?:F?!)5NOx Msn!“\[Q#|0y[V:GýJX]内'5OjxHҼ;ߋNZ?_g#$mtk/?K/nto +)V:dmuy4OPoDZgaZ}'_Ǟ+h |CwkZƯ7xV"VmkeƥiuyI<+?ᾧoj -[ß !jZn(x8{U !~՚;c~|';>9"/֯!co#? =%Ix?Ro$wȾP#v'Pݧ-5@]΅jπ9Ms]S}uq[}3YV[zQLCǷW^9O]]y^&_.Ao>uwڝ߅bm:f"'3v_Լo|v4iU[^|U_\NKɭP|+j'?C:p!а?]oΌN>i΃Z?umGo?o~}?h>.-tXV)EO Ɖxq+P iyCEz_Q~ i=AAAk?o|1mB[|<eh6OLoGG} LzȱPO !=śI_ 53Z/^6.KZòx;_u<9֙}4ֶt1_\p~/=7w_&Nՠ_$NRUz%:?y5js_k: zuxkw?e_[S|.'g[Ou;>!> ⿂p|A쮬 ŚrM6}SFKV5xn].>4:wu_4 )i45>Rk+;y5;[;xo8@wqWpoue\5% .XuzLb#nw\^ P hIK><^]톗ev<26Zh,aE1O%j>"m#['"<1|A|d./? >ML-{E~"OtiĭumRڀyn:3 :_k5c[ռ)Fnំ>"ڼ:%$қ)wäZx_OjvY .hlnk >)|T/ZxS_?I3z)s_]kVz{$5]hݳB?RiPJⷍ/EcoF|])kf<)|t޽֓kx;׷wow9?Mi7$־;-q:m@<2mak`]+Ft[K_ 4_Ţv +F՚ȳFM7v_X/fQjZĊ¯ bhm0j6ڼ? x5|VDjQ/m<;;ki?/Ti3 kehoeh[x7-tP8.Y,u[h@yš>o[oo+z__ w[3iRBje ䷁]/k26 COx2[І^}Nﮥ67WSiIFL Xվ(k> Oe'-υX֥in[S>iZX]WV6{Ԋ: |L'>% š|Cs _m߄WxgA@7V$jܱx[ҵ  #ᯎ/xgj?xc*|E ?Ƕ:U:i54}:kΉY6S<Zdէƻ)jq;յt>GѯtMsoA<8'VKh.MHxt鯮tf$V7厥=Oᒖ6Z5H&Wɷ9H~kVyzt^D PzC]>$]5oţ]ܛ,. _ 9|Yx~t gX G bju nL҈:;A|xTas$oVL-}zL@P4!n"N 2:;A|xTaw5x(_Fzwk-K_/uZRs?n/E3^m[_" /8 D>|G=c፿j >|U4Z:,t-?cN'ԭ wMo^㛨m ud>-2mtشiχ.%i6ھ:-ٵ!?<={j~1VOG?ڗ˪/e3_ ?/xBOOUa|5#Xn"ӠXOյ$APl״KYc}"-z2EV?(O}yme\y=, ?,pC(Xm^9-Þ +w.#,R&.2. gZ5mYivUmwIm.u^[htlgH2Z=FT=>x*39Kң7QQ\Sk70^jϷC͏SMwqmo4I!’xþ+(W^\ZO-G:uy!IR̅;;A|xTaq_{ؾxW.pGNL`M1abckrI`ߏo|#-Wƚx< On/x:-kZk6o`/,J%\c0[Vώh/ZŸ4ZΞ.tcڔP-HoCOqit#~ٿ~j^6 6>}#I>5x">&gTCoGZwYޏh:65Zxy Oh/xsQ/xWƙmjW$Ϳg6q.q{ {_0'Ýz^1kw~3_x_oO~)Ÿ 5Zw t/RP}CO? aB%w4пI@ > t/RP}CO? xE[}0kg:DKjv8ۣ)ބ\2  > t/RP}CO? aB%w4пI@?jo|o CgM/ Cxo~!xJ!qw X|RտI_I|-71^v@5~ݿKC_3d|G&/ ~!% |]{S&եu/ φKR[\_Oo?,>CB7o Ad2oHd/t}x`\h>?|uivTԴMKl@>f, ?xoXtukm2_ *:׆5xCWx>Cxs0hwz]}k g|{^)WC]~z$c k5;Xү-KgD}CO? aB%w4пI@ > t/RP}CO? aB%w4пI@Oĭ/:INGN4_*`Mx_˒7NV_Aj~%[ x[Yt +PMb 6Ek -"WuēYưyq4ץw46_PM[~+A-kae_\no"T];be7mKLӤRY|7hOx'M-Mq~ ުݤbDwo"b_nc)ym{gx'KMKQ\m/kXim-o| mEegN:5ܳ4j%xfgދ&t3H𿅡X%կu{}K˛_TԵk뻙5;弾BC|bOxoC|bOxoC|bOxoC|bOxoC|bOxoC|bOxoC|bOxoRū.<]$ ݭc˶e 2JTVģcoRRRR~O>d5=+R]t˩Kփ v;YmHu xἶoNn0< Cmmgidm/&;kF[<:"62itf%Cj}ck;cƟE~zV&M,,,HЛKOnai..$hf2Wtҗ|bK'h9!oG<ǵYaf0# "1RxWඟqZo| )h>ϫe4t3M4t#OҴK{h= RRRRRRR cm_ICŒ\Xj:}n]zW":+vx~*2bpa!HF6ld sXIIh.uj;Ɲum yҡcy"qV |=m5ל==5ٷVɹL%K|йTZ\PSA# ۀUϫ }M   C8)6GgjL8s6Kx]/<}_PK-!;H@l[Content_Types].xmlPK-!}T  _rels/.relsPK-!nxl/_rels/workbook.xml.relsPK-!ツ xl/workbook.xmlPK-!) xl/sharedStrings.xmlPK-!0k xl/theme/theme1.xmlPK-!^ !xl/styles.xmlPK-!6Qxl/worksheets/sheet1.xmlPK- !%docProps/thumbnail.jpegPK-!jKgdocProps/core.xmlPK-!ףmhdocProps/app.xmlPK 2openpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/contains_chartsheets.xlsx000066400000000000000000000344711224514475200274640ustar00rootroot00000000000000PK!A [Content_Types].xml (ĕN0M|[vaK]|Yi Yp 9s]) XWha2 (E)y[zĂt$ +0cdGRQ%nָ01=Pl{B@4g?1NҭwWi#: BuH|)0&%+' 0 { <Ǵ%ȗAIKddO ?Jsh3~|bvl[!7B٤a@>~~̽y2f$X{>cŞ[m. |6jwlP/mO|}|Ã>Ug x'PK!U0#L _rels/.rels (N0 HCnHLH!T$$@Jc?[iTb/Nú(A3b{jxVb"giaWl_xb#b4O r0Qahѓeܔ=P-<4Mox/}bN@;vCf ۨBI"c&\O8q"KH<ߊs@.h<⧄MdaT_PK!>![xl/_rels/workbook.xml.rels (N0&CӽPGcBc2[)JZ{m)b&8 6$4vյ<6J%)g`+vx IR΂ h4=bQrC?@0q=pR9) EQ 4ۃ3O/%Ї]U5~҉ Sk ɣb<ɓ@i5ajQ` a?t-dkį1us`6k`WapKܮ CCv/[5)l4L(gbK(PK!-$xxl/workbook.xmlRMO1ޥ˺$,&F\βv%Ɠv{ӱ1>hgK>eNi+nY`g' iq{3:13!0r-ZT7);ZB#,g*-}6A<$?Ժ |1y#m > tDU ]D߷{mxs]3B?>/|5v ӇVyAf.8#I]*m5Ak%fcW%K ȒDNf ";/i=g.~W㠹7ҜȆ͍<'E E$'#(&yϴPK!bmxl/theme/theme1.xmlYOo6w tom'uرMniXS@I}úa0l+t&[HJKՇD"|#uڃC"$q۫]z>8h{wK cxLޜH]ś*$A>J%aACMʈJ&M;4Be tY>c~4$ &^ L1bma]ut(gZ[Wvr2u{`M,EF,2nQ%[NJeD >֗f}{7vtd%|JYw2Oڡ~J=L8-o|(<4 ժX}.@'d}.Fbo\C\ҼMT0 zSώt--g.—~?~xY'y92h!ы/ɋ>%mGEFD[t3q%'#qSgv 9feqwW@(^wdbh a8g.J pC*Xx8rbV`|XƻcǵYU3 Jݐ8b3+(QuK>QELKM2#'vi~ vlwu8+zHHJ:) ~L\E\O*t@G1lm~C*uG.R(:-ys^Di7QR8,b?SQ*q7C;+}ݧ;4pDZ_^'܉M01UJS#]flmʒgD^< dB[_WE)*k;ZxO(c5g4 h܇A:I~KBxԙ \ YWQB@^4@.hKik<ʞ6b+jΎ9#U`δuM֯DAaVB[͈f-WY؜j0[:X ~;Qㅋt >z/fʒ"Z x Zp;+e{/eP;,.&'Qk5q&pT(KLb} Sd›L17 jpaS! 35'+ZzQ TIIvt]K&⫢ #v5-|#4b3q:TA1pa*~9mm34銗bg1KB[Y&[)H V*Q Ua?SE'p>vX`3qBU( 8W0 Aw 9Kä5$ PeD)jeI2b!aC]zoPnIZ diͩdks|l2Rn6 Mf\ļ=XvYEEĢͪgY [A+M[XK52`%p7!?ڊ&aQ}6HH;8`ҤiI[-۬/0,>eE;ck;ٓ) C cc?f}p|61%M0*<ҭPK!!a/xl/chartsheets/sheet1.xmlN0Hw/j lb7Z/xzi+8rxf@΁R E SCmH} =,.n5|alxǒ=E\+ks1\ЈIo:DGm];=*[]Hp Tu]hI>Q?|ŗ A툷~o֠7ִ rlt,pI P˗ SS;Rبɀ)NVC<D3v0t6K?,2cGIRȠJ}U_PK!=d+#xl/worksheets/_rels/sheet2.xml.rels 0nR{U,$dۛ xe<EӰra ;$^.Mc`)5 )RlqƔeU@sTY?P1jG>Cv]7{sɥFfAC)sdPu*/PK!D߼%#xl/drawings/_rels/drawing1.xml.rels 0nzЫXIFooӰ;7;E'r k\;^í 8mqr4,PMq c:a,Ų!FR@3tlr:fi hؓgAO_LQB ŧlu3l̀!& ) ,P_7PK!&ꇻ%#xl/drawings/_rels/drawing2.xml.rels 0n "MzWXIFѷ7EA4Nь)8XØR+vY@.; *eI1.힄"%agI/gji0 9։x]wB:-(A'I'U ,o%|GxҞLŞCA&_{9fKD_6VhPMDD/UK/uhb 8ea'\.#w#ѐ0ƎO֑a ;Ybw맦vWl.6Eծ_wW(ۂײsY(:)Am(jnfhd'Z8)ep ~'QU  YU.ne؈V"~:WkS?`➑x|Ě@PDb$#8D[d$Z|jSҎ[~ȃ1|b+l \V*PK!+uxl/drawings/drawing1.xmlSMo0 Orh44MुFk P~L qT?.AS֤|4r&2~f9&t|["#JȉBjp[IC٭E r#A2I*BJl3Ã4(óf2r{lDaUֱ:Qg³x8LPvOgxܿ!T+-6]#sX%7]+~ Syc UlCҋBHY혱kȗPɯ5s[^P'Nzʍ T_M<=>i&Թ,nFt^ b,λBU3LYJaL;΍jH@S+hPK!Q bx xl/charts/chart1.xmlWo6~EIFqbXMڇmsH{"enۀU bc䍨(dz}~μQ( Nm)&ņ(X@h&h]O|)6"XTJhTkTW~s PP&:y=rbŶB[+D͆Ս7Jix#|!7ݠTr+JZ.@_9T PBm께zׅj0n8Ӈ\0t/6}o+#_QQBF4.Fmg~NC ք{φύ9F Yh95 Nr[%%|(@HQN5-bRs犒3MA4Dda2Q}Se#감\D2ܟ\+UIɎ޷hj5"onkZx1 'muֆZ"aOࣽ VgV̯*@G[s>ۅ,&=7V{o:8Z*^zk  H /RH/2NmŭbnM ȡq9(M3/<3, , oFi,8 \wF<(IpFa?£id+A YlkM( }Y+m-%TTaB*f#b=;`tcңN=WOa[䁉gZe*TOx6*Θ8?0C 2ͣg7sׯdA6GmT n~TtTvX>z\!wu7?$,1Ɍ>[]31ߚ7Z,YS]Im#`^)"``g2x`Ua'PK!9xl/sharedStrings.xmllAN0 EH!ʞI !fH`8'h@JYo%u>QwVHC=M~?=k4BH"뽻̢]^"fVJY2aQb0]d"xjHdU%Q8YqkY5k  eOx,e ŇF؄.-5l|P:`UJzy:)oF[] .KQ$&@ʒR+gQV7,_~{,Љ L1ɒ\ m@`EQz5`w+͑7t$Լ8!.UC@u3j }[ t~ m.̒64εSV:WksP,)8DCFə;\?;D[鞊C"\DHd{^x-g=7a=^{3 Сqufq /|gC% VFOu_+w{^~\}7(XE0A<_mllq?71~Y )(ϾѺ"6<% bTPK! xl/charts/chart2.xmlWmo6>`58qb.)%MѤouPFR_dɵb|9>{}nSfRL$ ̙(їh 9R4zQ""N1$5]AI@R"yE$9Hȿ( y3r(HZ B'+Vh@zk§QvQ_!_H%Ǝ}I'sn@ ZHak!*zc* g͹"b%1gf 4!PQ2KsgQŽx`D7>4h΅[yFcܚn]2)'f8'o6nyqǐF>S7@1K3W@v36k2o^լ0=25 \\TAYh:tDhM0=:H(ۖu?qRЉge%˸j|D;tJw^mA]6s]gGw =l]+H*ԩPHCdZGyra+v 4U̞uES±V[jbޅ{ 5^dIUg}m'=&v3V'`c߳WC@:ڰ ->ؾ'pg| רQriI@vV'jsǽ}ϡ:+];q[LP*kL;&^ ǷѪtm]yd~b=E8=O܍IݨMF<){ޢ~eAPE95]_<^WϞ-7QdocProps/core.xml (|_K0C{fӡ@eO+o!ۂhoon!%'˽jp^]"(͍z[zޡkeu}UpKq$$ܖh{|: 2Ŷgy ,0S;)߮ch@?oņ^8 t;e >{hl6k}kTnWPUN <5̇uFx8DBM>t{RO U>%:i>g]%yAojrGg rPK!ߑ2xl/calcChain.xml\ 0 {w(wY |}ŵh['&/!i>(gTE p ۞kgt|IreI`x4h1~a Ѵ. 5 q D @/`CR9,sXa:}۾PK!,^%docProps/app.xml (Sn0 ?7rڠYŐa$ΚL'BeIX#׏iۍ$qkmALƻM'%+i_#+*W+Tlʋb}RA.Ul'V V!q} w^_  GB60;_/=K)kBR~5:,>4XǠ u+/^S‚elO*T&&):w"dی?T,bF9$YmH؆Q~9m0 N CǽU@_V"< ZHߚe֎A_P~4uj|L+A }19=S >/̀?mu=o?'Iy]K?. PK-!A [Content_Types].xmlPK-!U0#L _rels/.relsPK-!>![ xl/_rels/workbook.xml.relsPK-!-$xk xl/workbook.xmlPK-!bm xl/theme/theme1.xmlPK-!!a/xl/chartsheets/sheet1.xmlPK-!<+$xl/chartsheets/_rels/sheet1.xml.relsPK-!=d+#xl/worksheets/_rels/sheet2.xml.relsPK-!D߼%#xl/drawings/_rels/drawing1.xml.relsPK-!&ꇻ%#xl/drawings/_rels/drawing2.xml.relsPK-!4rxl/worksheets/sheet2.xmlPK-!xl/worksheets/sheet1.xmlPK-!+uxl/drawings/drawing1.xmlPK-!Q bx xl/charts/chart1.xmlPK-!9'#xl/sharedStrings.xmlPK-!aE4$xl/drawings/drawing2.xmlPK-!7G &xl/styles.xmlPK-! ~(xl/charts/chart2.xmlPK-!o>QX-docProps/core.xmlPK-!ߑ2/xl/calcChain.xmlPK-!,^%0docProps/app.xmlPK3openpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/date_1900.xlsx000066400000000000000000000665731224514475200246470ustar00rootroot00000000000000PK!{ZgL[Content_Types].xml (̔N0EHC-J !Դ K|'_{&.*A`+3gӵ *gK6,,[9lSS~2Jmtry1o<`FK8Ǫ#p,.74܋j!WF1K5MoI(e}UɄZU"(VѸ{WV_dE&qlǫ=4tz JB6!> C| 7EjG]] N@@HlE #10<3H_>cO8#RO_I0n4݊9瑪?2sOB}{{GjTtIPK!a0xl/workbook.xmlSMo0 Є\rf"`(vk*˱K2$yv1쿏r,Ew'|N{YXUIWLB3v<: Zk3-?~H;mZ$P6Õs",v Rj#N [qd0 r5k%W:o+X#DJԺM!/2;´M+jh&8]d+avC ðhJKh;\ ~:woIBw]hn7D qBw_Wy9|y@/B0e(MԯNDJ++2a,/$nECA6٥D-ʵ? dXiJF8,nx (MKoP(\HbWϿ})zg'8yV#x'˯?oOz3?^?O?~B,z_=yǿ~xPiL$M>7Ck9I#L nꎊ)f>\<|HL|3.ŅzI2O.&e>Ƈ8qBۙ5toG1sD1IB? }J^wi(#SKID ݠ1eBp{8yC]$f94^c>Y[XE>#{Sq c8 >;-&~ ..R(zy s^Fvԇ$*cߓqrB3' }'g7t4Kf"߇ފAV_] 2H7Hk;hIf;ZX_Fڲe}NM;SIvưõ[H5Dt(?]oQ|fNL{d׀O&kNa4%d8?L_H-Ak1h fx-jWBxlB -6j>},khxd׺rXg([x?eޓϲكkS1'|^=aѱnRvPK!_Z4xl/worksheets/sheet2.xmlMo0 )Y7AmP@hϲLB,ѐ>^b|_o;۰ATrNca\_?'7\t#~}>߅ 2":v%D5X؂UR_zPp6b.BXe + hؠ[pqxhTCmpY} *۷%Dn3WOCfWJCrF{ X)KDօ zٙ2w3. ΫC^q<)}8}zؼ"d*yZ@M`:kjU748ޛY\I9O&w{9E"'I2'rI|]UZ@aشqӲVWJ|sVף&|jV{PK!_Z4xl/worksheets/sheet3.xmlMo0 )Y7AmP@hϲLB,ѐ>^b|_o;۰ATrNca\_?'7\t#~}>߅ 2":v%D5X؂UR_zPp6b.BXe + hؠ[pqxhTCmpY} *۷%Dn3WOCfWJCrF{ X)KDօ zٙ2w3. ΫC^q<)}8}zؼ"d*yZ@M`:kjU748ޛY\I9O&w{9E"'I2'rI|]UZ@aشqӲVWJ|sVף&|jV{PK!^lxl/worksheets/sheet1.xmlQo0';X~Ol4$F*Zn1Gmd; մ4)/y|;N7+k29%`-gٚ)Ec desES&fiFI(fplPL,ruRU"arEG&enUS&daDK.(Perfil RGB GenricoProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Allgemeines RGB-Profilfn RGB cϏeNN, RGB 000000Perfil RGB genricoAlgemeen RGB-profielYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&ltExifMM*>F(iNHHCC }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|><*t-Z=m464)I]Գ6K3rēUdO/?5ݟzNZ;JԵ m,Y 8yf(A4r}=k^uiᦩ \i~7lj|CZzIC"^j1'w,mdce{$?:g_#R?׃??3?i߉hχ:~i>5O>CoPZ~.}YG7~"0[мS d8)_NŸUDN@[|k𦎞W_ ~ w:rxQo;[+mSZ[j7[d+ۘтz)_NŸUDN@Gm7nt/: awVQ5&öt}>?y-ījtuEknf W;5:g_#PxSû˟x^};Ktm&%jN-Z@kaKF|=_¯'^ wKj%ᧃ.'dJ*#4pm<7 qP)wO|MǾ 'ƭkZ٨x_c /%/leSHLcxĺ^'3}Cya,:g_#P W;5yO?]/rj^'vwX\=Tai`cqQ#U@:g_#Pߵ Jwo-Q+i GG߄osW῀F /KY x7cvOu:LzOu˨ :g_#P%AG!G ;g!IEyu umWu=OoBCo/=uwuq, ,M4$ĒMz_)_NOۧdόx7:/|'hz'/-{rzޑh#oISˈ$݅zo࿅6GOe?w_o⸽W{HxS~JغkMN o.&K=¯'^ wKj_ڗ׀57|u]~S5/GԴF_X[“]L4$R*#4> aO<3CsZYt=3V:e-, W&KOu$'ĶY v.޿{7b |-X|Ik:"fQɶ{KDceo+O,o=Ā;{s%|gWDZߪ9#' LŏxGW'>$j>j"қP73oj燎pOtZ>h_ċ=xZ]O1]s2Ҵ\_h|aiZ e7S6?ɖ>xOm~]gK.?^ƚOxQ9#P{߶ G.OkÍZ}i~č}͂T4 QM\˥j}Xg[M-?_x>E6 QHM#&39$vO:oN@a|m]3shtOXjvA7ZE76H4Nf9ݺ)`|4l~&5;_ [PE_[nP 8*$xh_+O!6Ɲ_fD[sOKRW[Fso >4G/|Vl_-xOacI<[ߊ-RRmdքjz~Mc|,#\r_V$wuNփ;7pjqjsވnQnmNjM=_č%Z\i׃F-&{hne3px 3˚?CB>o Џ?{?#6.h 3˚4k^_xxw|#ThvZ^.ͫo4TFʹ)߂@{?#6.h 3˚?CB>o Џ?{?#6.hkŞ(v)xqv;7LWR^&%1H$ 3˚?CB>o Џ?{?#6.h oCf9kz~GVxú2ɪqRUu*ɫ>`"Ƞ0":oЏ?{?#6.h 3˚?CB>o 8ι7OE7KHd𞼒IzHѴA,t"_SExĚNj|kдM?Oҭ$>>"WoxKa4ۙ#O &+ed(ː<Qi0Qi0Qi0Qi0Qi0\xL[ҌkOF}jgTrG#(Z?(Z?(Z?(Z?(Z䴏x}|Y [҂I9 ,>fia {H{H{H{H{HG#vؓ[ҐU"Zdq~ܧ uxsG -xsG -xsG -xsG -6Op#%XFu4#~]oJI#抒#_G]:22YNC)R hß?akhß?akhß?akhß?akhϾ,xAe ִ:qE$*" ;TX$gŸ"ᗀo/|[]x{Hi4viYy$fwbY$CktHKxW> |M ; ?īo kZ#:5lt\|@MNMԵzӵ5?Lռ>O iy y-.W/WhH-Q6x *Yʂ;m#Zo?ikƨ? HV6jm#Zo4+"??a🈜حDžb<>:m#Zo?ikƨ? HV6jm#Zot'J>-3O*oj+bum@'{]+M_5@6@7mGTci P+M_5@t)5f|_ ڪ~UP_+M_5@6@7mGTci P_G9VG {@ǁ*OxMLӝz#35fceXI9$I4ci P+M_5@6@7mGT./%MD$K;utae`y  hxT|>':~/I4 φwgf| omslRhb*\ข0v|=bj_~($s4-Lĝw߳ܺnɩkZZ}N ͮ<jG{4w\Awms/^lS$i/>Gq$xQYXxE;ʒ DcԿeCNP/t%&撀ӣ)74gx I;_4JM%r6TJ3 _F%CWx?ceP|7 U/t%&撀ӣ)74gx I;_4JM%2GRoi(t'tE6CY$͝痲?HPd6dHH`0Y/t%&撀ӣ)74gx I;_4JM%2GRoi(Ƶ֌>%ңŞ(YfYwLЖE#IX0v$gx I;_4JM%2GRoi( :?I@ };;>)1 L0s '_zVĺTPtf'ijVs}ŷL8o_>8xßk~<+=ů5 5Wj]Zk[W$"m'Vx>_4a':4~ fCpAq1T1+g+?Х_ ] ^%HХ_ ] ^%HХ_/qk]&oED|/ko P+ "uQ ] ^%HХ_ ] ^%HХ_ ] ^%HrW>-1/f5̋˴U ] ^%HХ_ ] ^%HХ_ ] ^%Hr-6 W≉"ˬ90y WyB?@%w)x!WyB?@%w)x!U+DKʑBz:y|9]A B<-)~ѐK&9ӭLA(/$?~J?R/C/$?~J?R/CK?j^8<=|)7u9>'k~6dž? G^=>-7>ेRyo#8,BHHPXO g&;/ݷmm>g?v/*ݷJ xnjg pN;P[?T}~_Umo@lP?Tixț||5ǃ;m>g?v/*ݷmmwh-5[ńfxA< A@lP?T}~_Umo@ ˓.?< yh< nAxi dx }~_Umo@lP?T.[w%[vfQ4dtیdzɠ9agNڮ·]HOY't#3%M|'k<|G: փ/ xz-:폈N\|Wˍ3Ρ{sk=>X-mڀ?E~2.$Oi$xgrB|Ѵ!S4n zg"wF} л]ѿ_kC¿./t!_to.<)&&ehmM_ WeY$Uc} л]ѿ_kC¿./t!_to? 7 rzO<2+|M QxpƆت, ^lu f:D<+B_7@"wF} л]ѿ_kC¿./tx?¾[2:L<[☐-f#AT@Tp л]ѿ_kC¿./t!_todG?q}3k DI${ fwwӭٝǖfbY$I л]ѿ_kC¿./t!_toݷ?\[ZT-zXf$DӮ$GX+4 4~(쮼1i)-䰁wvAXѮng!s$1,@<^(>7x퇎-"5OjZ:^h^,t`$~cZoIvޤ70{x/:v鵿^)֬- ɸo xH{?ٹ)!# y "0\{PφC>nG|O|g??q?f{PφVBI> bT.1.<>",Fn6:pC>nG|O|g??q?f{PφC>n9='BonG|O|g??q?f{PφK,[|[ȍ|;)]|N^McOP=?C 7@#ڇ>'|3j83t&>'X=⛠k:ž#?tVDN"8I&*; ͒@:G|O|g??q?f{PφC>n8 {3W)k{:pLR4'|MI"h^Oi-G->ߦx{Iӯ<>c2WѼ?ex[u+y4 YG5_Ě7!PPEy+J0k?YKy| x3ok%XMlS7U\,oZ?,oZ?,oZ?,oZ?,oZjc?8H@oڭ@ڭ@ڭ@ڭ@ڭ@so w|5,3guj?Uj?Uj?Uj?Uj?Uq gg,#2 wd;[?*[?*[?*[?*%ͱGhomj9ۏxD2 Ķۯu4}~ T}~ T}~ T}~ T.-w%Yf>JtˎݒO`9&<_:ï꺟9{>/o.,#{> g/$;M|-+XOu?S o&B|)sx|?ktVUuW^ig4]RI~x[mSxk9fKY|39d9HTPOi Pg?,@!xS_Oi P+q 0 M¾!iW#YVIր:C<)@ /c ? 4(3Ÿ? KX€C<)@ /c xeWCZ8a甆2Z^3sO KX€C<)@ /c ? 4(3Ÿ? KX€9/K3Zf]Mr-LJZ2V8uQ(3Ÿ? KX€C<)@ /c ? 4(x7¡KV?\j O 6,ѤG,ĒII:C<)@ /c ? 4(3Ÿ? KX€<ⷄ5kǷ6& xK^E9SNd +ÐFh4+K~O7hZ?mׅumqt +iڦowl[19Yj-;_u[?~|k7߂7>:xKG_K/}sź3A[>+ _UX!s8x[❎ ukxh&7Bu+'Q_/8iM+Lh_Zg@?p?"??WC)^'>*j ȑxw̗N>hk;_,/OJWC&E4c-38iMr~ҼNו_xbiEnW#|ފQ8iM+Lh_Zg@?p?"?k* +0 s ҼO'<,xwFh[C[sͰ|ؖlncWC&E4c-3⦗XKzMo4y+'Ιܿ0V4 c- h >*ӬnotE..c֡DSn٧;%I>y?f__? |9|Eu?_bf~ο%|0 D kKR1kzV}y;@ˏ=: <"I^")?4BӼbYV3Rz&Z_x _L'"miO%|E?2ğJeϗ?+hn|o?_ ":pV#yFU(QYH:L'"miO%|E?2ğJeϗ?+hK/+/V'L_SiС|3#q<.$([ -/|I[@&Z_x _L'"miO%|E?2ğJCźl6kO1xQg_Yu cӘ$I$muVPY >^$W_-/|I[@&Z_x _L'"m5ep,I?URChWt<X^%cLj%ӭ),zs$qtfGeb$K/+/V >^$W_-/|I[@&Z_x _8+Ӯx;M}dZHo]YYce#}ϫ~ɒ閺?5Gvv:?nZ׆+44F{_k#L꤆D{ /ouş F>>2.|W}r_r4 ߉ZS~xe?UFh (J?%N{gg¹zP[@P@~#^a@P@A>-#=>; (&J8 sx+ #ZΛlh (ξ.-bwrM|/[~'BV mnYG?Ǎtq)- ~ۨdCyVYdS;Q?j?@ >YG ?]OgKSJ?^-c/ei9[q9 ,vt?.@|'@ڏ%;Q?j?@|ዋ]iKW7PbY5nvI9 ,vt?.@|'@ڏ%;Qk e7ڏ`O?t/& K7ida{.Ogl Ixq@7 >YG ?]OgK ,vt|TWN1[xO^>~%NdmtXՔ`FAg>CxOO5_=_x^IK-Cú/nEumcE;>~Ծ/Ïٹ|=jPֵv Ju[ٵMU\q6a~e^5#=B.H824._g'=g'=g'=g'=g'=rx}(S" (BՄB/ F]g1J+FՐ?-?'i?-?'i?-?'i?-?'i?-?'i`%^. x\L"͡jgwhьK.I*t}$H.e ;@O[Ok[Ok[Ok[OkV=7zd׍v q,3K㔮B;*`l2š+Y|!7a7uB/~1 5U҆<0.VmG-JeW0;Z6Sk7z_> xxִ߉7KmHz ='?4+GO}59NMRu@~':/<jx|==b=cNKVHwG3 Y2?g{ _OxO i3=V ?c{'A>4Q|Irla,:+uwh#:Ⲻb3` _OxO i3=V ?c{'A? u?[@G"5 |Irz"I[\q]TH2L0,o B!GA DFt̙Vۂs9jBQZI F;Vj?ClOht-c߁+ Gt :V'$ iNfI9ՄL Y#О\8|+,sS4 o~p57 16J+Sbdmۜ<_Y[ e^QaUPK!<docProps/app.xml (n0 @VzXI&ӶP[2D6m$qɟé#Dtgb>KEކ2׻"A2>7uP<6"9- C| 7EjG]] N@@HlE #10<3H_>cO8#RO_I0n4݊9瑪?2sOB}{{GjTtIPK!% rxl/workbook.xmlSn0?^ǎQE4mr,@R%C/Zhvz-ɴJ81IU߿m %"|P4nA`h131 ZiA,zN3R1+ ,K|dXPu)( &DXo\5oñ#D {l1j*p ثyMԯNDJ++2a,/$nECA6٥D-ʵ? dXiJF8,nx (MKoP(\HbWϿ})zg'8yV#x'˯?oOz3?^?O?~B,z_=yǿ~xPiL$M>7Ck9I#L nꎊ)f>\<|HL|3.ŅzI2O.&e>Ƈ8qBۙ5toG1sD1IB? }J^wi(#SKID ݠ1eBp{8yC]$f94^c>Y[XE>#{Sq c8 >;-&~ ..R(zy s^Fvԇ$*cߓqrB3' }'g7t4Kf"߇ފAV_] 2H7Hk;hIf;ZX_Fڲe}NM;SIvưõ[H5Dt(?]oQ|fNL{d׀O&kNa4%d8?L_H-Ak1h fx-jWBxlB -6j>},khxd׺rXg([x?eޓϲكkS1'|^=aѱnRvPK!_Z4xl/worksheets/sheet2.xmlMo0 )Y7AmP@hϲLB,ѐ>^b|_o;۰ATrNca\_?'7\t#~}>߅ 2":v%D5X؂UR_zPp6b.BXe + hؠ[pqxhTCmpY} *۷%Dn3WOCfWJCrF{ X)KDօ zٙ2w3. ΫC^q<)}8}zؼ"d*yZ@M`:kjU748ޛY\I9O&w{9E"'I2'rI|]UZ@aشqӲVWJ|sVף&|jV{PK!_Z4xl/worksheets/sheet3.xmlMo0 )Y7AmP@hϲLB,ѐ>^b|_o;۰ATrNca\_?'7\t#~}>߅ 2":v%D5X؂UR_zPp6b.BXe + hؠ[pqxhTCmpY} *۷%Dn3WOCfWJCrF{ X)KDօ zٙ2w3. ΫC^q<)}8}zؼ"d*yZ@M`:kjU748ޛY\I9O&w{9E"'I2'rI|]UZ@aشqӲVWJ|sVף&|jV{PK!Jexl/worksheets/sheet1.xmlQo0';X~Ol&$F*Zn1XNB51MK~59u4 hUS&نE4p6%>{p'Hh\B+ۈ1'+M )chKZ "t#!0LQ( ;#?B,£~WM4-iav&nZJS+}w{.ZIk)qlzy˶ Ii+tЗX(zPCqkE 5H91'}*}j$Lr_!{C!a@MFܝ"9lJpDZ& Ƨt]nV1;,MGP+JxT#5QQxge{exDž1~ g'9Z2ݭlp܇|sjA'a8ihF^LÂ=W)ۄ2X?JPK !rIIdocProps/thumbnail.jpegJFIFHHICC_PROFILEappl mntrRGB XYZ   acspAPPLappl-appl descodscmxVcprt8wtptrXYZgXYZ0bXYZDrTRCXchadh,bTRCXgTRCXdescGeneric RGB ProfileGeneric RGB Profilemluc ptBR&frFU(zhTWBitIT(XnbNO&koKRdeDE,svSE&zhCNjaJPptPO&nlNL(>esES&fiFI(fplPL,ruRU"arEG&enUS&daDK.(Perfil RGB GenricoProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Allgemeines RGB-Profilfn RGB cϏeNN, RGB 000000Perfil RGB genricoAlgemeen RGB-profielYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&ltExifMM*>F(iNHHCC }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|><*t-Z=m464)I]Գ6K3rēUdO/?5ݟzNZ;JԵ m,Y 8yf(A4r}=k^uiᦩ \i~7lj|CZzIC"^j1'w,mdce{$?:g_#R?׃??3?i߉hχ:~i>5O>CoPZ~.}YG7~"0[мS d8)_NŸUDN@[|k𦎞W_ ~ w:rxQo;[+mSZ[j7[d+ۘтz)_NŸUDN@Gm7nt/: awVQ5&öt}>?y-ījtuEknf W;5:g_#PxSû˟x^};Ktm&%jN-Z@kaKF|=_¯'^ wKj%ᧃ.'dJ*#4pm<7 qP)wO|MǾ 'ƭkZ٨x_c /%/leSHLcxĺ^'3}Cya,:g_#P W;5yO?]/rj^'vwX\=Tai`cqQ#U@:g_#Pߵ Jwo-Q+i GG߄osW῀F /KY x7cvOu:LzOu˨ :g_#P%AG!G ;g!IEyu umWu=OoBCo/=uwuq, ,M4$ĒMz_)_NOۧdόx7:/|'hz'/-{rzޑh#oISˈ$݅zo࿅6GOe?w_o⸽W{HxS~JغkMN o.&K=¯'^ wKj_ڗ׀57|u]~S5/GԴF_X[“]L4$R*#4> aO<3CsZYt=3V:e-, W&KOu$'ĶY v.޿{7b |-X|Ik:"fQɶ{KDceo+O,o=Ā;{s%|gWDZߪ9#' LŏxGW'>$j>j"қP73oj燎pOtZ>h_ċ=xZ]O1]s2Ҵ\_h|aiZ e7S6?ɖ>xOm~]gK.?^ƚOxQ9#P{߶ G.OkÍZ}i~č}͂T4 QM\˥j}Xg[M-?_x>E6 QHM#&39$vO:oN@a|m]3shtOXjvA7ZE76H4Nf9ݺ)`|4l~&5;_ [PE_[nP 8*$xh_+O!6Ɲ_fD[sOKRW[Fso >4G/|Vl_-xOacI<[ߊ-RRmdքjz~Mc|,#\r_V$wuNփ;7pjqjsވnQnmNjM=_č%Z\i׃F-&{hne3px 3˚?CB>o Џ?{?#6.h 3˚4k^_xxw|#ThvZ^.ͫo4TFʹ)߂@{?#6.h 3˚?CB>o Џ?{?#6.hkŞ(v)xqv;7LWR^&%1H$ 3˚?CB>o Џ?{?#6.h oCf9kz~GVxú2ɪqRUu*ɫ>`"Ƞ0":oЏ?{?#6.h 3˚?CB>o 8ι7OE7KHd𞼒IzHѴA,t"_SExĚNj|kдM?Oҭ$>>"WoxKa4ۙ#O &+ed(ː<Qi0Qi0Qi0Qi0Qi0\xL[ҌkOF}jgTrG#(Z?(Z?(Z?(Z?(Z䴏x}|Y [҂I9 ,>fia {H{H{H{H{HG#vؓ[ҐU"Zdq~ܧ uxsG -xsG -xsG -xsG -6Op#%XFu4#~]oJI#抒#_G]:22YNC)R hß?akhß?akhß?akhß?akhϾ,xAe ִ:qE$*" ;TX$gŸ"ᗀo/|[]x{Hi4viYy$fwbY$CktHKxW> |M ; ?īo kZ#:5lt\|@MNMԵzӵ5?Lռ>O iy y-.W/WhH-Q6x *Yʂ;m#Zo?ikƨ? HV6jm#Zo4+"??a🈜حDžb<>:m#Zo?ikƨ? HV6jm#Zot'J>-3O*oj+bum@'{]+M_5@6@7mGTci P+M_5@t)5f|_ ڪ~UP_+M_5@6@7mGTci P_G9VG {@ǁ*OxMLӝz#35fceXI9$I4ci P+M_5@6@7mGT./%MD$K;utae`y  hxT|>':~/I4 φwgf| omslRhb*\ข0v|=bj_~($s4-Lĝw߳ܺnɩkZZ}N ͮ<jG{4w\Awms/^lS$i/>Gq$xQYXxE;ʒ DcԿeCNP/t%&撀ӣ)74gx I;_4JM%r6TJ3 _F%CWx?ceP|7 U/t%&撀ӣ)74gx I;_4JM%2GRoi(t'tE6CY$͝痲?HPd6dHH`0Y/t%&撀ӣ)74gx I;_4JM%2GRoi(Ƶ֌>%ңŞ(YfYwLЖE#IX0v$gx I;_4JM%2GRoi( :?I@ };;>)1 L0s '_zVĺTPtf'ijVs}ŷL8o_>8xßk~<+=ů5 5Wj]Zk[W$"m'Vx>_4a':4~ fCpAq1T1+g+?Х_ ] ^%HХ_ ] ^%HХ_/qk]&oED|/ko P+ "uQ ] ^%HХ_ ] ^%HХ_ ] ^%HrW>-1/f5̋˴U ] ^%HХ_ ] ^%HХ_ ] ^%Hr-6 W≉"ˬ90y WyB?@%w)x!WyB?@%w)x!U+DKʑBz:y|9]A B<-)~ѐK&9ӭLA(/$?~J?R/C/$?~J?R/CK?j^8<=|)7u9>'k~6dž? G^=>-7>ेRyo#8,BHHPXO g&;/ݷmm>g?v/*ݷJ xnjg pN;P[?T}~_Umo@lP?Tixț||5ǃ;m>g?v/*ݷmmwh-5[ńfxA< A@lP?T}~_Umo@ ˓.?< yh< nAxi dx }~_Umo@lP?T.[w%[vfQ4dtیdzɠ9agNڮ·]HOY't#3%M|'k<|G: փ/ xz-:폈N\|Wˍ3Ρ{sk=>X-mڀ?E~2.$Oi$xgrB|Ѵ!S4n zg"wF} л]ѿ_kC¿./t!_to.<)&&ehmM_ WeY$Uc} л]ѿ_kC¿./t!_to? 7 rzO<2+|M QxpƆت, ^lu f:D<+B_7@"wF} л]ѿ_kC¿./tx?¾[2:L<[☐-f#AT@Tp л]ѿ_kC¿./t!_todG?q}3k DI${ fwwӭٝǖfbY$I л]ѿ_kC¿./t!_toݷ?\[ZT-zXf$DӮ$GX+4 4~(쮼1i)-䰁wvAXѮng!s$1,@<^(>7x퇎-"5OjZ:^h^,t`$~cZoIvޤ70{x/:v鵿^)֬- ɸo xH{?ٹ)!# y "0\{PφC>nG|O|g??q?f{PφVBI> bT.1.<>",Fn6:pC>nG|O|g??q?f{PφC>n9='BonG|O|g??q?f{PφK,[|[ȍ|;)]|N^McOP=?C 7@#ڇ>'|3j83t&>'X=⛠k:ž#?tVDN"8I&*; ͒@:G|O|g??q?f{PφC>n8 {3W)k{:pLR4'|MI"h^Oi-G->ߦx{Iӯ<>c2WѼ?ex[u+y4 YG5_Ě7!PPEy+J0k?YKy| x3ok%XMlS7U\,oZ?,oZ?,oZ?,oZ?,oZjc?8H@oڭ@ڭ@ڭ@ڭ@ڭ@so w|5,3guj?Uj?Uj?Uj?Uj?Uq gg,#2 wd;[?*[?*[?*[?*%ͱGhomj9ۏxD2 Ķۯu4}~ T}~ T}~ T}~ T.-w%Yf>JtˎݒO`9&<_:ï꺟9{>/o.,#{> g/$;M|-+XOu?S o&B|)sx|?ktVUuW^ig4]RI~x[mSxk9fKY|39d9HTPOi Pg?,@!xS_Oi P+q 0 M¾!iW#YVIր:C<)@ /c ? 4(3Ÿ? KX€C<)@ /c xeWCZ8a甆2Z^3sO KX€C<)@ /c ? 4(3Ÿ? KX€9/K3Zf]Mr-LJZ2V8uQ(3Ÿ? KX€C<)@ /c ? 4(x7¡KV?\j O 6,ѤG,ĒII:C<)@ /c ? 4(3Ÿ? KX€<ⷄ5kǷ6& xK^E9SNd +ÐFh4+K~O7hZ?mׅumqt +iڦowl[19Yj-;_u[?~|k7߂7>:xKG_K/}sź3A[>+ _UX!s8x[❎ ukxh&7Bu+'Q_/8iM+Lh_Zg@?p?"??WC)^'>*j ȑxw̗N>hk;_,/OJWC&E4c-38iMr~ҼNו_xbiEnW#|ފQ8iM+Lh_Zg@?p?"?k* +0 s ҼO'<,xwFh[C[sͰ|ؖlncWC&E4c-3⦗XKzMo4y+'Ιܿ0V4 c- h >*ӬnotE..c֡DSn٧;%I>y?f__? |9|Eu?_bf~ο%|0 D kKR1kzV}y;@ˏ=: <"I^")?4BӼbYV3Rz&Z_x _L'"miO%|E?2ğJeϗ?+hn|o?_ ":pV#yFU(QYH:L'"miO%|E?2ğJeϗ?+hK/+/V'L_SiС|3#q<.$([ -/|I[@&Z_x _L'"miO%|E?2ğJCźl6kO1xQg_Yu cӘ$I$muVPY >^$W_-/|I[@&Z_x _L'"m5ep,I?URChWt<X^%cLj%ӭ),zs$qtfGeb$K/+/V >^$W_-/|I[@&Z_x _8+Ӯx;M}dZHo]YYce#}ϫ~ɒ閺?5Gvv:?nZ׆+44F{_k#L꤆D{ /ouş F>>2.|W}r_r4 ߉ZS~xe?UFh (J?%N{gg¹zP[@P@~#^a@P@A>-#=>; (&J8 sx+ #ZΛlh (ξ.-bwrM|/[~'BV mnYG?Ǎtq)- ~ۨdCyVYdS;Q?j?@ >YG ?]OgKSJ?^-c/ei9[q9 ,vt?.@|'@ڏ%;Q?j?@|ዋ]iKW7PbY5nvI9 ,vt?.@|'@ڏ%;Qk e7ڏ`O?t/& K7ida{.Ogl Ixq@7 >YG ?]OgK ,vt|TWN1[xO^>~%NdmtXՔ`FAg>CxOO5_=_x^IK-Cú/nEumcE;>~Ծ/Ïٹ|=jPֵv Ju[ٵMU\q6a~e^5#=B.H824._g'=g'=g'=g'=g'=rx}(S" (BՄB/ F]g1J+FՐ?-?'i?-?'i?-?'i?-?'i?-?'i`%^. x\L"͡jgwhьK.I*t}$H.e ;@O[Ok[Ok[Ok[OkV=7zd׍v q,3K㔮B;*`l2š+Y|!7a7uB/~1 5U҆<0.VmG-JeW0;Z6Sk7z_> xxִ߉7KmHz ='?4+GO}59NMRu@~':/<jx|==b=cNKVHwG3 Y2?g{ _OxO i3=V ?c{'A>4Q|Irla,:+uwh#:Ⲻb3` _OxO i3=V ?c{'A? u?[@G"5 |Irz"I[\q]TH2L0,o B!GA DFt̙Vۂs9jBQZI F;Vj?ClOht-c߁+ Gt :V'$ iNfI9ՄL Y#О\8|+,sS4 o~p57 16J+Sbdmۜ<_Y[ e^QaUPK!<docProps/app.xml (n0 @VzXI&ӶP[2D6m$qɟé#Dtgb>KEކ2׻"A2>7uP<6"9- D욉EickXݥ7,(fD)\/ bBJ6ʯGF1,.4 [-e]PjARIҿzЊC X\,?qB<0 g`ismFpbF3۶]` |0jjlV*` 功/oplO*DjNSɃkpk]Б$fih*k^ՀM27PyX̄HE.j)|hFJ\W.%օnEͫO-PK-!{ZgL[Content_Types].xmlPK-!}T  _rels/.relsPK-!Gxl/_rels/workbook.xml.relsPK-!% r xl/workbook.xmlPK-!0k] xl/theme/theme1.xmlPK-!_Z4xl/worksheets/sheet2.xmlPK-!_Z4xl/worksheets/sheet3.xmlPK-!Jexl/worksheets/sheet1.xmlPK- !rIIdocProps/thumbnail.jpegPK-!„d bxl/styles.xmlPK-!<ddocProps/app.xmlPK-!u"sgdocProps/core.xmlPK ajopenpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/empty-workbook-styles.xml000066400000000000000000000026671224514475200273670ustar00rootroot00000000000000 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/merged-ranges.xml000066400000000000000000000032631224514475200255660ustar00rootroot00000000000000 12340123464646546464610openpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/null_archive.xlsx000066400000000000000000000001761224514475200257170ustar00rootroot00000000000000PK r#>null_file.xlsxPK r#> null_file.xlsxPK<,openpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/null_file.xlsx000066400000000000000000000000001224514475200251770ustar00rootroot00000000000000openpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/shared-strings-rich.xml000066400000000000000000000017151224514475200267260ustar00rootroot00000000000000 Welcome to the best shop in town let's play openpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/sharedStrings-emptystring.xml000066400000000000000000000003211224514475200302410ustar00rootroot00000000000000 Testing empty cell openpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/sharedStrings.xml000066400000000000000000000003511224514475200256610ustar00rootroot00000000000000 This is cell A1 in Sheet 1This is cell G5openpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/sheet2.xml000066400000000000000000000061351224514475200242410ustar00rootroot00000000000000 10.0120.0230.0340.04510.0560.0677.0000000000000007E-280.0890.09100.1110.11120.12130.13140.14000000000000001150.15160.16170.17180.18190.19200.2210.21220.22230.23240.24250.25260.26270.27280.28000000000000003290.28999999999999998300.3openpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/simple-styles.xml000066400000000000000000000031171224514475200256560ustar00rootroot00000000000000 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/vba-test.xlsm000066400000000000000000001473121224514475200247620ustar00rootroot00000000000000PK!(>[Content_Types].xml (U;o0 ?\ N(,gHұ JS'1_ E=JrҨ%7Y,˫F5 fS_/$ +k {j~ d6 WƂ؂Zw*5ob7.LX<&Z@UnsdTAsJ (MIumUI(kMQ&ՀIqy"҅OX3oWĥ u5*xXrϟ켗%dw\cYؓ;zeh(j.5|;ĥ9R }\̐<08lɴ)Ik?n q sڎtJy=?3Qv0~D`s?<m>WBDuV3tok* ࣄQ5D^f?z1HϹ^`c `_5aF[0y{V |tAPf텼PK!D3Y _rels/.rels (MN0FH!APnRvpIj5Xc" vyugC4J1 SkJ`xl/workbook.xmlMO0 H(Ҵ4c!6wH* 8rJ~ W49c)TfWЧ:R0RhkdYb+AChgql #uh][B 4:NčPV*ږL!ؾUb^) DDhD n Ѵ:2Jܾ]h~)A`uY2\q5#lՊ-?i)oDB%:lPc|'>+/7a~Kz?ZYtChdÌw jWN~X>3 :=Nƥ{C-fd2 ue?'Cb'q/PK!Ќ |xl/sharedStrings.xml A 0@ѽm iڝ' I 7d.o5$= (r\O[[¿#C?ۀ> `'-ͫVj:@H{U]^hSQ]SQjaa/rV5М] 5Msy#@8L%.o+h)7ڢ~E* p&`sGSyO\ x̪5!H̵_b E7=\kʬд.Ꮾ/r!kC;!!y !_?e\'Gȑ8!RmĿOSIN$)|5*;e;}>L-al'9ߧY*$QJ2T9]p|F`F'<P &&&nLh+`000@:ffs倹;P @u ^XP ] FIkB p+`9``%` Xp`-`N] h~@;`3:!@ b8 pp?`v@AFC*c00yeݵ "&EW(#5u/_~c"// r"3z!+ڟ,A l,S8. eZef/VL,{\Sʴ2 ).Ԑk1Ch!fAMqVR Gn覲T/ub%бXp e6'0Rã5vMAsIPEqF>P7䎲FHْp "o&hd'pv_UID!|2`:sy:=%\O*uL*~5~[C,h'#HV@%czJ2/34puh %DH((U\׉ MCL:`R=sy0(2zL5ه gČM^g/,3\)W1Ѩ6N(b$f8 9PQQ_9t\'cmǣM]qk]Db+>_gԸ_iaќbE$nVwm_ o*Dm^s0~%3k>[L;Qk]yu s8/aGju]x37XotCqs4y?O,$GO}52- ޽m)5Mt'&Z]rJ+ZJmb_W[)oa~HQ-lv݁jwb (0 9w-4{#?k,ǻ] O S:9VX ]ә-ꍷu0UY ~*$ctţW[ktpn[Вh7%X8Y+knXswkrڢAۧf} QA&ܭPjJw81Ik1wmo];Brty1pY[7rG DAQ7'$;:~,Z"ϼV\`F\)9n$`wM=՚w]&]Oa=<:g2N\8;$S6u X/2~-(2-aMDaW3؟%:|yѴL;^cD|<ގ震~ (8YFc<1Ecv?)r72FH=^*.u>;/XWaS`Jů?ʻhpwܓ075 1I\8מ;pwI#ɷrVO ,jU@X0:MS?~ZUg$=eRt HXL0͈iYF*n1с;T\zffA?WY7Bp]DLa}n6R_I4z~: H8o9lE$㗷QC6ZۉXX 26J\lDL) ͹%n%1ބC?-VK:յNDH܆ {m~Tn$x1&3 Q.)hD=p!֫9@ c޵g0-"tB2# I%F.40Y 9oiJU!*_~sCST'arpr0F@ \%ݫl+c W=4epVV0$Ϝ1urp؝֤u)l} 0_ HaIa֝¶0 9!\hr<}K~L^ie>8'T7~4sq>raG1WJwv[kcdF܍nV^O.rķ Ex:م+p{̼ sLٸ(7v.#~ 9wJ ]kتmL;K&U,ùz^˧SR&əZG>@O4bܫ #ZaL ;ha2V [}>鲶N>)P"| ;PiIϓG2'=G'E(%/IW.D7LEdhd~ 躝ZK􋠓rX<{ļӭܥ߃Ybs9wq96FH)iOOO6| Mz߹47x h:/Y5; i-q}A?F4:_V ɝLra JաR:ۙɬ\ n:@#}K/4Xx<|TWG>| kAhPΨ~軔i匬jCy$zANzV@4=I4ҫT{D|{ez-e"P\7D? NJ _?ғw>WqHQXqʲFՀa|nШi|wyC_|c#\sMz}Rgk)tjJOFhP<Ӄ.'=Z]y(Ph]IBoFNc}ѵ pE@8Ai_ګ忉:rdrd7^t2:N.ݑێ{j.pApr:{G> C҄xRn;x 6tEK6+Ћ]Й&Xt*!f:^+2Z쓸 [S?ul&<_Lñ8YոYIz5I<cn4|-lxt:S=FOUvZd3/Cr.%} d cOdn6ls#H|$g'8` 'z̨'4٣s&":w-@G`'%?s |P4n+¹CA_bl5S MgCsnCx%-`g^lEޠ'6fvTMܒN$WEޣ~\_ҩ]%N8 &}4v%]'.yIH:I8ztIb,oc hwH:ՑF$NuPݎxpC'> p.v~A;_NrhA444>I'3(pEcvZ|kGĩ~H:'ԏ{\%ϧ,5s=mNzH\'qҳC^s=$GISzy>E,\>!_MN#invuDf6ւ7}C;yh` m*pla'dԟz.xCK,]0GHL+H o|\0sƑ-OGTt.s'G8]K=\|<"T~lQAcnV9}wV~ [ѧ(^K[rĿ/-b14W.4cxgsm*hWԛ^Ju*" C TfRs:NiH${S{#H?i,:s؍_@K{S}S͒OvH=<ݝJi:t`EHN񠌱D![d1lM5ۆc G K*TB{TUcƴexS }h wǯu>EDD//1E~ S%E?nu&[PCT_oDcΰ+XyP^e>냌ZxZO=rT{J,e왔?/qEQ߳CZ5ܕ$FPhpyW3qf6)t҅zaM8 ^{@V"GcO=hksXˇS|e1CoS0"٣ߡ%=þ#۩k^] |L/x&VEMeJzYHދd0C1J0М%r#g4ڐ]~K,^~{*;pAyi'1Q=ѻrkryW#ʹQΟ*wy}Q{1% ~}nD* C&H1O# >| .#ӋvzDS?b/ 7>iO<4|3a11]t 9g2#죡u0 sK7S=QO.C}_e&{D('t%P?ѕ gd|18#ߦZd۷ _=ѼRq+9TCǠ7ʉ@5F~N~Ji^+Q>4jV)Fgr&+Kp6^3]bd /[|p`+V7Ski@}w;[ZM/6$h}!Or*!ӂCVr]\ tK_}6;qV-Xh;Lӗ\Xj7q#ԿOsg2VF3C!56ntǃUښK?^‰>~\5,pc<>!^lE5蟿yi_C؍A~q^Lvj'WkS1jv;n`=yv|Gߐ *칋񬂱*w+7lq|#:^|]1;QPVhW]ؘm`Ę&,\R]iԘrd6eghߍ %;Y~hxsXd9&6;cZsM*0^6]-x]NƮPۮ5yYleP%z]R-Q ~ohtܮ5߄7iFo0F6YȵZs^'Jso\Y=k~TSW@"֨w.ZO[ߜ8{tOf _5:W2K5f2cAh+?G'+% _mu99o?1/m)73,_x?d~ngΎjZ9jΛ˜+viߕPl,zXVܛ^mx%'_)|1êuIjh+|:kB;Qm u+V¡LUFfDŒxO͸0eU\Z維WK#s5-=XfFUQ|X^BZgp.~qwvh"WJcҴW>g ]εIC"5wGkIwɇ||Ύi}*gj:B%Ju?(;sf?UckK2[sϬs䋆g ˲Z'L,y8WbN~'Cl<7cjCN쓆{o6~0$a>'~o'ntۗ~.=uU\r6l٬-;~C/l޵jUOlݩʖŧ}tI7_ȮT?yeeGOƞe(`_~ ϹnSMOEZ-0^0ZCmmf|l -0ý}+1Cw+`$H7įbxjEsYN3+_ \0a&7)J5̧J㫞}Bu&'N?ǤD]5/ۿu.[=tkӿeo |yF~mROjΕv@iI~7.Sj9n.6f)シ //x3g?xǔc4 FZ۵0wv{M ޹U:xflUN(ʘssWo$;7fؙlmЊ+,}P[35Ʀ|q624LVVLt=ZK'5g7=TcZeJY+PC靛,񧌳ww0kmwfkB^Q3eeHse'2 ̭7ެ 5[ǵ{U'f_]p;_v׆Z?z㰒YoTn#+UUWZs0Џނo+(ߓDŽb[d(X0exoRHx ЯXSLěql]NAB&bS.NF.%ֿ /,ߍlAR'} \|-eT HysR5*!xXfǟW߅p_Piʁ ]D}I 8b] ⇯̃%rGjh}O(3 J'( ~;Ux<HkQjxpzhBϸЗ7*ѐ,r{np׶I q=  &xLgfwie-Aw~)#/?,bW2#I9/-&S:O׌N?Aʓq@e=Rd_ߓ9gI}+dqd!i=3[ȼ*Z_(8%.SlU\N? D ŧ ,ǎgE%oj@OI䢎d1ѼI陲TJBpGC^] 11dMx4RDB&.Pwp{"Ic$&5BW0ۂɻ;„H^ -N$>@^h=q怰-s=IQ<^b$m~T H #)!݆kP_a2>M$c!3]21T;mYIcY os˨e)g%_[oX`Kqylz0U XZ$QmCFKO3s|lIzYRE'|l/w/Q U6+`({}ԉ# Pñv{MT*]<'fbN*jLg屇bx=`EՃ`쥴ԃdwlHpueRc2[~%'ULaGͨ> _VUZԓj=iBF 0bXZ.wW4ȟ{cۤ O4Lk6{i2=q) 󾐦_1%hxe.SOe_;'i-UXZx AlV{!Y{ٝ_h:U/> զrZʒ{YPve<ފv.+^ʲO_ĕsv,Ek`okglqwe;׼Li)޼oU^?+ JI+%U_ #Ve'\DdRTP*˸f?;tW&KݺgUV&zu^['l# ЃBeHA}BAݜuR2e*H/Z`mFeRv-m;Ÿua [x`J^l2 pQa"\J=ϟs3#xp/cJg5E xƏ_qoS&Ú[x{7ip䔠&G{+2>\Cs]% %ұ>^ >.YCǺ8%kٿJztpZq~k@GG7ڡW&X00fe^] HfBzk(uy?_[+~yk>xg{]趪lR%V~KB}wK+vACF0X;thҗ-1i5s*:$,ʂ0]:x$5Щ1܄PuBNlp.1 3`#ҶLaf2hӚ%V%J j@Sv~"Ր,$bˆ\ϭ*5snCkcq=)5c\ndN1'TY )` 3gaf|>Y2Pg6y+0 nZx3o9^jpPV%p΂ϖjH &~Th?rmG] r|~r.ͽq7$l!b >+2|w)c~l #}uŎo@\Þ6#3' 䇳_2z$0j~:TآlMM֕^!lS^Yn+c@źptpwd, rPhޢʞ*B po/m6(qxJ_ N:EG>:\@=0;[Nq;iA{fŻv7AnZ&h6^ocO:$ke0e!x^yB[AP~=(t{Ogmc%d,#' rgVAT=n"Zi>|IF;|&vϧ'sH ri ~=G§ׂ~^$ysVliecDXdcxIn%d1=KDϠnu9wi?,ݜV,+ >#R2kMBB^S4I/] O[ yz wCoFduNNO{ jZ59<#yծJV45F‰ zm!CI'@&d[I~ǟN~qIt[ND׋9JG=Y|\ ޓj׾x"e^h2 ?|_eA,QqI>z>k`{Eў㜩2'PJ;49MopG]䞎tjA=?)38⼇n'MD!qL|0Ac]r73%C 9|϶Ω0Z %1\b~\z`s|*8t;iû cn^bT6nh!X?[ K56Y-kq9+(0f&rF|KKj{k5 -cE:$`ܙ`7|0?lW.DPglQ1nuAq3'N$~j.K"= t%7>qGBl,8gcڡx|'=2*!MgQ8S*k:Ƿ9c~NrIw9[14I\ 'hQpoζa.~T.m7d"נdۂ2з{{\>tL;G~jN}ASZ?E! +a۽!WRU_#AhRN~MHW2-O  zH:MfQ{;'~[aG;RiaġS׿1-6:jMN 0^ R} J%~+bV$|$Xo͊P/hypUF8ZX$uIÕ3BˈO mF#mru݁\)[z%fj=VzOZރWw!y>rrKde3׿o\86X}H߭;!i A]SoCH_ 17-%0[P~)/ _w)I;m%}!w:0-s-Oi1*N_PZYJ*fNÈw̪Ř f,LRi՘*(WӫWOi8{5uMg-0q³?3uIϞ3"FȆVlTay[[6x︿V6["(`/pVzUKlZcxˊ<.{!mBNJ,Dv^+vH3MF6:#_ո*Vg #Z^T Nnm[qLh6 8RUX/&Lk{~,nw[;,lyҟ}\;jڽ<@IKZ񵶢e>2|QK]{RҗOjyYVw[Ыacn-oz^ǙCKN:\u=WʝK׶Ml"6X'}r71oRZ]pvl/o+Ҝԉzu&U o =i9ꭅ?}kԮ֝Ǎi3܊'vYCʼ $moomj\qeu >XwU6e¬ɐMEK#`'u3tkη z9yz;`$aA5iak){ Q)q _5\fdctކk6K0M59gmZC7w =VP5egJף Tq|#K|u鄥QY^|exοՎ1۷Wߚ, -=L5)Fyk5'w^ #@:-v+Ll=CD}EAd*y㙢?{ >&Nʚ=!?NjD7=}6#r+^)Os\x!3wbLٟiU!#_;ux>]gRLio.'EoKڿE|8y*< 4[e ;`އljӢsL[)?(G*(TiHŋ$)?Sb,Tʜ"82&Iw:e.r@9!˓<"QJ3r6\hs䲵b?ӁVj _^VÖZ-:F59^?+.q #Z}N+\G'\=6Y_s؍6ɕ SK_ S[S2BTj f0$5z*5Y(k|R%-<(i}ˆ0Fk^--ٍk+t_e'Vk. Ii+%Zk"LOO_ځO>ppaKsg}HY> )xFO/}3^3\錐yHQk3K}Tr eNwtyD #=HBuHwp zP.1^j m;]܂5ھ{J["mt1]}b\Q `B YQT(/Ep2?B6iCEH Xc.עJp }:0e"82`2#hȦ`r V3fz-F!¸,9k,2±vg!1~Ȫ nܳnׅCZL 9``Ub5CDZvN<|e _h6+A;e l̦KdD~`<ΪI_z+z _8`ykvK{̓ 2>裐S]bL-Lxp?%}eLPz"٣VqTJ}'op-V||ECl| eW_< G݀Gv&[݀<x/ ?#>Ӏ7`?_?8x,e{/~90/~8x K(׀p x&?x Gۀw3{ீ @? 8k "avp:IhO6`: 6W*X!LW5jh}JBSXQ fԋgjUE#4Be;~atzaraˍf;)S\m=fۻDS.QU&2-{f(M]굯jyy|~]r=Թ Dt|*mV}r{FLE15㓗_BHs6rUG-Jf}z>Wz+R୞~*o @2t :SӳොnE._ }0dY0ڵk.$NjrWt8SfW&g:щn!9qm'"9SfR{3uj&L4$gRy8<''9SfR 5yevj&@rI-K\P J;'i< -YX+g[&b5^ń&el&]RT~$LAnG2d|_5kտ0DdW1׏@2RNr j^mv6Ǚ1cRʝ[wg—!ōH; bά\0[Y*r*0kX*fK}'vWͪ]6Z̬9=m%բ|zksg;rpQ#ήZi^ 8ڴB%0 AƺƶH@a\uS=#D4#E"/,9 i/[Լ.z=C7F%=ubqk6ˢ[׽a#?L2YclyZqefuX/joȯD']^nYh pTXϋ4:3;SsssUGolz&b:8\+,9T{4k>Qq^[Zswۧ;YdtjiC.y}l D@5*.-z7<`P}=f\Wұ^\6L(Fbuuư&UgFe1` }w %1ѩg,< 6$L("rncݎ "K~ O;L^=X]iMCV׭9~ik#B+Z Ϭ,O:*uwo;;zW}39vV/iOu +75 yrsZ׸u٦캺̺7j\imoT)Ws9r#M8 ŴY9Y;VTfs2ݛjZ cv9o|YFv? }aFDk_V 4nwҙODFU7Fm֢=3[XmqJjW?TmԖW'F_9:˞:jb'|gEH~]e. )^d][3 \ o!Лu-5O^|IaPrMQzT#˄pn>.C ?վ;N-x5ۍ/KBw8x^yK̉)BOga6Bx="8k+1⬭7`0otS4'nI{Guރ3 чTڎ`f=lJyW1!]d߯Gsw~DA>Ue Ug1\,|'\?Vr1ױA onsE%4m_.JD6F)BTb:mbZ 4ZF-Ks:lV܉JU! !PbXP4f6KZ(A8ӯw9(Ր͒0+~Mw3ѴPtc}:%Q0rfm}Gm ڦ#qAuܮ֍p\k8C۰v)~<.w?*{᭩q'od9k;̏2}Xl5P1,g~; qN~[O,Wxa/'^0//eҧs;$/p9,Swr!rOs9s.n\kK?Jv]#霺 ~)X]_C@~]=n=!=d \2>Gcep_HȩOƧsdO@!,7q:Gcu/9T7?KD!E"<3|-~5\톒RHX`6 BUټwRtP uitsS;q? ϜO沨|[ih!?q~j<xԅP^ۣ4i=B<Hg"pH6q!cv[c>EݦqҹCB#Ln {$]}Sqs@)NtUA̅(\JENHOOi&F0n ؑo2C;݃_&`C]a,1F,py\3lğ>llsk!lJN2$/D1 Y{Xcxq(l6yq ,3ҷF|p{M~F'>%'ʗa F#1_sDxA.?g=+L;3M+7P9_W,*y5m?I+?p= `rz;O%Ȳ3q63gwGOg>DZtҘ^sx=At߇ƒV#}x\Iu3FXrqmۧSjN_5g/rq]IGݻ"<,jޏzcdvg?7xsU;{CCyTj@G's^ELqyO9 p;I]a\̿VN ip;f퀏kmq\Hї",UjIfE?"@7p=K(;fCݟ?:D_zn$Џ$o' I{\t'? :B;nx[.q\?슯@{E\¨rN]7@GDGDr")V"F͌z9xf0- Y-N4>A+eZcLeV ELLO4J#8?0GLu6#$ky9~I$oLR)lݡQ{4> j<*9RE sx* l[Ce(Ŗ'_ڱ }SVL*G1%Oxby˱U'nӽ5oL};U$-ؿpF s"8ۍ×!\5JMEߦ驒 Xy$csxo[t+qcO;f6DT]L>_9Ϟxl@%ɧ_OUʃO@\PNr:凹AR rRr?MG4'Ӏ*}tqAc p l-r;|VzH-P1bc˺uxmۗFnd .qYڸfS,Ā0ڌbpp` G~ŘȺJqoqc}{P` VZ= hhll̾s風V-J1:;~ zGz`хaFu,{i1>e7ESԙe),KVilY3΋]ﺯshɼk^",s'zԃ5\K1ikזK_is+[<#f?.E|s[= Zœ/f[gLHw|skkY{}&KO r a])4)YkL%f_V_0|d||lkEգfLiͯp9;Jjeh\{zrT0qm}=V!nԗSD o1 1X9Fq;K1~v?30f1[ ۓH[D-l׎} O3Zd)/}VN,)E꣬A, 體XGnxgK~0|^ ^DgXi k$wv#se~VcE*M#)+},7RO;5k*[&+mtbpҁzcΎ bޓ},%URn #h/ps<';+)U4 o,RY]hrچSUQ$PPĢDŽ~Q6F:+ʗ8S q)`Y$yxwj.W"]vZxńGI Xhg8wal@@ 2[\XEI 1}^-I\q*WQ iPڽfK/cM(px~ v~c*(H'3{ Z{sP^N?qu2 971rOwҞFbH%& Ґ_|uP1* *q" abR$T}D Sww"KdN>2zI!\hư =ڴյ(7A0qNysqnTq^1̉ߋ9sV}8BTX{-KFU_i]c6Rڣic58#mIYO0vzƸ3} nƨ=n4drI{~D$PLy@њ1uf!ދ^uAiɜPpsC[*X:<͜h2 4I-byTspa EmH 6E$ xj]"&/3k/i Ն`CC[-wA~="߇??fX)T?61Z9_8 aZ;OZÞFǠsM$YB}M72.nc0$5V[,YPSce6Ơ^M,I{k"_~'  dlqg ֕ N3P(:=!,0Lc%@ԊwK '޲"]̑meEhoMm|<*0崣lDE3$bBYH;#k98Wڊ& ̶gIAka7e6ǮeﰗDp m]MZ}+osQJ8)ɆE!7_)7Lvl _ěke O9Z0Y[62FoSvbL֕͸p{o ej+k |J@Z"|cG@0 MLSԚB^BMD:5,XEKA+ͭmk._ݶ(\2 \ ևG݀}vݥlX&lUՋ^j2a̾ϫX[2_+l@Mh[͆ eX3/BҬj$14&',9qY>~7,6?'`r_MR! 8CTQvV O{/"Mx h2|ofMm̈́ .[x0BxNiMdXy\#Nk/8Qm&q4?oP+T#ݛukFb{2‡wFn#|kfNs:~E@n5 Հcr|i⧖Sޑ8>-i!5U3gʹj(Kb^ A׼H:X.}ʬ?3ܤΒ@6j_`c8/͓_Bi(iO|"V]1(xPFOςԜ!F#\V$ڙ[C6%)Ux^rL\3ìuY kɡVs~/U׸lj1W {Wa}ܥ*e['@GPVv`UB˯CV)D6'+Y2( >ѱ:$($}A)fv~9:!tgk"97 ExɑjY@iqxT T^EZ1gc=c hrAde+ڂMmruX}6tȍ9!?";+!zP%E0UXLbQoEm \stζr\GW'D!{q}qH`a;V7L&ѱ~|2)6"Xq ?Ancl,AdO0>4O11_bOcEGGベaGα+p3< @DCtC0(:#p9pM7ܕg!7?(2 gEcc!\fyAC (Xn p,qd8  VC C VC6Cp!P=sAfLjs^&qn`J;"1f fA83 b%!IT==S /OV߮zqS,8=5zzQJ|/L _1ol y d||V?]_?$\ŞՆR'T9F_\SşOaGl2b3/&uP|[$/q"Q_4K'mK8&oؗ%GiGyTԞ)zOWug2uR+#>c{//>.7_T/npYAO+h]wb[ mHxoߐ'm@j?qmRβP&QhIG?+w1T Wۀb[:*iԨ-m$b6ZZ8u򧅒̳+.>Coߵ8/gfH#+g>t)|2ɽX8z~:Z5WҙC2cH)cf _mG[c:ihwcݿxp]]!'>@B8GcIDcL9`7Jz1=mxqcUbaWB1ʙ: p3=0OJolG8rv3O^M'3.tc1Y@!\}^g>K`+tB]?d6k4-̼.醰8Mot1cr ND0AZa^/=>u{k}rT1<5O>ɩx'Рc1:n1vh+coG.u H&㡳Nc{U%c۬4RO`#`ax*u)Ag<Ň*i3Xam6ֵ$Uhk~JS  n>p8uh8z_e3m`a&n/58o {ABbr!JjW4&#\ªKe ?'i?|'I].BGOL22S` \n\%*N(t3-wB:*l_P2i:%4_·/_iq!JS:,oX!)+V:OGSg U51^ڝ=o#2uS ߷R5˧Is/O.e8Q>0 Y5n1bp]çjF/S|<ۂm$K7hM]6ݡqCW|_1+X Wr: )%q}RN*u%w&O4( TJq"?5M4AW)X֛.hI#^'CǺkn1\ 5Z%MA(qW!dg-nKB?yhbJ%Z-j_J,j]Wj944f* Sڀ KgD&Z:_ܙtIwro,UH.2$bȞq)ԱxQiU6og.(Yup#S,c~e+ؙjuXr_ZQdr UC#ewn_sd{^C$Rľ6z}_^Ʀj3] `).3]a0u5OumTh+#MM3Sऋ>hx׏8H=Л ϰ6mIkI5g\Lo`j/0d%SOh=>~ y_iZ4+٫%lZ#+RZ'zvuMo/]vLٴ^&Vb;һ:j`Kɣ9%r9xj\g+p5^y|ߒ`OeԈJ!L ʀW?-Veŧqc]?4@3o)2v\!'j%uu1ipgB?k(^cG)TA'h]7Pm ۶,94Ha0)qGG/=M|&eȭos ZL9J+`G&ukGx%PjB6B-2CXMtF!~fIc(6KR0AX1kȕ~/J+/΄eS&]i?bXVʩ`ct?ﳗ'zOE㌽%0rgzllX>P > 6%\T3oCQjPEJGZXh;PNu9hG9J) !T|z!UwNJNN@3ēJP}.Z/ 8NkjZo'ڿܢeWR\|Flb *?Ok_rza ha/)Y78Pu&:*N\eb!9>ƺQ6DX>Q̸pBp{@ƹ\<;Yp:|S(չgᦒ>ozdZJnN3VTmLi-Λ'3S R7b^Ãل5A׉ca:?<ߌ}DM6k~V4 z}"O֕g}%f:UKE^Zwks4֖cYpN*g0WFF|Z=Ռ`.1lp3]]I /Nq__q_ODpngH~#n!ʽx*kfFdU-,d-_Q/tu,t1jxް:$g7fO@-9O8VLsvG.b ӹEi>=q'N$-'w"y1Srfws 3Ntuzu<6姯:cѷV}\=Mǿ̵Aƻ-yyⓁ܋[.?:/'v sm7١;phmwqv?}K^eX̡} /|fW~ޗdケ ?·rV#\cs~?bAO% 8.A7V{+'{bC믽0G>f CcW=cFx+~_p_FBonxǛp5ʟb_ ?ÌlZzc˹ŷP;.|cC[<{`˽WV3gyKzO^Hn9>G l;y+bGtŧqn#| O<7n1ُv/@o|~׸~u?[W 7}4%-RljxjZ^x/ΞbW~ׂk.M+?o'/9"Ny)3%2sg>3?nKF^_fOw~Ct཯}>OtqiwG Vtqhʞ!~ӲY/ڶ/)7O] ֧wǞ6m?s#N(m!mp縋лwq4Sv1`T11cHHVQ6] $4oacm ^wu+Z| ?}L8W(|ɄA=S|B&X[]IS (Y5 i5Ak߃.̽`"Z7r,kZG:*^L2ь^l5D<$jԯz.e6Cӄ7qFNO1T$V&=>U4|o4e&=rֻ|X{ЉddJgv(I]m(0R ǟ4H@ ;褮(_{2 %$`1'~MËG$ljq#G$jdSA\qU`lԆׄfwAW] 06mlu´ދi,FDawG$+\Dy%9NGYSZ͋x()y" vA{lB~ub0!|qX[]Q1OvVMuj&Yvgjd}>Hq0 ĈYN9buZq~ExpG=?):s S]INBjwymRȚYᎪKO6q8k1X^b\p\1Q|\GĎ<Sd9"upԑ_)@/șʁ([PT)؜_^]1Jg Ue%U5b|skwu'\Gg}i\ OY8NԒ `j9G Hk֒sK|ϵ LX)LQjuBe9EnsGY#Sr^199HB+Zrܗyi .ó.(5hִb,j ƋҮ aĺy Q1b"gn&{€+oSZIA3QnQ))txJU\1./7yʕuvuͰg9TjʠuLpk`b1~[J#߿fI:Ww:$Q[t=j]\ypEgw2'/ʲNG' r!͔QE2X̮ J-nO\w|L&lNy1 ]nH ANǣt?J#sÍi$4}$4'cl{Ľ:L/~xWtaiJ=c\YhTu=۠!5vx l"^u7etS֮Qm2aJSҷuߓ1N7NHjȜ8jVD,%8G#t^|m}bƪ9I Ym>Hjw_uU?,zI.lvi' PKJSgи@h0|Ya,HČNRLyUq$Ȁ(rY>`OG?hAbo,-"ʫJGD*h SWUI $K:-nX\ cmgti$eG0p.HI^|CxK+:UT BH)9RHa3$S*%͢Uz3~ 5N 4i0_y)Kpb^8Ѳ+آsDwEa. N sn/6xG)6SP*.+s'>8\s،AK;n 'ΉZidP-9ԛeψ3I!wdtcf륰9qd8Dm)9 (eeuSlϮƧ >KXx2i: mbw󣶨klBP^\(?.Tt%gC`43>Jzbi%A($łXP Srĕ늧O+*WǢ*7~71hark06c~?rSw:܅,%v*'2C(K+&+ضP(߁ar;9Bi%[|}#I< 7ѱ8kv-)bӤݯGS@QEK>c8qXBht/˵vV/Z\Qeб:6U,HKzF=@!-ꡄec9jV P.,VzzYyL38IVt@+$QWs DZU ы=๙GX{ʅ(K+kʉE"_ȑr/>kl\ )no9=w]Nbۧ#07j*(H~!yG J#X6Z=mZٌpZuKgpn?mt̬f7bA^j2x%|P^rA+7!"d|Vwdfk ;zugrZ_ˏ.U63 {\WmIR'`rJ߷A_V6E! fqޢjw"?-%@Dz)iE59t|S_y0gK8xGm=h /X/Cm`s_o!^j/UdQG3;Ii!)DS*ke8j*{& $# s|BLERJL9pnzW@\]/?tC%7|g٫T7gښ/rxe[:ok~h^a8^]y~]_^M1kIo쑜-5RC_qDU 3%jQ,g2N+M;0>ƝF X# *|5WI؍&$cGY@Aqvj9zV7bb^xԻ+`fCkAh<Y鮋<Ŷ'i똒ͯkG3y6W._45JE*871V,e Uc16'Qbiґ&Uj&eQRdc#F9*U3D2z:MIjy]YLOF쏞wu(֠q@E6pLOſ[ҽg$a/t5ԇ.G5ebOY yw2eEs۸vm{-uMc|CW%+}p< :0L)>Q~J)4-/iK2wCWe_a(0eVV_BVr=~o;_,ga%,?^>/'0]߄INY.h-u_`?L׿Ƭ Oy_տH_#oƟw~_ζ"PRH(6pIV( ZP`h1E33𝟀~_ tN~ٻm6Ag؟%\kh~ R"+w=gr@,6G߁~JL@;l|q `5G ւ C7<@ՍM mfǡ- uYAl`68A Y C0 .ݰrmPaFAy{/<`?p8 pD(88/+PW9x & 8 | M‡pނ rMW/ F"DBZb u1-&)?~6B1ү8v^h%{D`9t@$Jt*X k@:Xz |7afn6x$vء-m;2v`gADVم]@P" # 2us@4|s!Ok[S>zEr&&FC7/_ᥭ".s'N[E}Is`:o1t3VdK{թ3kט'CEoT~R۴J]~W_^ܙ_'e3Q-u+QS[!-Mb5q~ɉ2^~on/Aofȣ!niBc<~hA*?JؠDs_μB@BSbjOhe4S4?tH薇 Se-(fw!S$VK -޽8zËkI)zw8<ˎ#{f2x;7^ҟӴeюhJ'uYP/r;!ur(P%Q5ޗ4xmE&d3 @2I8$ pa;UG[PK!yq xl/styles.xmlT]k0}?rKB ])$ʶHrovl~{t,zarb< 0b*W=FQUPq,I>~k۟s ɹjCOLR{+ Rj#9[F 다 a\IF"yYeEϸi0v^hKwpD%[k&DV΢\8hY*!J"P9&Ik r k=JeR3}ZI%MϓZNytl}V>2IKG̾YBLI:fT Qۇ cf mrR@ (Ӧ\I$XÏ'woCJӣVTI.Lo+sT-SÔ{ /&4қ^S{ErG5U%Zf̤ ,'Rbh i/0=-\8YGY_VaP`%; VZ'] ~?{{0G1yݥllW'lv:_߾Pp xLl~xsa.M;oPK!bmxl/theme/theme1.xmlYOo6w tom'uرMniXS@I}úa0l+t&[HJKՇD"|#uڃC"$q۫]z>8h{wK cxLޜH]ś*$A>J%aACMʈJ&M;4Be tY>c~4$ &^ L1bma]ut(gZ[Wvr2u{`M,EF,2nQ%[NJeD >֗f}{7vtd%|JYw2Oڡ~J=L8-o|(<4 ժX}.@'d}.Fbo\C\ҼMT0 zSώt--g.—~?~xY'y92h!ы/ɋ>%mGEFD[t3q%'#qSgv 9feqwW@(^wdbh a8g.J pC*Xx8rbV`|XƻcǵYU3 Jݐ8b3+(QuK>QELKM2#'vi~ vlwu8+zHHJ:) ~L\E\O*t@G1lm~C*uG.R(:-ys^Di7QR8,b?SQ*q7C;+}ݧ;4pDZ_^'܉M01UJS#]flmʒgD^< dB[_WE)*k;ZxO(c5g4 h܇A:I~KBxԙ \ YWQB@^4@.hKik<ʞ6b+jΎ9#U`δuM֯DAaVB[͈f-WY؜j0[:X ~;Qㅋt >z/fʒ"Z x Zp;+e{/eP;,.&'Qk5q&pT(KLb} Sd›L17 jpaS! 35'+ZzQ TIIvt]K&⫢ #v5-|#4b3q:TA1pa*~9mm34銗bg1KB[Y&[)H V*Q Ua?SE'p>vX`3qBU( 8W0 Aw 9Kä5$ PeD)jeI2b!aC]zoPnIZ diͩdks|l2Rn6 Mf\ļ=XvYEEĢͪgY [A+M[XK52`%p7!?ڊ&aQ}6HH;8`ҤiI[-۬/0,>eE;ck;ٓ) C cc?f}p|61%M0*<ҭPK!ٗ- #xl/worksheets/_rels/sheet1.xml.relsMO &V]YތY-VK6LxrU1*\ B=*BdF0e PVP ER1>F@H=hu`R^#7rA L4oDݹm[am EISl6K)vEv\i#ST>U蠏| ]XXȀ/Fbji*w'pi,[]渜m㗽 !͹Va&FGF*m M&h|/L iV_>YɽY7}iլFNQͪ\k^N[bjs 2(RUz3j|t԰kj'Sü_PKQ2y|+QLnbJBgx.8[$;%'/HH?ALv>h/b,&"$z?HOT?N:0!RBxe|yO0!PK!\xl/worksheets/sheet1.xmlԙ]6+?P7`Hȇfg4Fvv/rX5|b'ag*kÙۧ<(+>Gmh[|f{g TEB =q5`%)ʖ 8廠*9uSQ&Aiׄ`-M K9)D $_iY5M/ކGx֦?Fo+- Ӕm eyPw)z<=]C%<ܚfT<<u385z \G ȏ86D#?X7]dl5TMRXi<^ߑl@]'Uf^^|P6$>ke V1mM{Ji C&> 4<4<ߐ*@Ӄh$)J ⧺t# ?֤T2}/=T,yβ U5fF ux0dRd5SԈoCqDm'^4cM|l@cӆZy+:( %8{N*W/=zBB7#_ C&_ly @ sl k,,+-0XmaC8`e5―XXpVf[X#`aCl k,ce5VZ-ɎN3fDlِa.M6"6o&f9dXLMvs|5aFla4g#bݼ )ʌmOaDl'.WFnTٌX}\mr+#7Wt͕*&w2rsJ]JɆOtsedxֳnN]2#6̓8g#b<d#bc7tk͈Mv*r\丕J=c+UeذS/P{b=@]f nF/adXK^. K:H .xk%ޑ0Ѣ2U T^W8 Vʲ)0le7keL4''wDJq UI[%c*Tͧy|F7 ߨpdkQ]S)WuU]e^fte{@r|BI)}: "> C [dxDZ|tGh@eNZlq/g3{YR|9 ȏpΎ[ek޻ hTyv)i_L`!v%K7T|LZI)ʈH6iWaTV2~䫖YixM@(}Џ!]JbWJ~XKXg n%55S:~<ϊMָycbI)}/Qf=˔O'ץ4+R0?)/62)6l6rL̖Bn gv[Ofw(x޳2j3Fg!5`fbtRg]X/=%.F& d>-opf0TJ+C_B |`{f LH'& WaDRfլv1y"T3&cȀjh֙!kHFH0*$$QxAQ$#BKЂȎl1x2^Qde9t8E\OVSxa7{q(=P#RDJ:ϬFN ꯦ ٶp?E ;NmM}" O,i象2p]%]VU-(Jfa 䃊hÿoPK!GKxl/drawings/drawing1.xmlX]o6}@чeY2"S6h:챠%*&B*I]R?:Ȓ5Cxs//=:MUILjBLeA6DKA3rSZ_+BO1KcbIk/dCX+jbQy"k~FQR%溵<&L.&WP%3) ǠDݯA!` Ş-%+hf fZV |h/=\WerEoK<W׀gFF%Dܚ-r+t6R}DC6(0sbY)] 1ZxafIN{(!oM`%gq<2n1'bcr{ hlZfb } b-25Y Q-wkKuŦ!98B&F1ao,o-ۃZWDuN8_ޮ>A#Fd 23TMϞ#z6-)`qc#vKc[qϼ = Aԇzyv_Lx5gKYb^`Mi˕p7rUr$t)B't^P6s4( :%}&BY"霋L8G6NzwY;vv2Do૫vڨ}=Z]TUUT)7v[}ÕĹ)+oᕓ3=HUNFlXNl a}~B3t̨\Wuu\E,*+);e)+hQpg{!qjJGxh? busWHUza`*) kʽ3 { z}R/Rv7{c?'5S4D;jNMrPK!{1"xl/activeX/_rels/activeX2.xml.relsl1k0ཐ nd(XPz϶u'#24x-.JaCe<9?/`" SwR1U`VM?SH"(93O6Ɖmmm@` ;ȨheO5\]X,o1<:+0ؾo?PK!S'/"xl/activeX/_rels/activeX1.xml.relsl1k0 newXPRz϶u'#>:4x5Ba]ӂ!2|>^Eq&VZ*KHTE5Z[BK#&Z<ۄ3ۀ4A>Ӗ;ȤheO5\]Xo1o<:I;0ءwo 7PK!UUTPxl/activeX/activeX2.binr0byr"6 L  | y)N%%y XL @!f`bfpNL*dPK!Gxl/ctrlProps/ctrlProp1.xml 10 w$y)LV<  $q7ݹ_bP?)jP,>Zx I n9;JI2{VŒId>ivhfbrRY5ĠuFG(zʰ%tPagEPK!:t2xl/ctrlProps/ctrlProp2.xml A0@ѽwhf/EWb,,L\p(e*vt ݒadXՠ^p%h#XQknL%s|fUH LMT)¾TfC-sF;ʄXRԧnt ү=Tdq)PK!LPbdocProps/core.xml (|Ak0]JP a0ec|mHu͓'IMd€ $Wm?GL V* )jEv{rU-x$ ucP1tVY;\3~`;! w@|D_c(i t-yO&ͪmt֝±}2Xl&h^c?/d7+(K\Jgy u+k7mbfj/:BߩIǧ|4?˯wFuV;OhFI4!^Y}+oPK!@docProps/app.xml (Ao0  9]1 H;avgNc$H׏Ywڍ{xDI:W E) &+zEJ1[ڤ1\pϕhJlZ /X4!u@ܦ Mc ڡ'y]%}U| S⪧ X5@|Kݚrhx8tJEt[4QJ[5pQ@=" KۀMYV= 7Z SO5ئf]̔ϐ^rHYI6Lñ{絽ťq@XDYr4Hxdx'I@}8ޙ+|[wOm k~~G^erCȺy/ <}qYJ~LgPK!QPxl/activeX/activeX1.binR3}ÕsuV1;\``R`f````h`b6 ILJ1d`8/````0R@@91'3(PK!>^LjcustomUI/customUI.xmlSj0<;+bbӐRJ,ۊ-bi$kIBoRvڀzA'JƘ#!l:TaoܯVduG|nr/Q(@(J -5īzG$F--Xg14Jj1D;9ӶZjˉ93DŽ *n4[5BVIM 1F ץ) @ĕHm@ jR:wX28-+,o4P&đ+c~C 6[Content_Types].xmlPK-!D3Y '_rels/.relsPK-!V/4txl/_rels/workbook.xml.relsPK-!L/>` xl/workbook.xmlPK-!Ќ |{ xl/sharedStrings.xmlPK-!N) xl/vbaProject.binPK-!yq nxl/styles.xmlPK-!bm xl/theme/theme1.xmlPK-!ٗ- #Ϡxl/worksheets/_rels/sheet1.xml.relsPK-!p.q&=xl/drawings/_rels/vmlDrawing1.vml.relsPK-!( Hxl/media/image2.emfPK-!\%xl/worksheets/sheet1.xmlPK-!^#vVO xl/drawings/vmlDrawing1.vmlPK-!GKFxl/drawings/drawing1.xmlPK-!2x$xl/media/image1.emfPK-!I.xl/activeX/activeX2.xmlPK-!58.кxl/activeX/activeX1.xmlPK-!{1"xl/activeX/_rels/activeX2.xml.relsPK-!S'/"xl/activeX/_rels/activeX1.xml.relsPK-!UUTPxl/activeX/activeX2.binPK-!Gxl/ctrlProps/ctrlProp1.xmlPK-!:t2[xl/ctrlProps/ctrlProp2.xmlPK-!LPb1docProps/core.xmlPK-!@docProps/app.xmlPK-!QPwxl/activeX/activeX1.binPK-!>^LjcustomUI/customUI.xmlPKopenpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/workbook.xml000066400000000000000000000015331224514475200247010ustar00rootroot00000000000000 'My Sheeet'!$D$8openpyxl-1.7.0+ds1/openpyxl/tests/test_data/reader/workbook_namedrange.xml000066400000000000000000000017671224514475200270730ustar00rootroot00000000000000 'My Sheeet with a , and '''!$U$16:$U$24,'My Sheeet with a , and '''!$V$28:$V$36 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/000077500000000000000000000000001224514475200223725ustar00rootroot00000000000000openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/000077500000000000000000000000001224514475200241735ustar00rootroot00000000000000openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/.rels000066400000000000000000000010761224514475200251450ustar00rootroot00000000000000 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/[Content_Types].xml000066400000000000000000000030501224514475200300010ustar00rootroot00000000000000 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/app.xml000066400000000000000000000014551224514475200255020ustar00rootroot00000000000000 Microsoft Excel0falsefalsefalsefalse12.0000Worksheets3SheetSheet1Sheet2openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/core.xml000066400000000000000000000013171224514475200256470ustar00rootroot00000000000000 TEST_USER SOMEBODY 2010-04-01T20:30:00Z 2010-04-05T14:05:30Z Untitled openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/decimal.xml000066400000000000000000000013651224514475200263200ustar00rootroot00000000000000 3.14 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/font.xml000066400000000000000000000003721224514475200256650ustar00rootroot00000000000000 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/long_number.xml000066400000000000000000000014241224514475200272250ustar00rootroot00000000000000 9781231231230 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/sharedStrings.xml000066400000000000000000000003031224514475200275310ustar00rootroot00000000000000 helloworldniceopenpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/sheet1.xml000066400000000000000000000014131224514475200261050ustar00rootroot00000000000000 0 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/sheet1_auto_filter.xml000066400000000000000000000012021224514475200304760ustar00rootroot000000000000000openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/sheet1_bool.xml000066400000000000000000000016121224514475200271210ustar00rootroot00000000000000 0 1 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/sheet1_formula.xml000066400000000000000000000020111224514475200276250ustar00rootroot00000000000000 10 32 F1+F2 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/sheet1_freeze_panes_both.xml000066400000000000000000000017331224514475200316540ustar00rootroot00000000000000 0 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/sheet1_freeze_panes_horiz.xml000066400000000000000000000015671224514475200320600ustar00rootroot00000000000000 0 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/sheet1_freeze_panes_vert.xml000066400000000000000000000015631224514475200317010ustar00rootroot00000000000000 0 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/sheet1_height.xml000066400000000000000000000014421224514475200274370ustar00rootroot00000000000000 10 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/sheet1_hyperlink.xml000066400000000000000000000015661224514475200302030ustar00rootroot00000000000000 0 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/sheet1_hyperlink.xml.rels000066400000000000000000000007331224514475200311420ustar00rootroot00000000000000 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/sheet1_style.xml000066400000000000000000000011551224514475200273300ustar00rootroot000000000000000.13openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/short_number.xml000066400000000000000000000014211224514475200274220ustar00rootroot00000000000000 1234567890 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/simple-styles.xml000066400000000000000000000031171224514475200275310ustar00rootroot00000000000000 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/styles.xml000066400000000000000000000016711224514475200262450ustar00rootroot00000000000000 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/theme1.xml000066400000000000000000000155231224514475200261060ustar00rootroot00000000000000 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/workbook.xml000066400000000000000000000013201224514475200265460ustar00rootroot00000000000000 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/workbook.xml.rels000066400000000000000000000012551224514475200275210ustar00rootroot00000000000000 openpyxl-1.7.0+ds1/openpyxl/tests/test_data/writer/expected/workbook_auto_filter.xml000066400000000000000000000016151224514475200311520ustar00rootroot00000000000000 openpyxl-1.7.0+ds1/openpyxl/tests/test_datavalidation.py000066400000000000000000000060021224514475200235010ustar00rootroot00000000000000# file openpyxl/tests/test_cell.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # 3rd party imports from nose.tools import eq_, raises, assert_raises #pylint: disable=E0611 # package imports from openpyxl.datavalidation import collapse_cell_addresses, DataValidation, ValidationType # There are already unit-tests in test_cell.py that test out the # coordinate_from_string method. This should be the only way the # collapse_cell_addresses method can throw, so we don't bother using invalid # cell coordinates in the test-data here. COLLAPSE_TEST_DATA = [ (["A1"], "A1"), (["A1", "B1"], "A1 B1"), (["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"], "A1:A4 B1:B4"), (["A2", "A4", "A3", "A1", "A5"], "A1:A5"), ] def test_collapse_cell_addresses(): def check_address(data): cells, expected = data collapsed = collapse_cell_addresses(cells) eq_(collapsed, expected) for data in COLLAPSE_TEST_DATA: yield check_address, data def test_list_validation(): dv = DataValidation(ValidationType.LIST, formula1='"Dog,Cat,Fish"') eq_(dv.formula1, '"Dog,Cat,Fish"') eq_(dv.generate_attributes_map()['type'], 'list') eq_(dv.generate_attributes_map()['allowBlank'], '0') eq_(dv.generate_attributes_map()['showErrorMessage'], '1') eq_(dv.generate_attributes_map()['showInputMessage'], '1') def test_error_message(): dv = DataValidation(ValidationType.LIST, formula1='"Dog,Cat,Fish"') dv.set_error_message('You done bad') eq_(dv.generate_attributes_map()['errorTitle'], 'Validation Error') eq_(dv.generate_attributes_map()['error'], 'You done bad') def test_prompt_message(): dv = DataValidation(ValidationType.LIST, formula1='"Dog,Cat,Fish"') dv.set_prompt_message('Please enter a value') eq_(dv.generate_attributes_map()['promptTitle'], 'Validation Prompt') eq_(dv.generate_attributes_map()['prompt'], 'Please enter a value') openpyxl-1.7.0+ds1/openpyxl/tests/test_dump.py000066400000000000000000000107131224514475200214660ustar00rootroot00000000000000 # file openpyxl/tests/test_dump.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # Python stdlib imports from datetime import time, datetime # 3rd party imports from nose.tools import eq_, raises from openpyxl.workbook import Workbook from openpyxl.writer import dump_worksheet from openpyxl.cell import get_column_letter from openpyxl.reader.excel import load_workbook from openpyxl.writer.strings import StringTableBuilder from openpyxl.shared.compat import NamedTemporaryFile, xrange from openpyxl.shared.exc import WorkbookAlreadySaved import os import os.path as osp import shutil def _get_test_filename(): test_file = NamedTemporaryFile(mode='w', prefix='openpyxl.', suffix='.xlsx', delete=False) test_file.close() return test_file.name def test_dump_sheet_title(): test_filename = _get_test_filename() wb = Workbook(optimized_write=True) ws = wb.create_sheet(title='Test1') wb.save(test_filename) wb2 = load_workbook(test_filename, True) ws = wb2.get_sheet_by_name('Test1') eq_('Test1', ws.title) def test_dump_sheet(): test_filename = _get_test_filename() wb = Workbook(optimized_write=True) ws = wb.create_sheet() letters = [get_column_letter(x + 1) for x in xrange(20)] expected_rows = [] for row in xrange(20): expected_rows.append(['%s%d' % (letter, row + 1) for letter in letters]) for row in xrange(20): expected_rows.append([(row + 1) for letter in letters]) for row in xrange(10): expected_rows.append([datetime(2010, ((x % 12) + 1), row + 1) for x in range(len(letters))]) for row in xrange(20): expected_rows.append(['=%s%d' % (letter, row + 1) for letter in letters]) for row in expected_rows: ws.append(row) wb.save(test_filename) wb2 = load_workbook(test_filename, True) ws = wb2.worksheets[0] for ex_row, ws_row in zip(expected_rows[:-20], ws.iter_rows()): for ex_cell, ws_cell in zip(ex_row, ws_row): eq_(ex_cell, ws_cell.internal_value) os.remove(test_filename) def test_table_builder(): sb = StringTableBuilder() result = {'a':0, 'b':1, 'c':2, 'd':3} for letter in sorted(result.keys()): for x in range(5): sb.add(letter) table = dict(sb.get_table()) for key, idx in result.items(): eq_(idx, table[key]) def test_open_too_many_files(): test_filename = _get_test_filename() wb = Workbook(optimized_write=True) for i in range(200): # over 200 worksheets should raise an OSError ('too many open files') wb.create_sheet() wb.save(test_filename) os.remove(test_filename) def test_create_temp_file(): f = dump_worksheet.create_temporary_file() if not osp.isfile(f): raise Exception("The file %s does not exist" % f) @raises(WorkbookAlreadySaved) def test_dump_twice(): test_filename = _get_test_filename() wb = Workbook(optimized_write=True) ws = wb.create_sheet() ws.append(['hello']) wb.save(test_filename) os.remove(test_filename) wb.save(test_filename) @raises(WorkbookAlreadySaved) def test_append_after_save(): test_filename = _get_test_filename() wb = Workbook(optimized_write=True) ws = wb.create_sheet() ws.append(['hello']) wb.save(test_filename) os.remove(test_filename) ws.append(['hello']) openpyxl-1.7.0+ds1/openpyxl/tests/test_iter.py000066400000000000000000000115501224514475200214640ustar00rootroot00000000000000# file openpyxl/tests/test_iter.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file from nose.tools import eq_, raises, assert_raises import os.path as osp from openpyxl.tests.helper import DATADIR from openpyxl.reader.iter_worksheet import get_range_boundaries from openpyxl.reader.excel import load_workbook from openpyxl.shared.compat import xrange import datetime class TestWorksheet(object): workbook_name = osp.join(DATADIR, 'genuine', 'empty.xlsx') def _open_wb(self): return load_workbook(filename = self.workbook_name, use_iterators = True) class TestDims(TestWorksheet): expected = [ 'A1:G5', 'D1:K30', 'D2:D2', 'A1:C1' ] def test_get_dimensions(self): wb = self._open_wb() for i, sheetn in enumerate(wb.get_sheet_names()): ws = wb.get_sheet_by_name(name = sheetn) eq_(ws._dimensions, self.expected[i]) def test_get_highest_column_iter(self): wb = self._open_wb() ws = wb.worksheets[0] eq_(ws.get_highest_column(), 7) class TestText(TestWorksheet): sheet_name = 'Sheet1 - Text' expected = [['This is cell A1 in Sheet 1', None, None, None, None, None, None], [None, None, None, None, None, None, None], [None, None, None, None, None, None, None], [None, None, None, None, None, None, None], [None, None, None, None, None, None, 'This is cell G5'], ] def test_read_fast_integrated(self): wb = self._open_wb() ws = wb.get_sheet_by_name(name = self.sheet_name) for row, expected_row in zip(ws.iter_rows(), self.expected): row_values = [x.internal_value for x in row] eq_(row_values, expected_row) def test_get_boundaries_range(self): eq_(get_range_boundaries('C1:C4'), (3, 1, 3, 4)) def test_get_boundaries_one(self): eq_(get_range_boundaries('C1'), (3, 1, 4, 1)) def test_read_single_cell_range(self): wb = self._open_wb() ws = wb.get_sheet_by_name(name = self.sheet_name) eq_('This is cell A1 in Sheet 1', list(ws.iter_rows('A1'))[0][0].internal_value) class TestIntegers(TestWorksheet): sheet_name = 'Sheet2 - Numbers' expected = [[x + 1] for x in xrange(30)] query_range = 'D1:E30' def test_read_fast_integrated(self): wb = self._open_wb() ws = wb.get_sheet_by_name(name = self.sheet_name) for row, expected_row in zip(ws.iter_rows(self.query_range), self.expected): row_values = [x.internal_value for x in row] eq_(row_values, expected_row) class TestFloats(TestWorksheet): sheet_name = 'Sheet2 - Numbers' query_range = 'K1:L30' expected = expected = [[(x + 1) / 100.0] for x in xrange(30)] def test_read_fast_integrated(self): wb = self._open_wb() ws = wb.get_sheet_by_name(name = self.sheet_name) for row, expected_row in zip(ws.iter_rows(self.query_range), self.expected): row_values = [x.internal_value for x in row] eq_(row_values, expected_row) class TestDates(TestWorksheet): sheet_name = 'Sheet4 - Dates' def test_read_single_cell_date(self): wb = self._open_wb() ws = wb.get_sheet_by_name(name = self.sheet_name) eq_(datetime.datetime(1973, 5, 20), list(ws.iter_rows('A1'))[0][0].internal_value) eq_(datetime.datetime(1973, 5, 20, 9, 15, 2), list(ws.iter_rows('C1'))[0][0].internal_value) openpyxl-1.7.0+ds1/openpyxl/tests/test_iter_stream.py000066400000000000000000000045541224514475200230450ustar00rootroot00000000000000# file openpyxl/tests/test_iter_stream.py # Copyright (c) 2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file from nose.tools import eq_, raises, assert_raises import os.path as osp from openpyxl.tests.helper import DATADIR from openpyxl.reader.iter_worksheet import get_range_boundaries from openpyxl.reader.excel import load_workbook import openpyxl.tests.test_iter as test_iter import datetime class StreamTestWorksheet(object): workbook_name = osp.join(DATADIR, 'genuine', 'empty_no_dimensions.xlsx') def _open_wb(self): ff = open(self.workbook_name, 'rb') return load_workbook(filename = ff, use_iterators = True) class TestDims(StreamTestWorksheet, test_iter.TestDims): pass class TestText(StreamTestWorksheet, test_iter.TestText): def test_get_boundaries_range(self): pass def test_get_boundaries_one(self): pass class TestIntegers(StreamTestWorksheet, test_iter.TestIntegers): workbook_name = osp.join(DATADIR, 'genuine', 'empty_no_dimensions.xlsx') class TestFloats(StreamTestWorksheet, test_iter.TestFloats): workbook_name = osp.join(DATADIR, 'genuine', 'empty_no_dimensions.xlsx') class TestDates(StreamTestWorksheet, test_iter.TestDates): workbook_name = osp.join(DATADIR, 'genuine', 'empty_no_dimensions.xlsx') openpyxl-1.7.0+ds1/openpyxl/tests/test_meta.py000066400000000000000000000036621224514475200214540ustar00rootroot00000000000000# file openpyxl/tests/test_meta.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # Python stdlib imports import os.path # package imports from openpyxl.tests.helper import DATADIR, assert_equals_file_content from openpyxl.writer.workbook import write_content_types, write_root_rels from openpyxl.workbook import Workbook def test_write_content_types(): wb = Workbook() wb.create_sheet() wb.create_sheet() content = write_content_types(wb) reference_file = os.path.join(DATADIR, 'writer', 'expected', '[Content_Types].xml') assert_equals_file_content(reference_file, content) def test_write_root_rels(): wb = Workbook() content = write_root_rels(wb) reference_file = os.path.join(DATADIR, 'writer', 'expected', '.rels') assert_equals_file_content(reference_file, content) openpyxl-1.7.0+ds1/openpyxl/tests/test_named_range.py000066400000000000000000000154571224514475200227730ustar00rootroot00000000000000# file openpyxl/tests/test_named_range.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # Python stdlib imports import os.path # 3rd-party imports from nose.tools import eq_, assert_raises, ok_ # package imports from openpyxl.tests.helper import DATADIR, TMPDIR, clean_tmpdir, make_tmpdir from openpyxl.namedrange import split_named_range, NamedRange from openpyxl.reader.workbook import read_named_ranges from openpyxl.shared.exc import NamedRangeException from openpyxl.reader.excel import load_workbook from openpyxl.writer.workbook import write_workbook from openpyxl.workbook import Workbook def test_split(): eq_([('My Sheet', '$D$8'), ], split_named_range("'My Sheet'!$D$8")) def test_split_no_quotes(): eq_([('HYPOTHESES', '$B$3:$L$3'), ], split_named_range('HYPOTHESES!$B$3:$L$3')) def test_bad_range_name(): assert_raises(NamedRangeException, split_named_range, 'HYPOTHESES$B$3') def test_range_name_worksheet_special_chars(): class DummyWs(object): title = 'My Sheeet with a , and \'' def __str__(self): return self.title ws = DummyWs() class DummyWB(object): def get_sheet_by_name(self, name): if name == ws.title: return ws handle = open(os.path.join(DATADIR, 'reader', 'workbook_namedrange.xml')) try: content = handle.read() named_ranges = read_named_ranges(content, DummyWB()) eq_(1, len(named_ranges)) ok_(isinstance(named_ranges[0], NamedRange)) eq_([(ws, '$U$16:$U$24'), (ws, '$V$28:$V$36')], named_ranges[0].destinations) finally: handle.close() def test_read_named_ranges(): class DummyWs(object): title = 'My Sheeet' def __str__(self): return self.title class DummyWB(object): def get_sheet_by_name(self, name): return DummyWs() handle = open(os.path.join(DATADIR, 'reader', 'workbook.xml')) try: content = handle.read() named_ranges = read_named_ranges(content, DummyWB()) eq_(["My Sheeet!$D$8"], [str(range) for range in named_ranges]) finally: handle.close() def test_oddly_shaped_named_ranges(): ranges_counts = ((4, 'TEST_RANGE'), (3, 'TRAP_1'), (13, 'TRAP_2')) def check_ranges(ws, count, range_name): eq_(count, len(ws.range(range_name))) wb = load_workbook(os.path.join(DATADIR, 'genuine', 'merge_range.xlsx'), use_iterators = False) ws = wb.worksheets[0] for count, range_name in ranges_counts: yield check_ranges, ws, count, range_name def test_merged_cells_named_range(): wb = load_workbook(os.path.join(DATADIR, 'genuine', 'merge_range.xlsx'), use_iterators = False) ws = wb.worksheets[0] cell = ws.range('TRAP_3') eq_('B15', cell.get_coordinate()) eq_(10, cell.value) def test_print_titles(): wb = Workbook() ws1 = wb.create_sheet() ws2 = wb.create_sheet() ws1.add_print_title(2) ws2.add_print_title(3, rows_or_cols='cols') def mystr(nr): return ','.join(['%s!%s' % (sheet.title, name) for sheet, name in nr.destinations]) actual_named_ranges = set([(nr.name, nr.scope, mystr(nr)) for nr in wb.get_named_ranges()]) expected_named_ranges = set([('_xlnm.Print_Titles', ws1, 'Sheet1!$1:$2'), ('_xlnm.Print_Titles', ws2, 'Sheet2!$A:$C')]) assert(actual_named_ranges == expected_named_ranges) class TestNameRefersToValue(object): def setup(self): self.wb = load_workbook(os.path.join(DATADIR, 'genuine', 'NameWithValueBug.xlsx')) self.ws = self.wb.get_sheet_by_name("Sheet1") make_tmpdir() def tearDown(self): clean_tmpdir() def test_has_ranges(self): ranges = self.wb.get_named_ranges() eq_(['MyRef', 'MySheetRef', 'MySheetRef', 'MySheetValue', 'MySheetValue', 'MyValue'], [range.name for range in ranges]) def test_workbook_has_normal_range(self): normal_range = self.wb.get_named_range("MyRef") eq_("MyRef", normal_range.name) def test_workbook_has_value_range(self): value_range = self.wb.get_named_range("MyValue") eq_("MyValue", value_range.name) eq_("9.99", value_range.value) def test_worksheet_range(self): range = self.ws.range("MyRef") def test_worksheet_range_error_on_value_range(self): assert_raises(NamedRangeException, self.ws.range, "MyValue") def range_as_string(self, range, include_value=False): def scope_as_string(range): if range.scope: return range.scope.title else: return "Workbook" retval = "%s: %s" % (range.name, scope_as_string(range)) if include_value: if isinstance(range, NamedRange): retval += "=[range]" else: retval += "=" + range.value return retval def test_handles_scope(self): ranges = self.wb.get_named_ranges() eq_(['MyRef: Workbook', 'MySheetRef: Sheet1', 'MySheetRef: Sheet2', 'MySheetValue: Sheet1', 'MySheetValue: Sheet2', 'MyValue: Workbook'], [self.range_as_string(range) for range in ranges]) def test_can_be_saved(self): FNAME = os.path.join(TMPDIR, "foo.xlsx") self.wb.save(FNAME) wbcopy = load_workbook(FNAME) eq_(['MyRef: Workbook=[range]', 'MySheetRef: Sheet1=[range]', 'MySheetRef: Sheet2=[range]', 'MySheetValue: Sheet1=3.33', 'MySheetValue: Sheet2=14.4', 'MyValue: Workbook=9.99'], [self.range_as_string(range, include_value=True) for range in wbcopy.get_named_ranges()]) openpyxl-1.7.0+ds1/openpyxl/tests/test_number_format.py000066400000000000000000000135541224514475200233670ustar00rootroot00000000000000# file openpyxl/tests/test_number_format.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # Python stdlib imports from datetime import datetime, date, timedelta # 3rd party imports from nose.tools import eq_, assert_almost_equal, assert_raises # package imports from openpyxl.workbook import Workbook from openpyxl.worksheet import Worksheet from openpyxl.cell import Cell from openpyxl.style import NumberFormat from openpyxl.shared.date_time import SharedDate, CALENDAR_MAC_1904, CALENDAR_WINDOWS_1900 import time # strptime fallback, thanks to coderfi # http://stackoverflow.com/questions/5585706/datetime-datetime-strptime-not-present-in-python-2-4-1/7226819#7226819 if hasattr(datetime, 'strptime'): #python 2.6 strptime = datetime.strptime else: #python 2.4 equivalent strptime = lambda date_string, format: datetime(*(time.strptime(date_string, format)[0:6])) class TestNumberFormat(object): @classmethod def setup_class(cls): cls.workbook = Workbook() cls.worksheet = Worksheet(cls.workbook, 'Test') cls.sd = SharedDate() def test_convert_date_to_julian(self): eq_(40167, self.sd.to_julian(2009, 12, 20)) def test_convert_date_from_julian(self): def test_date_equal(julian, datetime): eq_(self.sd.from_julian(julian), datetime) date_pairs= ( (40167, datetime(2009, 12, 20)), (21980, datetime(1960, 3, 5)), ) for count, dt in date_pairs: yield test_date_equal, count, dt def test_convert_datetime_to_julian(self): eq_(40167, self.sd.datetime_to_julian(datetime(2009, 12, 20))) eq_(40196.5939815, self.sd.datetime_to_julian(datetime(2010, 1, 18, 14, 15, 20, 1600))) def test_convert_timedelta_to_julian(self): eq_(1.125, self.sd.datetime_to_julian(timedelta(days=1, hours=3))) def test_insert_float(self): self.worksheet.cell('A1').value = 3.14 eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) def test_insert_percentage(self): self.worksheet.cell('A1').value = '3.14%' eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) assert_almost_equal(0.0314, self.worksheet.cell('A1').value) def test_insert_datetime(self): self.worksheet.cell('A1').value = date.today() eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) def test_insert_date(self): self.worksheet.cell('A1').value = datetime.now() eq_(Cell.TYPE_NUMERIC, self.worksheet.cell('A1')._data_type) def test_internal_date(self): dt = datetime(2010, 7, 13, 6, 37, 41) self.worksheet.cell('A3').value = dt eq_(40372.27616898148, self.worksheet.cell('A3')._value) def test_datetime_interpretation(self): dt = datetime(2010, 7, 13, 6, 37, 41) self.worksheet.cell('A3').value = dt eq_(dt, self.worksheet.cell('A3').value) def test_date_interpretation(self): dt = date(2010, 7, 13) self.worksheet.cell('A3').value = dt eq_(datetime(2010, 7, 13, 0, 0), self.worksheet.cell('A3').value) def test_number_format_style(self): self.worksheet.cell('A1').value = '12.6%' eq_(NumberFormat.FORMAT_PERCENTAGE, \ self.worksheet.cell('A1').style.number_format.format_code) def test_date_format_on_non_date(self): cell = self.worksheet.cell('A1') def check_date_pair(count, date_string): cell.value = strptime(date_string, '%Y-%m-%d') eq_(count, cell._value) date_pairs = ( (15, '1900-01-15'), (59, '1900-02-28'), (61, '1900-03-01'), (367, '1901-01-01'), (2958465, '9999-12-31'), ) for count, date_string in date_pairs: yield check_date_pair, count, date_string def test_1900_leap_year(self): assert_raises(ValueError, self.sd.from_julian, 60) assert_raises(ValueError, self.sd.to_julian, 1900, 2, 29) def test_bad_date(self): def check_bad_date(year, month, day): assert_raises(ValueError, self.sd.to_julian, year, month, day) bad_dates = ((1776, 7, 4), (1899, 12, 31), ) for year, month, day in bad_dates: yield check_bad_date, year, month, day def test_bad_julian_date(self): assert_raises(ValueError, self.sd.from_julian, -1) def test_mac_date(self): self.sd.excel_base_date = CALENDAR_MAC_1904 datetuple = (2011, 10, 31) dt = date(datetuple[0],datetuple[1],datetuple[2]) julian = self.sd.to_julian(datetuple[0],datetuple[1],datetuple[2]) reverse = self.sd.from_julian(julian).date() eq_(dt,reverse) self.sd.excel_base_date = CALENDAR_WINDOWS_1900 openpyxl-1.7.0+ds1/openpyxl/tests/test_password_hash.py000066400000000000000000000030771224514475200233730ustar00rootroot00000000000000# file openpyxl/tests/test_password_hash.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # 3rd party imports from nose.tools import eq_ # package imports from openpyxl.shared.password_hasher import hash_password from openpyxl.worksheet import SheetProtection def test_hasher(): eq_('CBEB', hash_password('test')) def test_sheet_protection(): protection = SheetProtection() protection.password = 'test' eq_('CBEB', protection.password) openpyxl-1.7.0+ds1/openpyxl/tests/test_props.py000066400000000000000000000113631224514475200216660ustar00rootroot00000000000000# coding=utf-8 # file openpyxl/tests/test_props.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # Python stdlib imports from zipfile import ZipFile, ZIP_DEFLATED from datetime import datetime import os.path # 3rd party imports from nose.tools import eq_ # package imports from openpyxl.tests.helper import DATADIR, TMPDIR, make_tmpdir, clean_tmpdir, \ assert_equals_file_content from openpyxl.reader.workbook import read_properties_core, \ read_sheets_titles from openpyxl.writer.workbook import write_properties_core, \ write_properties_app from openpyxl.shared.ooxml import ARC_APP, ARC_CORE, ARC_WORKBOOK from openpyxl.shared.date_time import CALENDAR_WINDOWS_1900 from openpyxl.workbook import DocumentProperties, Workbook class TestReaderProps(object): @classmethod def setup_class(cls): cls.genuine_filename = os.path.join(DATADIR, 'genuine', 'empty.xlsx') cls.archive = ZipFile(cls.genuine_filename, 'r', ZIP_DEFLATED) @classmethod def teardown_class(cls): cls.archive.close() def test_read_properties_core(self): content = self.archive.read(ARC_CORE) prop = read_properties_core(content) eq_(prop.creator, '*.*') try: # Python 2 eacute = unichr(233) except NameError: # Python 3 eacute = chr(233) eq_(prop.last_modified_by, 'Aur' + eacute + 'lien Camp' + eacute + 'as') eq_(prop.created, datetime(2010, 4, 9, 20, 43, 12)) eq_(prop.modified, datetime(2011, 2, 9, 13, 49, 32)) def test_read_sheets_titles(self): content = self.archive.read(ARC_WORKBOOK) sheet_titles = read_sheets_titles(content) eq_(sheet_titles, \ ['Sheet1 - Text', 'Sheet2 - Numbers', 'Sheet3 - Formulas', 'Sheet4 - Dates']) class TestLibreOfficeCompat(object): """ Just tests that the correct date/time format is returned from LibreOffice saved version """ @classmethod def setup_class(cls): cls.genuine_filename = os.path.join(DATADIR, 'genuine', 'empty_libre.xlsx') cls.archive = ZipFile(cls.genuine_filename, 'r', ZIP_DEFLATED) @classmethod def teardown_class(cls): cls.archive.close() def test_read_properties_core(self): content = self.archive.read(ARC_CORE) prop = read_properties_core(content) eq_(prop.excel_base_date, CALENDAR_WINDOWS_1900) def test_read_sheets_titles(self): content = self.archive.read(ARC_WORKBOOK) sheet_titles = read_sheets_titles(content) eq_(sheet_titles, \ ['Sheet1 - Text', 'Sheet2 - Numbers', 'Sheet3 - Formulas', 'Sheet4 - Dates']) class TestWriteProps(object): @classmethod def setup_class(cls): make_tmpdir() cls.tmp_filename = os.path.join(TMPDIR, 'test.xlsx') cls.prop = DocumentProperties() @classmethod def teardown_class(cls): clean_tmpdir() def test_write_properties_core(self): self.prop.creator = 'TEST_USER' self.prop.last_modified_by = 'SOMEBODY' self.prop.created = datetime(2010, 4, 1, 20, 30, 00) self.prop.modified = datetime(2010, 4, 5, 14, 5, 30) content = write_properties_core(self.prop) assert_equals_file_content( os.path.join(DATADIR, 'writer', 'expected', 'core.xml'), content) def test_write_properties_app(self): wb = Workbook() wb.create_sheet() wb.create_sheet() content = write_properties_app(wb) assert_equals_file_content( os.path.join(DATADIR, 'writer', 'expected', 'app.xml'), content) openpyxl-1.7.0+ds1/openpyxl/tests/test_read.py000066400000000000000000000213201224514475200214300ustar00rootroot00000000000000# file openpyxl/tests/test_read.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # Python stdlib imports import os.path from datetime import datetime, date # 3rd party imports from nose.tools import eq_, raises # compatibility imports from openpyxl.shared.compat import BytesIO, StringIO, unicode # package imports from openpyxl.tests.helper import DATADIR from openpyxl.worksheet import Worksheet from openpyxl.workbook import Workbook from openpyxl.style import NumberFormat, Style from openpyxl.reader.worksheet import read_worksheet, read_dimension from openpyxl.reader.excel import load_workbook from openpyxl.shared.exc import InvalidFileException from openpyxl.shared.date_time import CALENDAR_WINDOWS_1900, CALENDAR_MAC_1904 def test_read_standalone_worksheet(): class DummyWb(object): encoding = 'utf-8' excel_base_date = CALENDAR_WINDOWS_1900 _guess_types = True def get_sheet_by_name(self, value): return None path = os.path.join(DATADIR, 'reader', 'sheet2.xml') ws = None handle = open(path) try: ws = read_worksheet(handle.read(), DummyWb(), 'Sheet 2', {1: 'hello'}, {1: Style()}) finally: handle.close() assert isinstance(ws, Worksheet) eq_(ws.cell('G5').value, 'hello') eq_(ws.cell('D30').value, 30) eq_(ws.cell('K9').value, 0.09) def test_read_standard_workbook(): path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') wb = load_workbook(path) assert isinstance(wb, Workbook) def test_read_standard_workbook_from_fileobj(): path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') fo = open(path, mode='rb') wb = load_workbook(fo) assert isinstance(wb, Workbook) def test_read_worksheet(): path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') wb = load_workbook(path) sheet2 = wb.get_sheet_by_name('Sheet2 - Numbers') assert isinstance(sheet2, Worksheet) eq_('This is cell G5', sheet2.cell('G5').value) eq_(18, sheet2.cell('D18').value) def test_read_nostring_workbook(): genuine_wb = os.path.join(DATADIR, 'genuine', 'empty-no-string.xlsx') wb = load_workbook(genuine_wb) assert isinstance(wb, Workbook) @raises(InvalidFileException) def test_read_empty_file(): null_file = os.path.join(DATADIR, 'reader', 'null_file.xlsx') wb = load_workbook(null_file) @raises(InvalidFileException) def test_read_empty_archive(): null_file = os.path.join(DATADIR, 'reader', 'null_archive.xlsx') wb = load_workbook(null_file) def test_read_dimension(): path = os.path.join(DATADIR, 'reader', 'sheet2.xml') dimension = None handle = open(path) try: dimension = read_dimension(xml_source=handle.read()) finally: handle.close() eq_(('D', 1, 'K', 30), dimension) def test_calculate_dimension_iter(): path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') wb = load_workbook(filename=path, use_iterators=True) sheet2 = wb.get_sheet_by_name('Sheet2 - Numbers') dimensions = sheet2.calculate_dimension() eq_('%s%s:%s%s' % ('D', 1, 'K', 30), dimensions) def test_get_highest_row_iter(): path = os.path.join(DATADIR, 'genuine', 'empty.xlsx') wb = load_workbook(filename=path, use_iterators=True) sheet2 = wb.get_sheet_by_name('Sheet2 - Numbers') max_row = sheet2.get_highest_row() eq_(30, max_row) def test_read_workbook_with_no_properties(): genuine_wb = os.path.join(DATADIR, 'genuine', \ 'empty_with_no_properties.xlsx') wb = load_workbook(filename=genuine_wb) class TestReadWorkbookWithStyles(object): @classmethod def setup_class(cls): cls.genuine_wb = os.path.join(DATADIR, 'genuine', \ 'empty-with-styles.xlsx') wb = load_workbook(cls.genuine_wb) cls.ws = wb.get_sheet_by_name('Sheet1') def test_read_general_style(self): eq_(self.ws.cell('A1').style.number_format.format_code, NumberFormat.FORMAT_GENERAL) def test_read_date_style(self): eq_(self.ws.cell('A2').style.number_format.format_code, NumberFormat.FORMAT_DATE_XLSX14) def test_read_number_style(self): eq_(self.ws.cell('A3').style.number_format.format_code, NumberFormat.FORMAT_NUMBER_00) def test_read_time_style(self): eq_(self.ws.cell('A4').style.number_format.format_code, NumberFormat.FORMAT_DATE_TIME3) def test_read_percentage_style(self): eq_(self.ws.cell('A5').style.number_format.format_code, NumberFormat.FORMAT_PERCENTAGE_00) class TestReadBaseDateFormat(object): @classmethod def setup_class(cls): mac_wb_path = os.path.join(DATADIR, 'reader', 'date_1904.xlsx') cls.mac_wb = load_workbook(mac_wb_path) cls.mac_ws = cls.mac_wb.get_sheet_by_name('Sheet1') win_wb_path = os.path.join(DATADIR, 'reader', 'date_1900.xlsx') cls.win_wb = load_workbook(win_wb_path) cls.win_ws = cls.win_wb.get_sheet_by_name('Sheet1') def test_read_win_base_date(self): eq_(self.win_wb.properties.excel_base_date, CALENDAR_WINDOWS_1900) def test_read_mac_base_date(self): eq_(self.mac_wb.properties.excel_base_date, CALENDAR_MAC_1904) def test_read_date_style_mac(self): eq_(self.mac_ws.cell('A1').style.number_format.format_code, NumberFormat.FORMAT_DATE_XLSX14) def test_read_date_style_win(self): eq_(self.win_ws.cell('A1').style.number_format.format_code, NumberFormat.FORMAT_DATE_XLSX14) def test_read_date_value(self): datetuple = (2011, 10, 31) dt = datetime(datetuple[0], datetuple[1], datetuple[2]) eq_(self.mac_ws.cell('A1').value, dt) eq_(self.win_ws.cell('A1').value, dt) eq_(self.mac_ws.cell('A1').value, self.win_ws.cell('A1').value) def test_repair_central_directory(): from openpyxl.reader.excel import repair_central_directory, CENTRAL_DIRECTORY_SIGNATURE data_a = "foobarbaz" + CENTRAL_DIRECTORY_SIGNATURE data_b = "bazbarfoo1234567890123456890" # The repair_central_directory looks for a magic set of bytes # (CENTRAL_DIRECTORY_SIGNATURE) and strips off everything 18 bytes past the sequence f = repair_central_directory(StringIO(data_a + data_b), True) eq_(f.read(), data_a + data_b[:18]) f = repair_central_directory(StringIO(data_b), True) eq_(f.read(), data_b) def test_read_no_theme(): path = os.path.join(DATADIR, 'genuine', 'libreoffice_nrt.xlsx') wb = load_workbook(path) assert wb def test_read_contains_chartsheet(): """ Test reading workbook containing chartsheet. "contains_chartsheets.xlsx" has the following sheets: +---+------------+------------+ | # | Name | Type | +===+============+============+ | 1 | "data" | worksheet | +---+------------+------------+ | 2 | "chart" | chartsheet | +---+------------+------------+ | 3 | "moredata" | worksheet | +---+------------+------------+ """ # test data path = os.path.join(DATADIR, 'reader', 'contains_chartsheets.xlsx') wb = load_workbook(path) # workbook contains correct sheet names sheet_names = wb.get_sheet_names() eq_(sheet_names[0], 'data') eq_(sheet_names[1], 'moredata') def test_guess_types(): filename = os.path.join(DATADIR, 'genuine', 'guess_types.xlsx') for guess, dtype in ((True, float), (False, unicode)): wb = load_workbook(filename, guess_types=guess) ws = wb.get_active_sheet() assert isinstance(ws.cell('D2').value, dtype), 'wrong dtype (%s) when guess type is: %s (%s instead)' % (dtype, guess, type(ws.cell('A1').value)) openpyxl-1.7.0+ds1/openpyxl/tests/test_strings.py000066400000000000000000000052701224514475200222140ustar00rootroot00000000000000# file openpyxl/tests/test_strings.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # Python stdlib imports import os.path # 3rd party imports from nose.tools import eq_ # package imports from openpyxl.tests.helper import DATADIR from openpyxl.workbook import Workbook from openpyxl.writer.strings import create_string_table from openpyxl.reader.strings import read_string_table def test_create_string_table(): wb = Workbook() ws = wb.create_sheet() ws.cell('B12').value = 'hello' ws.cell('B13').value = 'world' ws.cell('D28').value = 'hello' table = create_string_table(wb) eq_({'hello': 0, 'world': 1}, table) def test_read_string_table(): handle = open(os.path.join(DATADIR, 'reader', 'sharedStrings.xml')) try: content = handle.read() string_table = read_string_table(content) eq_({0: 'This is cell A1 in Sheet 1', 1: 'This is cell G5'}, string_table) finally: handle.close() def test_empty_string(): handle = open(os.path.join(DATADIR, 'reader', 'sharedStrings-emptystring.xml')) try: content = handle.read() string_table = read_string_table(content) eq_({0: 'Testing empty cell', 1:''}, string_table) finally: handle.close() def test_formatted_string_table(): handle = open(os.path.join(DATADIR, 'reader', 'shared-strings-rich.xml')) try: content = handle.read() string_table = read_string_table(content) eq_({0: 'Welcome', 1: 'to the best shop in town', 2: " let's play "}, string_table) finally: handle.close()openpyxl-1.7.0+ds1/openpyxl/tests/test_style.py000066400000000000000000000461761224514475200216750ustar00rootroot00000000000000# file openpyxl/tests/test_style.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # Python stdlib imports import os.path import datetime # compatibility imports from openpyxl.shared.compat import BytesIO, StringIO # 3rd party imports from nose.tools import eq_, ok_ # package imports from openpyxl.reader.excel import load_workbook from openpyxl.tests.helper import DATADIR, assert_equals_file_content, get_xml from openpyxl.reader.style import read_style_table from openpyxl.workbook import Workbook from openpyxl.writer.excel import save_virtual_workbook from openpyxl.writer.styles import StyleWriter from openpyxl.style import NumberFormat, Border, Color, Font class TestCreateStyle(object): @classmethod def setup_class(cls): now = datetime.datetime.now() cls.workbook = Workbook() cls.worksheet = cls.workbook.create_sheet() cls.worksheet.cell(coordinate='A1').value = '12.34%' cls.worksheet.cell(coordinate='B4').value = now cls.worksheet.cell(coordinate='B5').value = now cls.worksheet.cell(coordinate='C14').value = 'This is a test' cls.worksheet.cell(coordinate='D9').value = '31.31415' cls.worksheet.cell(coordinate='D9').style.number_format.format_code = NumberFormat.FORMAT_NUMBER_00 cls.writer = StyleWriter(cls.workbook) def test_create_style_table(self): eq_(3, len(self.writer.style_table)) def test_write_style_table(self): reference_file = os.path.join(DATADIR, 'writer', 'expected', 'simple-styles.xml') #assert_equals_file_content(reference_file, self.writer.write_table()) class TestStyleWriter(object): def setup(self): self.workbook = Workbook() self.worksheet = self.workbook.create_sheet() def test_no_style(self): w = StyleWriter(self.workbook) eq_(0, len(w.style_table)) def test_nb_style(self): for i in range(1, 6): self.worksheet.cell(row=1, column=i).style.font.size += i w = StyleWriter(self.workbook) eq_(5, len(w.style_table)) self.worksheet.cell('A10').style.borders.top = Border.BORDER_THIN w = StyleWriter(self.workbook) eq_(6, len(w.style_table)) def test_style_unicity(self): for i in range(1, 6): self.worksheet.cell(row=1, column=i).style.font.bold = True w = StyleWriter(self.workbook) eq_(1, len(w.style_table)) def test_fonts(self): self.worksheet.cell('A1').style.font.size = 12 self.worksheet.cell('A1').style.font.bold = True w = StyleWriter(self.workbook) w._write_fonts() eq_(get_xml(w._root), '') def test_fonts_with_underline(self): self.worksheet.cell('A1').style.font.size = 12 self.worksheet.cell('A1').style.font.bold = True self.worksheet.cell('A1').style.font.underline = Font.UNDERLINE_SINGLE w = StyleWriter(self.workbook) w._write_fonts() eq_(get_xml(w._root), '') def test_fills(self): self.worksheet.cell('A1').style.fill.fill_type = 'solid' self.worksheet.cell('A1').style.fill.start_color.index = Color.DARKYELLOW w = StyleWriter(self.workbook) w._write_fills() eq_(get_xml(w._root), '') def test_borders(self): self.worksheet.cell('A1').style.borders.top.border_style = Border.BORDER_THIN self.worksheet.cell('A1').style.borders.top.color.index = Color.DARKYELLOW w = StyleWriter(self.workbook) w._write_borders() eq_(get_xml(w._root), '') def test_write_cell_xfs_1(self): self.worksheet.cell('A1').style.font.size = 12 w = StyleWriter(self.workbook) ft = w._write_fonts() nft = w._write_number_formats() w._write_cell_xfs(nft, ft, {}, {}) xml = get_xml(w._root) ok_('applyFont="1"' in xml) ok_('applyFill="1"' not in xml) ok_('applyBorder="1"' not in xml) ok_('applyAlignment="1"' not in xml) def test_alignment(self): self.worksheet.cell('A1').style.alignment.horizontal = 'center' self.worksheet.cell('A1').style.alignment.vertical = 'center' w = StyleWriter(self.workbook) nft = w._write_number_formats() w._write_cell_xfs(nft, {}, {}, {}) xml = get_xml(w._root) ok_('applyAlignment="1"' in xml) ok_('horizontal="center"' in xml) ok_('vertical="center"' in xml) def test_alignment_rotation(self): self.worksheet.cell('A1').style.alignment.vertical = 'center' self.worksheet.cell('A1').style.alignment.text_rotation = 90 self.worksheet.cell('A2').style.alignment.vertical = 'center' self.worksheet.cell('A2').style.alignment.text_rotation = 135 self.worksheet.cell('A3').style.alignment.text_rotation = -34 w = StyleWriter(self.workbook) nft = w._write_number_formats() w._write_cell_xfs(nft, {}, {}, {}) xml = get_xml(w._root) ok_('textRotation="90"' in xml) ok_('textRotation="135"' in xml) ok_('textRotation="124"' in xml) def test_alignment_indent(self): self.worksheet.cell('A1').style.alignment.indent = 1 self.worksheet.cell('A2').style.alignment.indent = 4 self.worksheet.cell('A3').style.alignment.indent = 0 self.worksheet.cell('A3').style.alignment.indent = -1 w = StyleWriter(self.workbook) nft = w._write_number_formats() w._write_cell_xfs(nft, {}, {}, {}) xml = get_xml(w._root) ok_('indent="1"' in xml) ok_('indent="4"' in xml) #Indents not greater than zero are ignored when writing ok_('indent="0"' not in xml) ok_('indent="-1"' not in xml) #def test_format_comparisions(): # format1 = NumberFormat() # format2 = NumberFormat() # format3 = NumberFormat() # format1.format_code = 'm/d/yyyy' # format2.format_code = 'm/d/yyyy' # format3.format_code = 'mm/dd/yyyy' # assert not format1 < format2 # assert format1 < format3 # assert format1 == format2 # assert format1 != format3 def test_builtin_format(): nFormat = NumberFormat() nFormat.format_code = '0.00' eq_(nFormat.builtin_format_code(2), nFormat._format_code) def test_read_style(): reference_file = os.path.join(DATADIR, 'reader', 'simple-styles.xml') handle = open(reference_file, 'r') try: content = handle.read() finally: handle.close() style_table = read_style_table(content) eq_(4, len(style_table)) eq_(NumberFormat._BUILTIN_FORMATS[9], style_table[1].number_format.format_code) eq_('yyyy-mm-dd', style_table[2].number_format.format_code) def test_read_complex_style(): reference_file = os.path.join(DATADIR, 'reader', 'complex-styles.xlsx') wb = load_workbook(reference_file) ws = wb.get_active_sheet() eq_(ws.column_dimensions['A'].width, 31.1640625) eq_(ws.cell('A2').style.font.name, 'Arial') eq_(ws.cell('A2').style.font.size, '10') eq_(ws.cell('A2').style.font.bold, False) eq_(ws.cell('A2').style.font.italic, False) eq_(ws.cell('A3').style.font.name, 'Arial') eq_(ws.cell('A3').style.font.size, '12') eq_(ws.cell('A3').style.font.bold, True) eq_(ws.cell('A3').style.font.italic, False) eq_(ws.cell('A4').style.font.name, 'Arial') eq_(ws.cell('A4').style.font.size, '14') eq_(ws.cell('A4').style.font.bold, False) eq_(ws.cell('A4').style.font.italic, True) eq_(ws.cell('A5').style.font.color.index, 'FF3300FF') eq_(ws.cell('A6').style.font.color.index, 'theme:9:') eq_(ws.cell('A7').style.fill.start_color.index, 'FFFFFF66') eq_(ws.cell('A8').style.fill.start_color.index, 'theme:8:') eq_(ws.cell('A9').style.alignment.horizontal, 'left') eq_(ws.cell('A10').style.alignment.horizontal, 'right') eq_(ws.cell('A11').style.alignment.horizontal, 'center') eq_(ws.cell('A12').style.alignment.vertical, 'top') eq_(ws.cell('A13').style.alignment.vertical, 'center') eq_(ws.cell('A14').style.alignment.vertical, 'bottom') eq_(ws.cell('A15').style.number_format._format_code, '0.00') eq_(ws.cell('A16').style.number_format._format_code, 'mm-dd-yy') eq_(ws.cell('A17').style.number_format._format_code, '0.00%') eq_('A18:B18' in ws._merged_cells, True) eq_(ws.cell('B18').merged, True) eq_(ws.cell('A19').style.borders.top.color.index, 'FF006600') eq_(ws.cell('A19').style.borders.bottom.color.index, 'FF006600') eq_(ws.cell('A19').style.borders.left.color.index, 'FF006600') eq_(ws.cell('A19').style.borders.right.color.index, 'FF006600') eq_(ws.cell('A21').style.borders.top.color.index, 'theme:7:') eq_(ws.cell('A21').style.borders.bottom.color.index, 'theme:7:') eq_(ws.cell('A21').style.borders.left.color.index, 'theme:7:') eq_(ws.cell('A21').style.borders.right.color.index, 'theme:7:') eq_(ws.cell('A23').style.fill.start_color.index, 'FFCCCCFF') eq_(ws.cell('A23').style.borders.top.color.index, 'theme:6:') eq_('A23:B24' in ws._merged_cells, True) eq_(ws.cell('A24').merged, True) eq_(ws.cell('B23').merged, True) eq_(ws.cell('B24').merged, True) eq_(ws.cell('A25').style.alignment.wrap_text, True) eq_(ws.cell('A26').style.alignment.shrink_to_fit, True) def test_change_existing_styles(): reference_file = os.path.join(DATADIR, 'reader', 'complex-styles.xlsx') wb = load_workbook(reference_file) ws = wb.get_active_sheet() ws.column_dimensions['A'].width = 20 ws.cell('A2').style.font.name = 'Times New Roman' ws.cell('A2').style.font.size = 12 ws.cell('A2').style.font.bold = True ws.cell('A2').style.font.italic = True ws.cell('A3').style.font.name = 'Times New Roman' ws.cell('A3').style.font.size = 14 ws.cell('A3').style.font.bold = False ws.cell('A3').style.font.italic = True ws.cell('A4').style.font.name = 'Times New Roman' ws.cell('A4').style.font.size = 16 ws.cell('A4').style.font.bold = True ws.cell('A4').style.font.italic = False ws.cell('A5').style.font.color.index = 'FF66FF66' ws.cell('A6').style.font.color.index = 'theme:1:' ws.cell('A7').style.fill.start_color.index = 'FF330066' ws.cell('A8').style.fill.start_color.index = 'theme:2:' ws.cell('A9').style.alignment.horizontal = 'center' ws.cell('A10').style.alignment.horizontal = 'left' ws.cell('A11').style.alignment.horizontal = 'right' ws.cell('A12').style.alignment.vertical = 'bottom' ws.cell('A13').style.alignment.vertical = 'top' ws.cell('A14').style.alignment.vertical = 'center' ws.cell('A15').style.number_format._format_code = '0.00%' ws.cell('A16').style.number_format._format_code = '0.00' ws.cell('A17').style.number_format._format_code = 'mm-dd-yy' ws.unmerge_cells('A18:B18') ws.cell('A19').style.borders.top.color.index = 'FF006600' ws.cell('A19').style.borders.bottom.color.index = 'FF006600' ws.cell('A19').style.borders.left.color.index = 'FF006600' ws.cell('A19').style.borders.right.color.index = 'FF006600' ws.cell('A21').style.borders.top.color.index = 'theme:7:' ws.cell('A21').style.borders.bottom.color.index = 'theme:7:' ws.cell('A21').style.borders.left.color.index = 'theme:7:' ws.cell('A21').style.borders.right.color.index = 'theme:7:' ws.cell('A23').style.fill.start_color.index = 'FFCCCCFF' ws.cell('A23').style.borders.top.color.index = 'theme:6:' ws.unmerge_cells('A23:B24') ws.cell('A25').style.alignment.wrap_text = False ws.cell('A26').style.alignment.shrink_to_fit = False saved_wb = save_virtual_workbook(wb) new_wb = load_workbook(BytesIO(saved_wb)) ws = new_wb.get_active_sheet() eq_(ws.column_dimensions['A'].width, 20.0) eq_(ws.cell('A2').style.font.name, 'Times New Roman') eq_(ws.cell('A2').style.font.size, '12') eq_(ws.cell('A2').style.font.bold, True) eq_(ws.cell('A2').style.font.italic, True) eq_(ws.cell('A3').style.font.name, 'Times New Roman') eq_(ws.cell('A3').style.font.size, '14') eq_(ws.cell('A3').style.font.bold, False) eq_(ws.cell('A3').style.font.italic, True) eq_(ws.cell('A4').style.font.name, 'Times New Roman') eq_(ws.cell('A4').style.font.size, '16') eq_(ws.cell('A4').style.font.bold, True) eq_(ws.cell('A4').style.font.italic, False) eq_(ws.cell('A5').style.font.color.index, 'FF66FF66') eq_(ws.cell('A6').style.font.color.index, 'theme:1:') eq_(ws.cell('A7').style.fill.start_color.index, 'FF330066') eq_(ws.cell('A8').style.fill.start_color.index, 'theme:2:') eq_(ws.cell('A9').style.alignment.horizontal, 'center') eq_(ws.cell('A10').style.alignment.horizontal, 'left') eq_(ws.cell('A11').style.alignment.horizontal, 'right') eq_(ws.cell('A12').style.alignment.vertical, 'bottom') eq_(ws.cell('A13').style.alignment.vertical, 'top') eq_(ws.cell('A14').style.alignment.vertical, 'center') eq_(ws.cell('A15').style.number_format._format_code, '0.00%') eq_(ws.cell('A16').style.number_format._format_code, '0.00') eq_(ws.cell('A17').style.number_format._format_code, 'mm-dd-yy') eq_('A18:B18' in ws._merged_cells, False) eq_(ws.cell('B18').merged, False) eq_(ws.cell('A19').style.borders.top.color.index, 'FF006600') eq_(ws.cell('A19').style.borders.bottom.color.index, 'FF006600') eq_(ws.cell('A19').style.borders.left.color.index, 'FF006600') eq_(ws.cell('A19').style.borders.right.color.index, 'FF006600') eq_(ws.cell('A21').style.borders.top.color.index, 'theme:7:') eq_(ws.cell('A21').style.borders.bottom.color.index, 'theme:7:') eq_(ws.cell('A21').style.borders.left.color.index, 'theme:7:') eq_(ws.cell('A21').style.borders.right.color.index, 'theme:7:') eq_(ws.cell('A23').style.fill.start_color.index, 'FFCCCCFF') eq_(ws.cell('A23').style.borders.top.color.index, 'theme:6:') eq_('A23:B24' in ws._merged_cells, False) eq_(ws.cell('A24').merged, False) eq_(ws.cell('B23').merged, False) eq_(ws.cell('B24').merged, False) eq_(ws.cell('A25').style.alignment.wrap_text, False) eq_(ws.cell('A26').style.alignment.shrink_to_fit, False) # Verify that previously duplicate styles remain the same eq_(ws.column_dimensions['C'].width, 31.1640625) eq_(ws.cell('C2').style.font.name, 'Arial') eq_(ws.cell('C2').style.font.size, '10') eq_(ws.cell('C2').style.font.bold, False) eq_(ws.cell('C2').style.font.italic, False) eq_(ws.cell('C3').style.font.name, 'Arial') eq_(ws.cell('C3').style.font.size, '12') eq_(ws.cell('C3').style.font.bold, True) eq_(ws.cell('C3').style.font.italic, False) eq_(ws.cell('C4').style.font.name, 'Arial') eq_(ws.cell('C4').style.font.size, '14') eq_(ws.cell('C4').style.font.bold, False) eq_(ws.cell('C4').style.font.italic, True) eq_(ws.cell('C5').style.font.color.index, 'FF3300FF') eq_(ws.cell('C6').style.font.color.index, 'theme:9:') eq_(ws.cell('C7').style.fill.start_color.index, 'FFFFFF66') eq_(ws.cell('C8').style.fill.start_color.index, 'theme:8:') eq_(ws.cell('C9').style.alignment.horizontal, 'left') eq_(ws.cell('C10').style.alignment.horizontal, 'right') eq_(ws.cell('C11').style.alignment.horizontal, 'center') eq_(ws.cell('C12').style.alignment.vertical, 'top') eq_(ws.cell('C13').style.alignment.vertical, 'center') eq_(ws.cell('C14').style.alignment.vertical, 'bottom') eq_(ws.cell('C15').style.number_format._format_code, '0.00') eq_(ws.cell('C16').style.number_format._format_code, 'mm-dd-yy') eq_(ws.cell('C17').style.number_format._format_code, '0.00%') eq_('C18:D18' in ws._merged_cells, True) eq_(ws.cell('D18').merged, True) eq_(ws.cell('C19').style.borders.top.color.index, 'FF006600') eq_(ws.cell('C19').style.borders.bottom.color.index, 'FF006600') eq_(ws.cell('C19').style.borders.left.color.index, 'FF006600') eq_(ws.cell('C19').style.borders.right.color.index, 'FF006600') eq_(ws.cell('C21').style.borders.top.color.index, 'theme:7:') eq_(ws.cell('C21').style.borders.bottom.color.index, 'theme:7:') eq_(ws.cell('C21').style.borders.left.color.index, 'theme:7:') eq_(ws.cell('C21').style.borders.right.color.index, 'theme:7:') eq_(ws.cell('C23').style.fill.start_color.index, 'FFCCCCFF') eq_(ws.cell('C23').style.borders.top.color.index, 'theme:6:') eq_('C23:D24' in ws._merged_cells, True) eq_(ws.cell('C24').merged, True) eq_(ws.cell('D23').merged, True) eq_(ws.cell('D24').merged, True) eq_(ws.cell('C25').style.alignment.wrap_text, True) eq_(ws.cell('C26').style.alignment.shrink_to_fit, True) def test_read_cell_style(): reference_file = os.path.join(DATADIR, 'reader', 'empty-workbook-styles.xml') handle = open(reference_file, 'r') try: content = handle.read() finally: handle.close() style_table = read_style_table(content) eq_(2, len(style_table)) openpyxl-1.7.0+ds1/openpyxl/tests/test_theme.py000066400000000000000000000030341224514475200216210ustar00rootroot00000000000000# file openpyxl/tests/test_theme.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # Python stdlib imports import os.path # package imports from openpyxl.tests.helper import DATADIR, assert_equals_file_content from openpyxl.writer.theme import write_theme def test_write_theme(): content = write_theme() assert_equals_file_content( os.path.join(DATADIR, 'writer', 'expected', 'theme1.xml'), content) openpyxl-1.7.0+ds1/openpyxl/tests/test_vba.py000066400000000000000000000055251224514475200212760ustar00rootroot00000000000000# file openpyxl/tests/test_theme.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # Python stdlib imports import os.path import zipfile # compatibility imports from openpyxl.shared.compat import BytesIO # package imports from openpyxl.tests.helper import DATADIR from openpyxl.reader.excel import load_workbook from openpyxl.writer.excel import save_virtual_workbook def test_save_vba(): path = os.path.join(DATADIR, 'reader', 'vba-test.xlsm') wb = load_workbook(path, keep_vba=True) buf = save_virtual_workbook(wb) files1 = set(zipfile.ZipFile(path, 'r').namelist()) files2 = set(zipfile.ZipFile(BytesIO(buf), 'r').namelist()) assert files1.issubset(files2), "Missing files: %s" % ', '.join(files1 - files2) def test_save_without_vba(): path = os.path.join(DATADIR, 'reader', 'vba-test.xlsm') vbFiles = set(['xl/activeX/activeX2.xml', 'xl/drawings/_rels/vmlDrawing1.vml.rels', 'xl/activeX/_rels/activeX1.xml.rels', 'xl/drawings/vmlDrawing1.vml', 'xl/activeX/activeX1.bin', 'xl/media/image1.emf', 'xl/vbaProject.bin', 'xl/activeX/_rels/activeX2.xml.rels', 'xl/worksheets/_rels/sheet1.xml.rels', 'customUI/customUI.xml', 'xl/media/image2.emf', 'xl/ctrlProps/ctrlProp1.xml', 'xl/activeX/activeX2.bin', 'xl/activeX/activeX1.xml', 'xl/ctrlProps/ctrlProp2.xml', 'xl/drawings/drawing1.xml']) wb = load_workbook(path, keep_vba=False) buf = save_virtual_workbook(wb) files1 = set(zipfile.ZipFile(path, 'r').namelist()) files2 = set(zipfile.ZipFile(BytesIO(buf), 'r').namelist()) difference = files1.difference(files2) assert difference.issubset(vbFiles), "Missing files: %s" % ', '.join(difference - vbFiles) openpyxl-1.7.0+ds1/openpyxl/tests/test_workbook.py000066400000000000000000000153531224514475200223630ustar00rootroot00000000000000# coding: utf-8 # file openpyxl/tests/test_workbook.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # 3rd party imports from nose.tools import eq_, with_setup, raises try: # For Python 3.2 and later from nose.tools import assert_is_instance except ImportError: def assert_is_instance(a, b): assert isinstance(a, b) import os.path as osp # package imports from openpyxl.workbook import Workbook from openpyxl.reader.excel import load_workbook from openpyxl.namedrange import NamedRange from openpyxl.shared.exc import ReadOnlyWorkbookException from openpyxl.tests.helper import TMPDIR, clean_tmpdir, make_tmpdir import datetime def test_get_active_sheet(): wb = Workbook() active_sheet = wb.get_active_sheet() eq_(active_sheet, wb.worksheets[0]) def test_create_sheet(): wb = Workbook() new_sheet = wb.create_sheet(0) eq_(new_sheet, wb.worksheets[0]) def test_create_sheet_with_name(): wb = Workbook() new_sheet = wb.create_sheet(0, title='LikeThisName') eq_(new_sheet, wb.worksheets[0]) def test_add_correct_sheet(): wb = Workbook() new_sheet = wb.create_sheet(0) wb.add_sheet(new_sheet) eq_(new_sheet, wb.worksheets[2]) @raises(AssertionError) def test_add_incorrect_sheet(): wb = Workbook() wb.add_sheet("Test") @raises(ReadOnlyWorkbookException) def test_create_sheet_readonly(): wb = Workbook() wb._set_optimized_read() wb.create_sheet() def test_remove_sheet(): wb = Workbook() new_sheet = wb.create_sheet(0) wb.remove_sheet(new_sheet) assert new_sheet not in wb.worksheets def test_get_sheet_by_name(): wb = Workbook() new_sheet = wb.create_sheet() title = 'my sheet' new_sheet.title = title found_sheet = wb.get_sheet_by_name(title) eq_(new_sheet, found_sheet) def test_get_index(): wb = Workbook() new_sheet = wb.create_sheet(0) sheet_index = wb.get_index(new_sheet) eq_(sheet_index, 0) def test_get_sheet_names(): wb = Workbook() names = ['Sheet', 'Sheet1', 'Sheet2', 'Sheet3', 'Sheet4', 'Sheet5'] for count in range(5): wb.create_sheet(0) actual_names = wb.get_sheet_names() eq_(sorted(actual_names), sorted(names)) def test_get_named_ranges(): wb = Workbook() eq_(wb.get_named_ranges(), wb._named_ranges) def test_add_named_range(): wb = Workbook() new_sheet = wb.create_sheet() named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) wb.add_named_range(named_range) named_ranges_list = wb.get_named_ranges() assert named_range in named_ranges_list def test_get_named_range(): wb = Workbook() new_sheet = wb.create_sheet() named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) wb.add_named_range(named_range) found_named_range = wb.get_named_range('test_nr') eq_(named_range, found_named_range) def test_remove_named_range(): wb = Workbook() new_sheet = wb.create_sheet() named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) wb.add_named_range(named_range) wb.remove_named_range(named_range) named_ranges_list = wb.get_named_ranges() assert named_range not in named_ranges_list @with_setup(setup=make_tmpdir, teardown=clean_tmpdir) def test_add_local_named_range(): wb = Workbook() new_sheet = wb.create_sheet() named_range = NamedRange('test_nr', [(new_sheet, 'A1')]) named_range.scope = new_sheet wb.add_named_range(named_range) dest_filename = osp.join(TMPDIR, 'local_named_range_book.xlsx') wb.save(dest_filename) @with_setup(setup=make_tmpdir, teardown=clean_tmpdir) def test_write_regular_date(): today = datetime.datetime(2010, 1, 18, 14, 15, 20, 1600) book = Workbook() sheet = book.get_active_sheet() sheet.cell("A1").value = today dest_filename = osp.join(TMPDIR, 'date_read_write_issue.xlsx') book.save(dest_filename) test_book = load_workbook(dest_filename) test_sheet = test_book.get_active_sheet() eq_(test_sheet.cell("A1").value, today) @with_setup(setup=make_tmpdir, teardown=clean_tmpdir) def test_write_regular_float(): float_value = 1.0 / 3.0 book = Workbook() sheet = book.get_active_sheet() sheet.cell("A1").value = float_value dest_filename = osp.join(TMPDIR, 'float_read_write_issue.xlsx') book.save(dest_filename) test_book = load_workbook(dest_filename) test_sheet = test_book.get_active_sheet() eq_(test_sheet.cell("A1").value, float_value) @raises(UnicodeDecodeError) def test_bad_encoding(): try: # Python 2 pound = unichr(163) except NameError: # Python 3 pound = chr(163) test_string = ('Compound Value (' + pound + ')').encode('latin1') utf_book = Workbook() utf_sheet = utf_book.get_active_sheet() utf_sheet.cell('A1').value = test_string def test_good_encoding(): try: # Python 2 pound = unichr(163) except NameError: # Python 3 pound = chr(163) test_string = ('Compound Value (' + pound + ')').encode('latin1') lat_book = Workbook(encoding='latin1') lat_sheet = lat_book.get_active_sheet() lat_sheet.cell('A1').value = test_string class AlternativeWorksheet(object): def __init__(self, parent_workbook, title=None): self.parent_workbook = parent_workbook if not title: title = 'AlternativeSheet' self.title = title def test_worksheet_class(): wb = Workbook(worksheet_class=AlternativeWorksheet) assert_is_instance(wb.worksheets[0], AlternativeWorksheet) @raises(AssertionError) def test_add_invalid_worksheet_class_instance(): wb = Workbook() ws = AlternativeWorksheet(parent_workbook=wb) wb.add_sheet(worksheet=ws) openpyxl-1.7.0+ds1/openpyxl/tests/test_worksheet.py000066400000000000000000000322641224514475200225410ustar00rootroot00000000000000# file openpyxl/tests/test_worksheet.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # 3rd party imports from nose.tools import eq_, raises, assert_raises # package imports from openpyxl.workbook import Workbook from openpyxl.worksheet import Worksheet, Relationship, flatten from openpyxl.writer.worksheet import write_worksheet from openpyxl.cell import Cell, coordinate_from_string from openpyxl.shared.exc import CellCoordinatesException, \ SheetTitleException, InsufficientCoordinatesException, \ NamedRangeException from openpyxl.writer.worksheet import write_worksheet class TestWorksheet(object): @classmethod def setup_class(cls): cls.wb = Workbook() def test_new_worksheet(self): ws = Worksheet(self.wb) eq_(self.wb, ws._parent) def test_new_sheet_name(self): self.wb.worksheets = [] ws = Worksheet(self.wb, title='') eq_(repr(ws), '') def test_get_cell(self): ws = Worksheet(self.wb) cell = ws.cell('A1') eq_(cell.get_coordinate(), 'A1') @raises(SheetTitleException) def test_set_bad_title(self): Worksheet(self.wb, 'X' * 50) def test_set_bad_title_character(self): assert_raises(SheetTitleException, Worksheet, self.wb, '[') assert_raises(SheetTitleException, Worksheet, self.wb, ']') assert_raises(SheetTitleException, Worksheet, self.wb, '*') assert_raises(SheetTitleException, Worksheet, self.wb, ':') assert_raises(SheetTitleException, Worksheet, self.wb, '?') assert_raises(SheetTitleException, Worksheet, self.wb, '/') assert_raises(SheetTitleException, Worksheet, self.wb, '\\') def test_worksheet_dimension(self): ws = Worksheet(self.wb) eq_('A1:A1', ws.calculate_dimension()) ws.cell('B12').value = 'AAA' eq_('A1:B12', ws.calculate_dimension()) def test_worksheet_range(self): ws = Worksheet(self.wb) xlrange = ws.range('A1:C4') assert isinstance(xlrange, tuple) eq_(4, len(xlrange)) eq_(3, len(xlrange[0])) def test_worksheet_named_range(self): ws = Worksheet(self.wb) self.wb.create_named_range('test_range', ws, 'C5') xlrange = ws.range('test_range') assert isinstance(xlrange, Cell) eq_(5, xlrange.row) @raises(NamedRangeException) def test_bad_named_range(self): ws = Worksheet(self.wb) ws.range('bad_range') @raises(NamedRangeException) def test_named_range_wrong_sheet(self): ws1 = Worksheet(self.wb) ws2 = Worksheet(self.wb) self.wb.create_named_range('wrong_sheet_range', ws1, 'C5') ws2.range('wrong_sheet_range') def test_cell_offset(self): ws = Worksheet(self.wb) eq_('C17', ws.cell('B15').offset(2, 1).get_coordinate()) def test_range_offset(self): ws = Worksheet(self.wb) xlrange = ws.range('A1:C4', 1, 3) assert isinstance(xlrange, tuple) eq_(4, len(xlrange)) eq_(3, len(xlrange[0])) eq_('D2', xlrange[0][0].get_coordinate()) def test_cell_alternate_coordinates(self): ws = Worksheet(self.wb) cell = ws.cell(row=8, column=4) eq_('E9', cell.get_coordinate()) @raises(InsufficientCoordinatesException) def test_cell_insufficient_coordinates(self): ws = Worksheet(self.wb) cell = ws.cell(row=8) def test_cell_range_name(self): ws = Worksheet(self.wb) self.wb.create_named_range('test_range_single', ws, 'B12') assert_raises(CellCoordinatesException, ws.cell, 'test_range_single') c_range_name = ws.range('test_range_single') c_range_coord = ws.range('B12') c_cell = ws.cell('B12') eq_(c_range_coord, c_range_name) eq_(c_range_coord, c_cell) def test_garbage_collect(self): ws = Worksheet(self.wb) ws.cell('A1').value = '' ws.cell('B2').value = '0' ws.cell('C4').value = 0 ws.garbage_collect() eq_(set(ws.get_cell_collection()), set([ws.cell('B2'), ws.cell('C4')])) def test_hyperlink_relationships(self): ws = Worksheet(self.wb) eq_(len(ws.relationships), 0) ws.cell('A1').hyperlink = "http://test.com" eq_(len(ws.relationships), 1) eq_("rId1", ws.cell('A1').hyperlink_rel_id) eq_("rId1", ws.relationships[0].id) eq_("http://test.com", ws.relationships[0].target) eq_("External", ws.relationships[0].target_mode) ws.cell('A2').hyperlink = "http://test2.com" eq_(len(ws.relationships), 2) eq_("rId2", ws.cell('A2').hyperlink_rel_id) eq_("rId2", ws.relationships[1].id) eq_("http://test2.com", ws.relationships[1].target) eq_("External", ws.relationships[1].target_mode) @raises(ValueError) def test_bad_relationship_type(self): rel = Relationship('bad_type') def test_append_list(self): ws = Worksheet(self.wb) ws.append(['This is A1', 'This is B1']) eq_('This is A1', ws.cell('A1').value) eq_('This is B1', ws.cell('B1').value) def test_append_dict_letter(self): ws = Worksheet(self.wb) ws.append({'A' : 'This is A1', 'C' : 'This is C1'}) eq_('This is A1', ws.cell('A1').value) eq_('This is C1', ws.cell('C1').value) def test_append_dict_index(self): ws = Worksheet(self.wb) ws.append({0 : 'This is A1', 2 : 'This is C1'}) eq_('This is A1', ws.cell('A1').value) eq_('This is C1', ws.cell('C1').value) @raises(TypeError) def test_bad_append(self): ws = Worksheet(self.wb) ws.append("test") def test_append_2d_list(self): ws = Worksheet(self.wb) ws.append(['This is A1', 'This is B1']) ws.append(['This is A2', 'This is B2']) vals = ws.range('A1:B2') eq_((('This is A1', 'This is B1'), ('This is A2', 'This is B2'),), flatten(vals)) def test_rows(self): ws = Worksheet(self.wb) ws.cell('A1').value = 'first' ws.cell('C9').value = 'last' rows = ws.rows eq_(len(rows), 9) eq_(rows[0][0].value, 'first') eq_(rows[-1][-1].value, 'last') def test_cols(self): ws = Worksheet(self.wb) ws.cell('A1').value = 'first' ws.cell('C9').value = 'last' cols = ws.columns eq_(len(cols), 3) eq_(cols[0][0].value, 'first') eq_(cols[-1][-1].value, 'last') def test_auto_filter(self): ws = Worksheet(self.wb) ws.auto_filter = ws.range('a1:f1') assert ws.auto_filter == 'A1:F1' ws.auto_filter = '' assert ws.auto_filter is None ws.auto_filter = 'c1:g9' assert ws.auto_filter == 'C1:G9' def test_page_margins(self): ws = Worksheet(self.wb) ws.page_margins.left = 2.0 ws.page_margins.right = 2.0 ws.page_margins.top = 2.0 ws.page_margins.bottom = 2.0 ws.page_margins.header = 1.5 ws.page_margins.footer = 1.5 xml_string = write_worksheet(ws, None, None) assert '' in xml_string ws = Worksheet(self.wb) xml_string = write_worksheet(ws, None, None) assert 'Cell B1' in xml_string ws.merge_cells('A1:B1') xml_string = write_worksheet(ws, string_table, None) assert 'Cell B1' not in xml_string assert '' in xml_string ws.unmerge_cells('A1:B1') xml_string = write_worksheet(ws, string_table, None) assert '' not in xml_string def test_freeze(self): ws = Worksheet(self.wb) ws.freeze_panes = ws.cell('b2') assert ws.freeze_panes == 'B2' ws.freeze_panes = '' assert ws.freeze_panes is None ws.freeze_panes = 'c5' assert ws.freeze_panes == 'C5' ws.freeze_panes = ws.cell('A1') assert ws.freeze_panes is None def test_printer_settings(self): ws = Worksheet(self.wb) ws.page_setup.orientation = ws.ORIENTATION_LANDSCAPE ws.page_setup.paperSize = ws.PAPERSIZE_TABLOID ws.page_setup.fitToPage = True ws.page_setup.fitToHeight = 0 ws.page_setup.fitToWidth = 1 ws.page_setup.horizontalCentered = True ws.page_setup.verticalCentered = True xml_string = write_worksheet(ws, None, None) assert '' in xml_string assert '' in xml_string assert '' in xml_string ws = Worksheet(self.wb) xml_string = write_worksheet(ws, None, None) assert "' in xml_string assert '&L&"Calibri,Regular"&K000000Left Header Text&C&"Arial,Regular"&6&K445566Center Header Text&R&"Arial,Bold"&8&K112233Right Header Text' in xml_string assert '&L&"Times New Roman,Regular"&10&K445566Left Footer Text_x000D_And &D and &T&C&"Times New Roman,Bold"&12&K778899Center Footer Text &Z&F on &A&R&"Times New Roman,Italic"&14&KAABBCCRight Footer Text &P of &N' in xml_string assert '' in xml_string ws = Worksheet(self.wb) xml_string = write_worksheet(ws, None, None) assert "" not in xml_string assert "" not in xml_string assert "" not in xml_string class TestPositioning(object): def test_point(self): wb = Workbook() ws = wb.get_active_sheet() eq_(ws.point_pos(top=40, left=150), ('C', 3)) def test_roundtrip(self): wb = Workbook() ws = wb.get_active_sheet() for address in ('A1', 'D52', 'X11'): eq_(ws.point_pos(*ws.cell(address).anchor), coordinate_from_string(address)) openpyxl-1.7.0+ds1/openpyxl/tests/test_write.py000066400000000000000000000202421224514475200216510ustar00rootroot00000000000000# file openpyxl/tests/test_write.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file # stdlib imports import decimal import os.path # compatibility imports from openpyxl.shared.compat import BytesIO, StringIO # 3rd party imports from nose.tools import eq_, with_setup, raises # package imports from openpyxl.tests.helper import TMPDIR, DATADIR, \ assert_equals_file_content, clean_tmpdir, make_tmpdir from openpyxl.workbook import Workbook from openpyxl.reader.excel import load_workbook from openpyxl.writer.excel import save_workbook, save_virtual_workbook, \ ExcelWriter from openpyxl.writer.workbook import write_workbook, write_workbook_rels from openpyxl.writer.worksheet import write_worksheet, write_worksheet_rels from openpyxl.writer.strings import write_string_table from openpyxl.writer.styles import StyleWriter @with_setup(setup = make_tmpdir, teardown = clean_tmpdir) def test_write_empty_workbook(): wb = Workbook() dest_filename = os.path.join(TMPDIR, 'empty_book.xlsx') save_workbook(wb, dest_filename) assert os.path.isfile(dest_filename) def test_write_virtual_workbook(): old_wb = Workbook() saved_wb = save_virtual_workbook(old_wb) new_wb = load_workbook(BytesIO(saved_wb)) assert new_wb def test_write_workbook_rels(): wb = Workbook() content = write_workbook_rels(wb) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'workbook.xml.rels'), content) def test_write_workbook(): wb = Workbook() content = write_workbook(wb) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'workbook.xml'), content) def test_write_string_table(): table = {'hello': 1, 'world': 2, 'nice': 3} content = write_string_table(table) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'sharedStrings.xml'), content) def test_write_worksheet(): wb = Workbook() ws = wb.create_sheet() ws.cell('F42').value = 'hello' content = write_worksheet(ws, {'hello': 0}, {}) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'sheet1.xml'), content) def test_write_hidden_worksheet(): wb = Workbook() ws = wb.create_sheet() ws.sheet_state = ws.SHEETSTATE_HIDDEN ws.cell('F42').value = 'hello' content = write_worksheet(ws, {'hello': 0}, {}) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'sheet1.xml'), content) def test_write_bool(): wb = Workbook() ws = wb.create_sheet() ws.cell('F42').value = False ws.cell('F43').value = True content = write_worksheet(ws, {}, {}) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'sheet1_bool.xml'), content) def test_write_formula(): wb = Workbook() ws = wb.create_sheet() ws.cell('F1').value = 10 ws.cell('F2').value = 32 ws.cell('F3').value = '=F1+F2' content = write_worksheet(ws, {}, {}) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'sheet1_formula.xml'), content) def test_write_style(): wb = Workbook() ws = wb.create_sheet() ws.cell('F1').value = '13%' ws.column_dimensions['F'].style_index = 2 style_id_by_hash = StyleWriter(wb).get_style_by_hash() content = write_worksheet(ws, {}, style_id_by_hash) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'sheet1_style.xml'), content) def test_write_height(): wb = Workbook() ws = wb.create_sheet() ws.cell('F1').value = 10 ws.row_dimensions[ws.cell('F1').row].height = 30 content = write_worksheet(ws, {}, {}) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'sheet1_height.xml'), content) def test_write_hyperlink(): wb = Workbook() ws = wb.create_sheet() ws.cell('A1').value = "test" ws.cell('A1').hyperlink = "http://test.com" content = write_worksheet(ws, {'test': 0}, {}) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'sheet1_hyperlink.xml'), content) def test_write_hyperlink_rels(): wb = Workbook() ws = wb.create_sheet() eq_(0, len(ws.relationships)) ws.cell('A1').value = "test" ws.cell('A1').hyperlink = "http://test.com/" eq_(1, len(ws.relationships)) ws.cell('A2').value = "test" ws.cell('A2').hyperlink = "http://test2.com/" eq_(2, len(ws.relationships)) content = write_worksheet_rels(ws, 1) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'sheet1_hyperlink.xml.rels'), content) def test_hyperlink_value(): wb = Workbook() ws = wb.create_sheet() ws.cell('A1').hyperlink = "http://test.com" eq_("http://test.com", ws.cell('A1').value) ws.cell('A1').value = "test" eq_("test", ws.cell('A1').value) def test_write_auto_filter(): wb = Workbook() ws = wb.worksheets[0] ws.cell('F42').value = 'hello' ws.auto_filter = 'A1:F1' content = write_worksheet(ws, {'hello': 0}, {}) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'sheet1_auto_filter.xml'), content) content = write_workbook(wb) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'workbook_auto_filter.xml'), content) def test_freeze_panes_horiz(): wb = Workbook() ws = wb.create_sheet() ws.cell('F42').value = 'hello' ws.freeze_panes = 'A4' content = write_worksheet(ws, {'hello': 0}, {}) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'sheet1_freeze_panes_horiz.xml'), content) def test_freeze_panes_vert(): wb = Workbook() ws = wb.create_sheet() ws.cell('F42').value = 'hello' ws.freeze_panes = 'D1' content = write_worksheet(ws, {'hello': 0}, {}) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'sheet1_freeze_panes_vert.xml'), content) pass def test_freeze_panes_both(): wb = Workbook() ws = wb.create_sheet() ws.cell('F42').value = 'hello' ws.freeze_panes = 'D4' content = write_worksheet(ws, {'hello': 0}, {}) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'sheet1_freeze_panes_both.xml'), content) def test_long_number(): wb = Workbook() ws = wb.create_sheet() ws.cell('A1').value = 9781231231230 content = write_worksheet(ws, {}, {}) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'long_number.xml'), content) def test_decimal(): wb = Workbook() ws = wb.create_sheet() ws.cell('A1').value = decimal.Decimal('3.14') content = write_worksheet(ws, {}, {}) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'decimal.xml'), content) def test_short_number(): wb = Workbook() ws = wb.create_sheet() ws.cell('A1').value = 1234567890 content = write_worksheet(ws, {}, {}) assert_equals_file_content(os.path.join(DATADIR, 'writer', 'expected', \ 'short_number.xml'), content) openpyxl-1.7.0+ds1/openpyxl/workbook.py000066400000000000000000000201131224514475200201500ustar00rootroot00000000000000# file openpyxl/workbook.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Workbook is the top-level container for all document information.""" __docformat__ = "restructuredtext en" # Python stdlib imports import datetime import os import threading # package imports from openpyxl.worksheet import Worksheet from openpyxl.writer.dump_worksheet import DumpWorksheet, save_dump from openpyxl.writer.strings import StringTableBuilder from openpyxl.namedrange import NamedRange from openpyxl.style import Style from openpyxl.writer.excel import save_workbook from openpyxl.shared.exc import ReadOnlyWorkbookException from openpyxl.shared.date_time import CALENDAR_WINDOWS_1900, CALENDAR_MAC_1904 from openpyxl.shared.xmltools import fromstring, QName from openpyxl.shared.ooxml import NAMESPACES class DocumentProperties(object): """High-level properties of the document.""" def __init__(self): self.creator = 'Unknown' self.last_modified_by = self.creator self.created = datetime.datetime.now() self.modified = datetime.datetime.now() self.title = 'Untitled' self.subject = '' self.description = '' self.keywords = '' self.category = '' self.company = 'Microsoft Corporation' self.excel_base_date = CALENDAR_WINDOWS_1900 class DocumentSecurity(object): """Security information about the document.""" def __init__(self): self.lock_revision = False self.lock_structure = False self.lock_windows = False self.revision_password = '' self.workbook_password = '' class Workbook(object): """Workbook is the container for all other parts of the document.""" def __init__(self, optimized_write=False, encoding='utf-8', worksheet_class=Worksheet, optimized_worksheet_class=DumpWorksheet, guess_types=True): self.worksheets = [] self._active_sheet_index = 0 self._named_ranges = [] self.properties = DocumentProperties() self.style = Style() self.security = DocumentSecurity() self.__optimized_write = optimized_write self.__optimized_read = False self.__thread_local_data = threading.local() self.strings_table_builder = StringTableBuilder() self.loaded_theme = None self._worksheet_class = worksheet_class self._optimized_worksheet_class = optimized_worksheet_class self.vba_archive = None self._guess_types = guess_types self.encoding = encoding if not optimized_write: self.worksheets.append(self._worksheet_class(parent_workbook=self)) def read_workbook_settings(self, xml_source): root = fromstring(xml_source) view = root.find('*/' + QName(NAMESPACES['main'], 'workbookView').text) if 'activeTab' in view.attrib: self._active_sheet_index = int(view.attrib['activeTab']) @property def _local_data(self): return self.__thread_local_data @property def excel_base_date(self): return self.properties.excel_base_date def _set_optimized_read(self): self.__optimized_read = True def get_active_sheet(self): """Returns the current active sheet.""" return self.worksheets[self._active_sheet_index] def create_sheet(self, index=None, title=None): """Create a worksheet (at an optional index). :param index: optional position at which the sheet will be inserted :type index: int """ if self.__optimized_read: raise ReadOnlyWorkbookException('Cannot create new sheet in a read-only workbook') if self.__optimized_write : new_ws = self._optimized_worksheet_class( parent_workbook=self, title=title) else: if title is not None: new_ws = self._worksheet_class( parent_workbook=self, title=title) else: new_ws = self._worksheet_class(parent_workbook=self) self.add_sheet(worksheet=new_ws, index=index) return new_ws def add_sheet(self, worksheet, index=None): """Add an existing worksheet (at an optional index).""" assert isinstance(worksheet, self._worksheet_class), "The parameter you have given is not of the type '%s'" % self._worksheet_class.__name__ if index is None: index = len(self.worksheets) self.worksheets.insert(index, worksheet) def remove_sheet(self, worksheet): """Remove a worksheet from this workbook.""" self.worksheets.remove(worksheet) def get_sheet_by_name(self, name): """Returns a worksheet by its name. Returns None if no worksheet has the name specified. :param name: the name of the worksheet to look for :type name: string """ requested_sheet = None for sheet in self.worksheets: if sheet.title == name: requested_sheet = sheet break return requested_sheet def get_index(self, worksheet): """Return the index of the worksheet.""" return self.worksheets.index(worksheet) def get_sheet_names(self): """Returns the list of the names of worksheets in the workbook. Names are returned in the worksheets order. :rtype: list of strings """ return [s.title for s in self.worksheets] def create_named_range(self, name, worksheet, range, scope=None): """Create a new named_range on a worksheet""" assert isinstance(worksheet, self._worksheet_class) named_range = NamedRange(name, [(worksheet, range)], scope) self.add_named_range(named_range) def get_named_ranges(self): """Return all named ranges""" return self._named_ranges def add_named_range(self, named_range): """Add an existing named_range to the list of named_ranges.""" self._named_ranges.append(named_range) def get_named_range(self, name): """Return the range specified by name.""" requested_range = None for named_range in self._named_ranges: if named_range.name == name: requested_range = named_range break return requested_range def remove_named_range(self, named_range): """Remove a named_range from this workbook.""" self._named_ranges.remove(named_range) def save(self, filename): """Save the current workbook under the given `filename`. Use this function instead of using an `ExcelWriter`. .. warning:: When creating your workbook using `optimized_write` set to True, you will only be able to call this function once. Subsequents attempts to modify or save the file will raise an :class:`openpyxl.shared.exc.WorkbookAlreadySaved` exception. """ if self.__optimized_write: save_dump(self, filename) else: save_workbook(self, filename) openpyxl-1.7.0+ds1/openpyxl/worksheet.py000066400000000000000000001015331224514475200203340ustar00rootroot00000000000000# file openpyxl/worksheet.py from openpyxl.shared.units import points_to_pixels from openpyxl.shared import DEFAULT_COLUMN_WIDTH, DEFAULT_ROW_HEIGHT # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Worksheet is the 2nd-level container in Excel.""" # Python stdlib imports import re # package imports import openpyxl.cell from openpyxl.cell import coordinate_from_string, \ column_index_from_string, get_column_letter from openpyxl.shared.exc import SheetTitleException, \ InsufficientCoordinatesException, CellCoordinatesException, \ NamedRangeException from openpyxl.shared.password_hasher import hash_password from openpyxl.style import Style, DEFAULTS as DEFAULTS_STYLE from openpyxl.drawing import Drawing from openpyxl.namedrange import NamedRangeContainingValue from openpyxl.shared.compat import OrderedDict, unicode, xrange, basestring from openpyxl.shared.compat.itertools import iteritems _DEFAULTS_STYLE_HASH = hash(DEFAULTS_STYLE) def flatten(results): rows = [] for row in results: cells = [] for cell in row: cells.append(cell.value) rows.append(tuple(cells)) return tuple(rows) class Relationship(object): """Represents many kinds of relationships.""" # TODO: Use this object for workbook relationships as well as # worksheet relationships TYPES = { 'hyperlink': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', 'drawing':'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing', 'image':'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing' # 'worksheet': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet', # 'sharedStrings': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings', # 'styles': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles', # 'theme': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme', } def __init__(self, rel_type): if rel_type not in self.TYPES: raise ValueError("Invalid relationship type %s" % rel_type) self.type = self.TYPES[rel_type] self.target = "" self.target_mode = "" self.id = "" class PageSetup(object): """Information about page layout for this sheet""" valid_setup = ("orientation", "paperSize", "scale", "fitToPage", "fitToHeight", "fitToWidth", "firstPageNumber", "useFirstPageNumber") valid_options = ("horizontalCentered", "verticalCentered") def __init__(self): self.orientation = self.paperSize = self.scale = self.fitToPage = self.fitToHeight = self.fitToWidth = self.firstPageNumber = self.useFirstPageNumber = None self.horizontalCentered = self.verticalCentered = None @property def setup(self): setupGroup = OrderedDict() for setup_name in self.valid_setup: setup_value = getattr(self, setup_name) if setup_value is not None: if setup_name == 'orientation': setupGroup[setup_name] = '%s' % setup_value elif setup_name in ('paperSize', 'scale'): setupGroup[setup_name] = '%d' % int(setup_value) elif setup_name in ('fitToHeight', 'fitToWidth') and int(setup_value) >= 0: setupGroup[setup_name] = '%d' % int(setup_value) return setupGroup @property def options(self): optionsGroup = OrderedDict() for options_name in self.valid_options: options_value = getattr(self, options_name) if options_value is not None: if options_name in ('horizontalCentered', 'verticalCentered') and options_value: optionsGroup[options_name] = '1' return optionsGroup class HeaderFooterItem(object): """Individual left/center/right header/footer items Header & Footer ampersand codes: * &A Inserts the worksheet name * &B Toggles bold * &D or &[Date] Inserts the current date * &E Toggles double-underline * &F or &[File] Inserts the workbook name * &I Toggles italic * &N or &[Pages] Inserts the total page count * &S Toggles strikethrough * &T Inserts the current time * &[Tab] Inserts the worksheet name * &U Toggles underline * &X Toggles superscript * &Y Toggles subscript * &P or &[Page] Inserts the current page number * &P+n Inserts the page number incremented by n * &P-n Inserts the page number decremented by n * &[Path] Inserts the workbook path * && Escapes the ampersand character * &"fontname" Selects the named font * &nn Selects the specified 2-digit font point size """ CENTER = 'C' LEFT = 'L' RIGHT = 'R' REPLACE_LIST = ( ('\n', '_x000D_'), ('&[Page]', '&P'), ('&[Pages]', '&N'), ('&[Date]', '&D'), ('&[Time]', '&T'), ('&[Path]', '&Z'), ('&[File]', '&F'), ('&[Tab]', '&A'), ('&[Picture]', '&G') ) __slots__ = ('type', 'font_name', 'font_size', 'font_color', 'text') def __init__(self, type): self.type = type self.font_name = "Calibri,Regular" self.font_size = None self.font_color = "000000" self.text = None def has(self): return True if self.text else False def get(self): t = [] if self.text: t.append('&%s' % self.type) t.append('&"%s"' % self.font_name) if self.font_size: t.append('&%d' % self.font_size) t.append('&K%s' % self.font_color) text = self.text for old, new in self.REPLACE_LIST: text = text.replace(old, new) t.append(text) return ''.join(t) def set(self, itemArray): textArray = [] for item in itemArray[1:]: if len(item) and textArray: textArray.append('&%s' % item) elif len(item) and not textArray: if item[0] == '"': self.font_name = item.replace('"', '') elif item[0] == 'K': self.font_color = item[1:7] textArray.append(item[7:]) else: try: self.font_size = int(item) except: textArray.append('&%s' % item) self.text = ''.join(textArray) class HeaderFooter(object): """Information about the header/footer for this sheet. """ __slots__ = ('left_header', 'center_header', 'right_header', 'left_footer', 'center_footer', 'right_footer') def __init__(self): self.left_header = HeaderFooterItem(HeaderFooterItem.LEFT) self.center_header = HeaderFooterItem(HeaderFooterItem.CENTER) self.right_header = HeaderFooterItem(HeaderFooterItem.RIGHT) self.left_footer = HeaderFooterItem(HeaderFooterItem.LEFT) self.center_footer = HeaderFooterItem(HeaderFooterItem.CENTER) self.right_footer = HeaderFooterItem(HeaderFooterItem.RIGHT) def hasHeader(self): return True if self.left_header.has() or self.center_header.has() or self.right_header.has() else False def hasFooter(self): return True if self.left_footer.has() or self.center_footer.has() or self.right_footer.has() else False def getHeader(self): t = [] if self.left_header.has(): t.append(self.left_header.get()) if self.center_header.has(): t.append(self.center_header.get()) if self.right_header.has(): t.append(self.right_header.get()) return ''.join(t) def getFooter(self): t = [] if self.left_footer.has(): t.append(self.left_footer.get()) if self.center_footer.has(): t.append(self.center_footer.get()) if self.right_footer.has(): t.append(self.right_footer.get()) return ''.join(t) def setHeader(self, item): itemArray = [i.replace('#DOUBLEAMP#', '&&') for i in item.replace('&&', '#DOUBLEAMP#').split('&')] l = itemArray.index('L') if 'L' in itemArray else None c = itemArray.index('C') if 'C' in itemArray else None r = itemArray.index('R') if 'R' in itemArray else None if l: if c: self.left_header.set(itemArray[l:c]) elif r: self.left_header.set(itemArray[l:r]) else: self.left_header.set(itemArray[l:]) if c: if r: self.center_header.set(itemArray[c:r]) else: self.center_header.set(itemArray[c:]) if r: self.right_header.set(itemArray[r:]) def setFooter(self, item): itemArray = [i.replace('#DOUBLEAMP#', '&&') for i in item.replace('&&', '#DOUBLEAMP#').split('&')] l = itemArray.index('L') if 'L' in itemArray else None c = itemArray.index('C') if 'C' in itemArray else None r = itemArray.index('R') if 'R' in itemArray else None if l: if c: self.left_footer.set(itemArray[l:c]) elif r: self.left_footer.set(itemArray[l:r]) else: self.left_footer.set(itemArray[l:]) if c: if r: self.center_footer.set(itemArray[c:r]) else: self.center_footer.set(itemArray[c:]) if r: self.right_footer.set(itemArray[r:]) class SheetView(object): """Information about the visible portions of this sheet.""" pass class RowDimension(object): """Information about the display properties of a row.""" __slots__ = ('row_index', 'height', 'visible', 'outline_level', 'collapsed', 'style_index',) def __init__(self, index=0): self.row_index = index self.height = -1 self.visible = True self.outline_level = 0 self.collapsed = False self.style_index = None class ColumnDimension(object): """Information about the display properties of a column.""" __slots__ = ('column_index', 'width', 'auto_size', 'visible', 'outline_level', 'collapsed', 'style_index',) def __init__(self, index='A'): self.column_index = index self.width = -1 self.auto_size = False self.visible = True self.outline_level = 0 self.collapsed = False self.style_index = 0 class PageMargins(object): """Information about page margins for view/print layouts.""" valid_margins = ("left", "right", "top", "bottom", "header", "footer") def __init__(self): self.left = self.right = self.top = self.bottom = self.header = self.footer = None @property def margins(self): margins = OrderedDict() for margin_name in self.valid_margins: margin_value = getattr(self, margin_name) if margin_value: margins[margin_name] = "%0.2f" % margin_value return margins class SheetProtection(object): """Information about protection of various aspects of a sheet.""" def __init__(self): self.sheet = False self.objects = False self.scenarios = False self.format_cells = False self.format_columns = False self.format_rows = False self.insert_columns = False self.insert_rows = False self.insert_hyperlinks = False self.delete_columns = False self.delete_rows = False self.select_locked_cells = False self.sort = False self.auto_filter = False self.pivot_tables = False self.select_unlocked_cells = False self._password = '' def set_password(self, value='', already_hashed=False): """Set a password on this sheet.""" if not already_hashed: value = hash_password(value) self._password = value def _set_raw_password(self, value): """Set a password directly, forcing a hash step.""" self.set_password(value, already_hashed=False) def _get_raw_password(self): """Return the password value, regardless of hash.""" return self._password password = property(_get_raw_password, _set_raw_password, 'get/set the password (if already hashed, ' 'use set_password() instead)') class Worksheet(object): """Represents a worksheet. Do not create worksheets yourself, use :func:`openpyxl.workbook.Workbook.create_sheet` instead """ repr_format = unicode('') BREAK_NONE = 0 BREAK_ROW = 1 BREAK_COLUMN = 2 SHEETSTATE_VISIBLE = 'visible' SHEETSTATE_HIDDEN = 'hidden' SHEETSTATE_VERYHIDDEN = 'veryHidden' # Paper size PAPERSIZE_LETTER = '1' PAPERSIZE_LETTER_SMALL = '2' PAPERSIZE_TABLOID = '3' PAPERSIZE_LEDGER = '4' PAPERSIZE_LEGAL = '5' PAPERSIZE_STATEMENT = '6' PAPERSIZE_EXECUTIVE = '7' PAPERSIZE_A3 = '8' PAPERSIZE_A4 = '9' PAPERSIZE_A4_SMALL = '10' PAPERSIZE_A5 = '11' # Page orientation ORIENTATION_PORTRAIT = 'portrait' ORIENTATION_LANDSCAPE = 'landscape' def __init__(self, parent_workbook, title='Sheet'): self._parent = parent_workbook self._title = '' if not title: self.title = 'Sheet%d' % (1 + len(self._parent.worksheets)) else: self.title = title self.row_dimensions = {} self.column_dimensions = {} self.page_breaks = [] self._cells = {} self._styles = {} self._charts = [] self._images = [] self._merged_cells = [] self.relationships = [] self._data_validations = [] self.selected_cell = 'A1' self.active_cell = 'A1' self.sheet_state = self.SHEETSTATE_VISIBLE self.page_setup = PageSetup() self.page_margins = PageMargins() self.header_footer = HeaderFooter() self.sheet_view = SheetView() self.protection = SheetProtection() self.show_gridlines = True self.print_gridlines = False self.show_summary_below = True self.show_summary_right = True self.default_row_dimension = RowDimension() self.default_column_dimension = ColumnDimension() self._auto_filter = None self._freeze_panes = None self.paper_size = None self.orientation = None self.xml_source = None def __repr__(self): return self.repr_format % self.title @property def parent(self): return self._parent @property def encoding(self): return self._parent.encoding def garbage_collect(self): """Delete cells that are not storing a value.""" delete_list = [coordinate for coordinate, cell in \ iteritems(self._cells) if (not cell.merged and cell.value in ('', None) and \ (coordinate not in self._styles or hash(cell.style) == _DEFAULTS_STYLE_HASH))] for coordinate in delete_list: del self._cells[coordinate] def get_cell_collection(self): """Return an unordered list of the cells in this worksheet.""" return self._cells.values() def _set_title(self, value): """Set a sheet title, ensuring it is valid.""" bad_title_char_re = re.compile(r'[\\*?:/\[\]]') if bad_title_char_re.search(value): msg = 'Invalid character found in sheet title' raise SheetTitleException(msg) # check if sheet_name already exists # do this *before* length check if self._parent.get_sheet_by_name(value): # use name, but append with lowest possible integer i = 1 while self._parent.get_sheet_by_name('%s%d' % (value, i)): i += 1 value = '%s%d' % (value, i) if len(value) > 31: msg = 'Maximum 31 characters allowed in sheet title' raise SheetTitleException(msg) self._title = value def _get_title(self): """Return the title for this sheet.""" return self._title title = property(_get_title, _set_title, doc= 'Get or set the title of the worksheet. ' 'Limited to 31 characters, no special characters.') def _set_auto_filter(self, range): # Normalize range to a str or None if not range: range = None elif isinstance(range, str): range = range.upper() else: # Assume a range range = range[0][0].address + ':' + range[-1][-1].address self._auto_filter = range def _get_auto_filter(self): return self._auto_filter auto_filter = property(_get_auto_filter, _set_auto_filter, doc= 'get or set auto filtering on columns') def _set_freeze_panes(self, topLeftCell): if not topLeftCell: topLeftCell = None elif isinstance(topLeftCell, str): topLeftCell = topLeftCell.upper() else: # Assume a cell topLeftCell = topLeftCell.address if topLeftCell == 'A1': topLeftCell = None self._freeze_panes = topLeftCell def _get_freeze_panes(self): return self._freeze_panes freeze_panes = property(_get_freeze_panes, _set_freeze_panes, doc= "Get or set frozen panes") def add_print_title(self, n, rows_or_cols='rows'): """ Print Titles are rows or columns that are repeated on each printed sheet. This adds n rows or columns at the top or left of the sheet """ if rows_or_cols == 'cols': r = '$A:$%s' % get_column_letter(n) else: r = '$1:$%d' % n self.parent.create_named_range('_xlnm.Print_Titles', self, r, self) def cell(self, coordinate=None, row=None, column=None): """Returns a cell object based on the given coordinates. Usage: cell(coodinate='A15') **or** cell(row=15, column=1) If `coordinates` are not given, then row *and* column must be given. Cells are kept in a dictionary which is empty at the worksheet creation. Calling `cell` creates the cell in memory when they are first accessed, to reduce memory usage. :param coordinate: coordinates of the cell (e.g. 'B12') :type coordinate: string :param row: row index of the cell (e.g. 4) :type row: int :param column: column index of the cell (e.g. 3) :type column: int :raise: InsufficientCoordinatesException when coordinate or (row and column) are not given :rtype: :class:`openpyxl.cell.Cell` """ if not coordinate: if (row is None or column is None): msg = "You have to provide a value either for " \ "'coordinate' or for 'row' *and* 'column'" raise InsufficientCoordinatesException(msg) else: coordinate = '%s%s' % (get_column_letter(column + 1), row + 1) else: coordinate = coordinate.replace('$', '') return self._get_cell(coordinate) def _get_cell(self, coordinate): if not coordinate in self._cells: column, row = coordinate_from_string(coordinate) new_cell = openpyxl.cell.Cell(self, column, row) self._cells[coordinate] = new_cell if column not in self.column_dimensions: self.column_dimensions[column] = ColumnDimension(column) if row not in self.row_dimensions: self.row_dimensions[row] = RowDimension(row) return self._cells[coordinate] def get_highest_row(self): """Returns the maximum row index containing data :rtype: int """ if self.row_dimensions: return max(self.row_dimensions.keys()) else: return 1 def get_highest_column(self): """Get the largest value for column currently stored. :rtype: int """ if self.column_dimensions: return max([column_index_from_string(column_index) for column_index in self.column_dimensions]) else: return 1 def calculate_dimension(self): """Return the minimum bounding range for all cells containing data.""" return 'A1:%s%d' % (get_column_letter(self.get_highest_column()), self.get_highest_row()) def range(self, range_string, row=0, column=0): """Returns a 2D array of cells, with optional row and column offsets. :param range_string: cell range string or `named range` name :type range_string: string :param row: number of rows to offset :type row: int :param column: number of columns to offset :type column: int :rtype: tuples of tuples of :class:`openpyxl.cell.Cell` """ if ':' in range_string: # R1C1 range result = [] min_range, max_range = range_string.split(':') min_col, min_row = coordinate_from_string(min_range) max_col, max_row = coordinate_from_string(max_range) if column: min_col = get_column_letter( column_index_from_string(min_col) + column) max_col = get_column_letter( column_index_from_string(max_col) + column) min_col = column_index_from_string(min_col) max_col = column_index_from_string(max_col) cache_cols = {} for col in xrange(min_col, max_col + 1): cache_cols[col] = get_column_letter(col) rows = xrange(min_row + row, max_row + row + 1) cols = xrange(min_col, max_col + 1) for row in rows: new_row = [] for col in cols: new_row.append(self.cell('%s%s' % (cache_cols[col], row))) result.append(tuple(new_row)) return tuple(result) else: try: return self.cell(coordinate=range_string, row=row, column=column) except CellCoordinatesException: pass # named range named_range = self._parent.get_named_range(range_string) if named_range is None: msg = '%s is not a valid range name' % range_string raise NamedRangeException(msg) if isinstance(named_range, NamedRangeContainingValue): msg = '%s refers to a value, not a range' % range_string raise NamedRangeException(msg) result = [] for destination in named_range.destinations: worksheet, cells_range = destination if worksheet is not self: msg = 'Range %s is not defined on worksheet %s' % \ (cells_range, self.title) raise NamedRangeException(msg) content = self.range(cells_range) if isinstance(content, tuple): for cells in content: result.extend(cells) else: result.append(content) if len(result) == 1: return result[0] else: return tuple(result) def get_style(self, coordinate): """Return the style object for the specified cell.""" if not coordinate in self._styles: self._styles[coordinate] = Style() elif self._styles[coordinate].static: self._styles[coordinate] = self._styles[coordinate].copy() return self._styles[coordinate] def set_printer_settings(self, paper_size, orientation): """Set printer settings """ self.paper_size = paper_size assert orientation in (self.ORIENTATION_PORTRAIT, self.ORIENTATION_LANDSCAPE), "Values should be %s or %s" % (self.ORIENTATION_PORTRAIT, self.ORIENTATION_LANDSCAPE) self.orientation = orientation def create_relationship(self, rel_type): """Add a relationship for this sheet.""" rel = Relationship(rel_type) self.relationships.append(rel) rel_id = self.relationships.index(rel) rel.id = 'rId' + str(rel_id + 1) return self.relationships[rel_id] def add_data_validation(self, data_validation): """ Add a data-validation object to the sheet. The data-validation object defines the type of data-validation to be applied and the cell or range of cells it should apply to. """ data_validation._sheet = self self._data_validations.append(data_validation) def add_chart(self, chart): """ Add a chart to the sheet """ chart._sheet = self self._charts.append(chart) def add_image(self, img): """ Add an image to the sheet """ img._sheet = self self._images.append(img) def merge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None): """ Set merge on a cell range. Range is a cell range (e.g. A1:E1) """ if not range_string: if start_row is None or start_column is None or end_row is None or end_column is None: msg = "You have to provide a value either for "\ "'coordinate' or for 'start_row', 'start_column', 'end_row' *and* 'end_column'" raise InsufficientCoordinatesException(msg) else: range_string = '%s%s:%s%s' % (get_column_letter(start_column + 1), start_row + 1, get_column_letter(end_column + 1), end_row + 1) elif len(range_string.split(':')) != 2: msg = "Range must be a cell range (e.g. A1:E1)" raise InsufficientCoordinatesException(msg) else: range_string = range_string.replace('$', '') # Make sure top_left cell exists - is this necessary? min_col, min_row = coordinate_from_string(range_string.split(':')[0]) max_col, max_row = coordinate_from_string(range_string.split(':')[1]) min_col = column_index_from_string(min_col) max_col = column_index_from_string(max_col) # Blank out the rest of the cells in the range for col in xrange(min_col, max_col + 1): for row in xrange(min_row, max_row + 1): if not (row == min_row and col == min_col): # PHPExcel adds cell and specifically blanks it out if it doesn't exist self._get_cell('%s%s' % (get_column_letter(col), row)).value = None self._get_cell('%s%s' % (get_column_letter(col), row)).merged = True if range_string not in self._merged_cells: self._merged_cells.append(range_string) def unmerge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None): """ Remove merge on a cell range. Range is a cell range (e.g. A1:E1) """ if not range_string: if start_row is None or start_column is None or end_row is None or end_column is None: msg = "You have to provide a value either for "\ "'coordinate' or for 'start_row', 'start_column', 'end_row' *and* 'end_column'" raise InsufficientCoordinatesException(msg) else: range_string = '%s%s:%s%s' % (get_column_letter(start_column + 1), start_row + 1, get_column_letter(end_column + 1), end_row + 1) elif len(range_string.split(':')) != 2: msg = "Range must be a cell range (e.g. A1:E1)" raise InsufficientCoordinatesException(msg) else: range_string = range_string.replace('$', '') if range_string in self._merged_cells: self._merged_cells.remove(range_string) min_col, min_row = coordinate_from_string(range_string.split(':')[0]) max_col, max_row = coordinate_from_string(range_string.split(':')[1]) min_col = column_index_from_string(min_col) max_col = column_index_from_string(max_col) # Mark cell as unmerged for col in xrange(min_col, max_col + 1): for row in xrange(min_row, max_row + 1): if not (row == min_row and col == min_col): self._get_cell('%s%s' % (get_column_letter(col), row)).merged = False else: msg = 'Cell range %s not known as merged.' % range_string raise InsufficientCoordinatesException(msg) def append(self, list_or_dict): """Appends a group of values at the bottom of the current sheet. * If it's a list: all values are added in order, starting from the first column * If it's a dict: values are assigned to the columns indicated by the keys (numbers or letters) :param list_or_dict: list or dict containing values to append :type list_or_dict: list/tuple or dict Usage: * append(['This is A1', 'This is B1', 'This is C1']) * **or** append({'A' : 'This is A1', 'C' : 'This is C1'}) * **or** append({0 : 'This is A1', 2 : 'This is C1'}) :raise: TypeError when list_or_dict is neither a list/tuple nor a dict """ row_idx = len(self.row_dimensions) if isinstance(list_or_dict, (list, tuple)): for col_idx, content in enumerate(list_or_dict): self.cell(row=row_idx, column=col_idx).value = content elif isinstance(list_or_dict, dict): for col_idx, content in iteritems(list_or_dict): if isinstance(col_idx, basestring): col_idx = column_index_from_string(col_idx) - 1 self.cell(row=row_idx, column=col_idx).value = content else: raise TypeError('list_or_dict must be a list or a dict') @property def rows(self): return self.range(self.calculate_dimension()) @property def columns(self): max_row = self.get_highest_row() cols = [] for col_idx in range(self.get_highest_column()): col = get_column_letter(col_idx + 1) res = self.range('%s1:%s%d' % (col, col, max_row)) cols.append(tuple([x[0] for x in res])) return tuple(cols) def point_pos(self, left=0, top=0): """ tells which cell is under the given coordinates (in pixels) counting from the top-left corner of the sheet. Can be used to locate images and charts on the worksheet """ current_col = 1 current_row = 1 column_dimensions = self.column_dimensions row_dimensions = self.row_dimensions default_width = points_to_pixels(DEFAULT_COLUMN_WIDTH) default_height = points_to_pixels(DEFAULT_ROW_HEIGHT) left_pos = 0 top_pos = 0 while left_pos <= left: letter = get_column_letter(current_col) current_col += 1 if letter in column_dimensions: cdw = column_dimensions[letter].width if cdw > 0: left_pos += points_to_pixels(cdw) continue left_pos += default_width while top_pos <= top: row = current_row current_row += 1 if row in row_dimensions: rdh = row_dimensions[row].height if rdh > 0: top_pos += points_to_pixels(rdh) continue top_pos += default_height return (letter, row) openpyxl-1.7.0+ds1/openpyxl/writer/000077500000000000000000000000001224514475200172605ustar00rootroot00000000000000openpyxl-1.7.0+ds1/openpyxl/writer/__init__.py000066400000000000000000000027401224514475200213740ustar00rootroot00000000000000# file openpyxl/writer/__init__.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Imports for the openpyxl.writer namespace.""" # package imports from openpyxl.writer import excel from openpyxl.writer import strings from openpyxl.writer import styles from openpyxl.writer import theme from openpyxl.writer import workbook from openpyxl.writer import worksheet openpyxl-1.7.0+ds1/openpyxl/writer/charts.py000066400000000000000000000304371224514475200211250ustar00rootroot00000000000000# coding=UTF-8 # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file from openpyxl.shared.xmltools import Element, SubElement, get_document_content from openpyxl.shared.compat.itertools import iteritems from openpyxl.chart import Chart, ErrorBar try: # Python 2 basestring except NameError: # Python 3 basestring = str try: from numbers import Number except ImportError: Number = False def safe_string(value): """Safely and consistently format numeric values""" if Number is False: # Python 2.5 if type(value) == int or type(value) == float: value = "%.15g" % value elif not isinstance(value, basestring): value = str(value) elif isinstance(value, Number): value = "%.15g" % value elif not isinstance(value, basestring): value = str(value) return value class ChartWriter(object): def __init__(self, chart): self.chart = chart def write(self): """ write a chart """ root = Element('c:chartSpace', {'xmlns:c':"http://schemas.openxmlformats.org/drawingml/2006/chart", 'xmlns:a':"http://schemas.openxmlformats.org/drawingml/2006/main", 'xmlns:r':"http://schemas.openxmlformats.org/officeDocument/2006/relationships"}) SubElement(root, 'c:lang', {'val':self.chart.lang}) self._write_chart(root) self._write_print_settings(root) self._write_shapes(root) return get_document_content(root) def _write_chart(self, root): chart = self.chart ch = SubElement(root, 'c:chart') self._write_title(ch) plot_area = SubElement(ch, 'c:plotArea') layout = SubElement(plot_area, 'c:layout') mlayout = SubElement(layout, 'c:manualLayout') SubElement(mlayout, 'c:layoutTarget', {'val':'inner'}) SubElement(mlayout, 'c:xMode', {'val':'edge'}) SubElement(mlayout, 'c:yMode', {'val':'edge'}) SubElement(mlayout, 'c:x', {'val':str(chart._get_margin_left())}) SubElement(mlayout, 'c:y', {'val':str(chart._get_margin_top())}) SubElement(mlayout, 'c:w', {'val':str(chart.width)}) SubElement(mlayout, 'c:h', {'val':str(chart.height)}) if chart.type == Chart.SCATTER_CHART: subchart = SubElement(plot_area, 'c:scatterChart') SubElement(subchart, 'c:scatterStyle', {'val':str('lineMarker')}) else: if chart.type == Chart.BAR_CHART: subchart = SubElement(plot_area, 'c:barChart') SubElement(subchart, 'c:barDir', {'val':'col'}) else: subchart = SubElement(plot_area, 'c:lineChart') SubElement(subchart, 'c:grouping', {'val':chart.grouping}) self._write_series(subchart) SubElement(subchart, 'c:marker', {'val':'1'}) SubElement(subchart, 'c:axId', {'val':str(chart.x_axis.id)}) SubElement(subchart, 'c:axId', {'val':str(chart.y_axis.id)}) if chart.type == Chart.SCATTER_CHART: self._write_axis(plot_area, chart.x_axis, 'c:valAx') else: self._write_axis(plot_area, chart.x_axis, 'c:catAx') self._write_axis(plot_area, chart.y_axis, 'c:valAx') self._write_legend(ch) SubElement(ch, 'c:plotVisOnly', {'val':'1'}) def _write_title(self, chart): if self.chart.title != '': title = SubElement(chart, 'c:title') tx = SubElement(title, 'c:tx') rich = SubElement(tx, 'c:rich') SubElement(rich, 'a:bodyPr') SubElement(rich, 'a:lstStyle') p = SubElement(rich, 'a:p') pPr = SubElement(p, 'a:pPr') SubElement(pPr, 'a:defRPr') r = SubElement(p, 'a:r') SubElement(r, 'a:rPr', {'lang':self.chart.lang}) t = SubElement(r, 'a:t').text = self.chart.title SubElement(title, 'c:layout') def _write_axis(self, plot_area, axis, label): ax = SubElement(plot_area, label) SubElement(ax, 'c:axId', {'val':str(axis.id)}) scaling = SubElement(ax, 'c:scaling') SubElement(scaling, 'c:orientation', {'val':axis.orientation}) if label == 'c:valAx': SubElement(scaling, 'c:max', {'val':str(float(axis.max))}) SubElement(scaling, 'c:min', {'val':str(float(axis.min))}) SubElement(ax, 'c:axPos', {'val':axis.position}) if label == 'c:valAx': SubElement(ax, 'c:majorGridlines') SubElement(ax, 'c:numFmt', {'formatCode':"General", 'sourceLinked':'1'}) if axis.title != '': title = SubElement(ax, 'c:title') tx = SubElement(title, 'c:tx') rich = SubElement(tx, 'c:rich') SubElement(rich, 'a:bodyPr') SubElement(rich, 'a:lstStyle') p = SubElement(rich, 'a:p') pPr = SubElement(p, 'a:pPr') SubElement(pPr, 'a:defRPr') r = SubElement(p, 'a:r') SubElement(r, 'a:rPr', {'lang':self.chart.lang}) t = SubElement(r, 'a:t').text = axis.title SubElement(title, 'c:layout') SubElement(ax, 'c:tickLblPos', {'val':axis.tick_label_position}) SubElement(ax, 'c:crossAx', {'val':str(axis.cross)}) SubElement(ax, 'c:crosses', {'val':axis.crosses}) if axis.auto: SubElement(ax, 'c:auto', {'val':'1'}) if axis.label_align: SubElement(ax, 'c:lblAlgn', {'val':axis.label_align}) if axis.label_offset: SubElement(ax, 'c:lblOffset', {'val':str(axis.label_offset)}) if label == 'c:valAx': if self.chart.type == Chart.SCATTER_CHART: SubElement(ax, 'c:crossBetween', {'val':'midCat'}) else: SubElement(ax, 'c:crossBetween', {'val':'between'}) SubElement(ax, 'c:majorUnit', {'val':str(float(axis.unit))}) def _write_series(self, subchart): for i, serie in enumerate(self.chart._series): ser = SubElement(subchart, 'c:ser') SubElement(ser, 'c:idx', {'val':str(i)}) SubElement(ser, 'c:order', {'val':str(i)}) if serie.legend: tx = SubElement(ser, 'c:tx') self._write_serial(tx, serie.legend) if serie.color: sppr = SubElement(ser, 'c:spPr') if self.chart.type == Chart.BAR_CHART: # fill color fillc = SubElement(sppr, 'a:solidFill') SubElement(fillc, 'a:srgbClr', {'val':serie.color}) # edge color ln = SubElement(sppr, 'a:ln') fill = SubElement(ln, 'a:solidFill') SubElement(fill, 'a:srgbClr', {'val':serie.color}) if serie.error_bar: self._write_error_bar(ser, serie) marker = SubElement(ser, 'c:marker') SubElement(marker, 'c:symbol', {'val':serie.marker}) if serie.labels: cat = SubElement(ser, 'c:cat') self._write_serial(cat, serie.labels) if self.chart.type == Chart.SCATTER_CHART: if serie.xvalues: xval = SubElement(ser, 'c:xVal') self._write_serial(xval, serie.xreference) yval = SubElement(ser, 'c:yVal') self._write_serial(yval, serie.reference) else: val = SubElement(ser, 'c:val') self._write_serial(val, serie.reference) def _write_serial(self, node, reference, literal=False): cache = reference.values if isinstance(cache[0], basestring): typ = 'str' else: typ = 'num' if not literal: if typ == 'num': ref = SubElement(node, 'c:numRef') else: ref = SubElement(node, 'c:strRef') SubElement(ref, 'c:f').text = str(reference) if typ == 'num': data = SubElement(ref, 'c:numCache') else: data = SubElement(ref, 'c:strCache') else: data = SubElement(node, 'c:numLit') if typ == 'num': SubElement(data, 'c:formatCode').text = 'General' if literal: values = (1,) else: values = cache SubElement(data, 'c:ptCount', {'val':str(len(values))}) for j, val in enumerate(values): point = SubElement(data, 'c:pt', {'idx':str(j)}) if not isinstance(val, basestring): val = safe_string(val) SubElement(point, 'c:v').text = val def _write_error_bar(self, node, serie): flag = {ErrorBar.PLUS_MINUS:'both', ErrorBar.PLUS:'plus', ErrorBar.MINUS:'minus'} eb = SubElement(node, 'c:errBars') SubElement(eb, 'c:errBarType', {'val':flag[serie.error_bar.type]}) SubElement(eb, 'c:errValType', {'val':'cust'}) plus = SubElement(eb, 'c:plus') self._write_serial(plus, serie.error_bar.values, literal=(serie.error_bar.type == ErrorBar.MINUS)) minus = SubElement(eb, 'c:minus') self._write_serial(minus, serie.error_bar.values, literal=(serie.error_bar.type == ErrorBar.PLUS)) def _write_legend(self, chart): if self.chart.show_legend: legend = SubElement(chart, 'c:legend') SubElement(legend, 'c:legendPos', {'val':self.chart.legend.position}) SubElement(legend, 'c:layout') def _write_print_settings(self, root): settings = SubElement(root, 'c:printSettings') SubElement(settings, 'c:headerFooter') try: # Python 2 print_margins_items = iteritems(self.chart.print_margins) except AttributeError: # Python 3 print_margins_items = self.chart.print_margins.items() margins = dict([(k, str(v)) for (k, v) in print_margins_items]) SubElement(settings, 'c:pageMargins', margins) SubElement(settings, 'c:pageSetup') def _write_shapes(self, root): if self.chart._shapes: SubElement(root, 'c:userShapes', {'r:id':'rId1'}) def write_rels(self, drawing_id): root = Element('Relationships', {'xmlns' : 'http://schemas.openxmlformats.org/package/2006/relationships'}) attrs = {'Id' : 'rId1', 'Type' : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUserShapes', 'Target' : '../drawings/drawing%s.xml' % drawing_id } SubElement(root, 'Relationship', attrs) return get_document_content(root) openpyxl-1.7.0+ds1/openpyxl/writer/drawings.py000066400000000000000000000245321224514475200214560ustar00rootroot00000000000000# coding=UTF-8 # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file from openpyxl.shared.xmltools import Element, SubElement, get_document_content class DrawingWriter(object): """ one main drawing file per sheet """ def __init__(self, sheet): self._sheet = sheet def write(self): """ write drawings for one sheet in one file """ root = Element('xdr:wsDr', {'xmlns:xdr' : "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing", 'xmlns:a' : "http://schemas.openxmlformats.org/drawingml/2006/main"}) for i, chart in enumerate(self._sheet._charts): drawing = chart.drawing # anchor = SubElement(root, 'xdr:twoCellAnchor') # (start_row, start_col), (end_row, end_col) = drawing.coordinates # # anchor coordinates # _from = SubElement(anchor, 'xdr:from') # x = SubElement(_from, 'xdr:col').text = str(start_col) # x = SubElement(_from, 'xdr:colOff').text = '0' # x = SubElement(_from, 'xdr:row').text = str(start_row) # x = SubElement(_from, 'xdr:rowOff').text = '0' # _to = SubElement(anchor, 'xdr:to') # x = SubElement(_to, 'xdr:col').text = str(end_col) # x = SubElement(_to, 'xdr:colOff').text = '0' # x = SubElement(_to, 'xdr:row').text = str(end_row) # x = SubElement(_to, 'xdr:rowOff').text = '0' # we only support absolute anchor atm (TODO: oneCellAnchor, twoCellAnchor x, y, w, h = drawing.get_emu_dimensions() anchor = SubElement(root, 'xdr:absoluteAnchor') SubElement(anchor, 'xdr:pos', {'x':str(x), 'y':str(y)}) SubElement(anchor, 'xdr:ext', {'cx':str(w), 'cy':str(h)}) # graph frame frame = SubElement(anchor, 'xdr:graphicFrame', {'macro':''}) name = SubElement(frame, 'xdr:nvGraphicFramePr') SubElement(name, 'xdr:cNvPr', {'id':'%s' % i, 'name':'Graphique %s' % i}) SubElement(name, 'xdr:cNvGraphicFramePr') frm = SubElement(frame, 'xdr:xfrm') # no transformation SubElement(frm, 'a:off', {'x':'0', 'y':'0'}) SubElement(frm, 'a:ext', {'cx':'0', 'cy':'0'}) graph = SubElement(frame, 'a:graphic') data = SubElement(graph, 'a:graphicData', {'uri':'http://schemas.openxmlformats.org/drawingml/2006/chart'}) SubElement(data, 'c:chart', { 'xmlns:c':'http://schemas.openxmlformats.org/drawingml/2006/chart', 'xmlns:r':'http://schemas.openxmlformats.org/officeDocument/2006/relationships', 'r:id':'rId%s' % (i + 1)}) SubElement(anchor, 'xdr:clientData') for i, img in enumerate(self._sheet._images): drawing = img.drawing x, y, w, h = drawing.get_emu_dimensions() anchor = SubElement(root, 'xdr:absoluteAnchor') SubElement(anchor, 'xdr:pos', {'x':str(x), 'y':str(y)}) SubElement(anchor, 'xdr:ext', {'cx':str(w), 'cy':str(h)}) pic = SubElement(anchor, 'xdr:pic') name = SubElement(pic, 'xdr:nvPicPr') SubElement(name, 'xdr:cNvPr', {'id':'%s' % i, 'name':'Picture %s' % i}) SubElement(SubElement(name, 'xdr:cNvPicPr'), 'a:picLocks', {'noChangeAspect':"1" if img.nochangeaspect else '0','noChangeArrowheads':"1" if img.nochangearrowheads else '0'}) blipfill = SubElement(pic, 'xdr:blipFill') SubElement(blipfill, 'a:blip', { 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships', 'r:embed': 'rId%s' % (i + 1), 'cstate':'print' }) SubElement(blipfill, 'a:srcRect') SubElement(SubElement(blipfill, 'a:stretch'), 'a:fillRect') sppr = SubElement(pic, 'xdr:spPr', {'bwMode':'auto'}) frm = SubElement(sppr, 'a:xfrm') # no transformation SubElement(frm, 'a:off', {'x':'0', 'y':'0'}) SubElement(frm, 'a:ext', {'cx':'0', 'cy':'0'}) SubElement(SubElement(sppr, 'a:prstGeom', {'prst':'rect'}), 'a:avLst') SubElement(sppr, 'a:noFill') ln = SubElement(sppr, 'a:ln', {'w':'1'}) SubElement(ln, 'a:noFill') SubElement(ln, 'a:miter', {'lim':'800000'}) SubElement(ln, 'a:headEnd') SubElement(ln, 'a:tailEnd', {'type':'none', 'w':'med', 'len':'med'}) SubElement(sppr, 'a:effectLst') SubElement(anchor, 'xdr:clientData') return get_document_content(root) def write_rels(self, chart_id, image_id): root = Element('Relationships', {'xmlns' : 'http://schemas.openxmlformats.org/package/2006/relationships'}) i = 0 for i, chart in enumerate(self._sheet._charts): attrs = {'Id' : 'rId%s' % (i + 1), 'Type' : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart', 'Target' : '../charts/chart%s.xml' % (chart_id + i) } SubElement(root, 'Relationship', attrs) for j, img in enumerate(self._sheet._images): attrs = {'Id' : 'rId%s' % (i + j + 1), 'Type' : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', 'Target' : '../media/image%s.png' % (image_id + j) } SubElement(root, 'Relationship', attrs) return get_document_content(root) class ShapeWriter(object): """ one file per shape """ schema = "http://schemas.openxmlformats.org/drawingml/2006/main" def __init__(self, shapes): self._shapes = shapes def write(self, shape_id): root = Element('c:userShapes', {'xmlns:c' : 'http://schemas.openxmlformats.org/drawingml/2006/chart'}) for shape in self._shapes: anchor = SubElement(root, 'cdr:relSizeAnchor', {'xmlns:cdr' : "http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"}) xstart, ystart, xend, yend = shape.get_coordinates() _from = SubElement(anchor, 'cdr:from') SubElement(_from, 'cdr:x').text = str(xstart) SubElement(_from, 'cdr:y').text = str(ystart) _to = SubElement(anchor, 'cdr:to') SubElement(_to, 'cdr:x').text = str(xend) SubElement(_to, 'cdr:y').text = str(yend) sp = SubElement(anchor, 'cdr:sp', {'macro':'', 'textlink':''}) nvspr = SubElement(sp, 'cdr:nvSpPr') SubElement(nvspr, 'cdr:cNvPr', {'id':str(shape_id), 'name':'shape %s' % shape_id}) SubElement(nvspr, 'cdr:cNvSpPr') sppr = SubElement(sp, 'cdr:spPr') frm = SubElement(sppr, 'a:xfrm', {'xmlns:a':self.schema}) # no transformation SubElement(frm, 'a:off', {'x':'0', 'y':'0'}) SubElement(frm, 'a:ext', {'cx':'0', 'cy':'0'}) prstgeom = SubElement(sppr, 'a:prstGeom', {'xmlns:a':self.schema, 'prst':str(shape.style)}) SubElement(prstgeom, 'a:avLst') fill = SubElement(sppr, 'a:solidFill', {'xmlns:a':self.schema}) SubElement(fill, 'a:srgbClr', {'val':shape.color}) border = SubElement(sppr, 'a:ln', {'xmlns:a':self.schema, 'w':str(shape._border_width)}) sf = SubElement(border, 'a:solidFill') SubElement(sf, 'a:srgbClr', {'val':shape.border_color}) self._write_style(sp) self._write_text(sp, shape) shape_id += 1 return get_document_content(root) def _write_text(self, node, shape): """ write text in the shape """ tx_body = SubElement(node, 'cdr:txBody') SubElement(tx_body, 'a:bodyPr', {'xmlns:a':self.schema, 'vertOverflow':'clip'}) SubElement(tx_body, 'a:lstStyle', {'xmlns:a':self.schema}) p = SubElement(tx_body, 'a:p', {'xmlns:a':self.schema}) if shape.text: r = SubElement(p, 'a:r') rpr = SubElement(r, 'a:rPr', {'lang':'en-US'}) fill = SubElement(rpr, 'a:solidFill') SubElement(fill, 'a:srgbClr', {'val':shape.text_color}) SubElement(r, 'a:t').text = shape.text else: SubElement(p, 'a:endParaRPr', {'lang':'en-US'}) def _write_style(self, node): """ write style theme """ style = SubElement(node, 'cdr:style') ln_ref = SubElement(style, 'a:lnRef', {'xmlns:a':self.schema, 'idx':'2'}) scheme_clr = SubElement(ln_ref, 'a:schemeClr', {'val':'accent1'}) SubElement(scheme_clr, 'a:shade', {'val':'50000'}) fill_ref = SubElement(style, 'a:fillRef', {'xmlns:a':self.schema, 'idx':'1'}) SubElement(fill_ref, 'a:schemeClr', {'val':'accent1'}) effect_ref = SubElement(style, 'a:effectRef', {'xmlns:a':self.schema, 'idx':'0'}) SubElement(effect_ref, 'a:schemeClr', {'val':'accent1'}) font_ref = SubElement(style, 'a:fontRef', {'xmlns:a':self.schema, 'idx':'minor'}) SubElement(font_ref, 'a:schemeClr', {'val':'lt1'}) openpyxl-1.7.0+ds1/openpyxl/writer/dump_worksheet.py000066400000000000000000000243021224514475200226730ustar00rootroot00000000000000# file openpyxl/writer/straight_worksheet.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Write worksheets to xml representations in an optimized way""" import datetime import os from openpyxl.shared.compat import OrderedDict from openpyxl.cell import get_column_letter, Cell from openpyxl.worksheet import Worksheet from openpyxl.shared.xmltools import (XMLGenerator, start_tag, end_tag, tag) from openpyxl.shared.date_time import SharedDate from openpyxl.shared.ooxml import MAX_COLUMN, MAX_ROW from openpyxl.shared import NUMERIC_TYPES from openpyxl.shared.exc import WorkbookAlreadySaved from openpyxl.shared.compat import NamedTemporaryFile from openpyxl.writer.excel import ExcelWriter from openpyxl.writer.strings import write_string_table from openpyxl.writer.styles import StyleWriter from openpyxl.style import Style, NumberFormat from openpyxl.shared.ooxml import (ARC_SHARED_STRINGS, PACKAGE_WORKSHEETS) STYLES = {'datetime' : {'type':Cell.TYPE_NUMERIC, 'style':'1'}, 'string':{'type':Cell.TYPE_STRING, 'style':'0'}, 'numeric':{'type':Cell.TYPE_NUMERIC, 'style':'0'}, 'formula':{'type':Cell.TYPE_FORMULA, 'style':'0'}, 'boolean':{'type':Cell.TYPE_BOOL, 'style':'0'}, } DESCRIPTORS_CACHE_SIZE = 50 DATETIME_STYLE = Style() DATETIME_STYLE.number_format.format_code = NumberFormat.FORMAT_DATE_YYYYMMDD2 BOUNDING_BOX_PLACEHOLDER = 'A1:%s%d' % (get_column_letter(MAX_COLUMN), MAX_ROW) def create_temporary_file(suffix=''): fobj = NamedTemporaryFile(mode='w+', suffix=suffix, prefix='openpyxl.', delete=False) filename = fobj.name return filename class DumpWorksheet(Worksheet): """ .. warning:: You shouldn't initialize this yourself, use :class:`openpyxl.workbook.Workbook` constructor instead, with `optimized_write = True`. """ def __init__(self, parent_workbook, title): Worksheet.__init__(self, parent_workbook, title) self._max_col = 0 self._max_row = 0 self._parent = parent_workbook self._fileobj_header_name = create_temporary_file(suffix='.header') self._fileobj_content_name = create_temporary_file(suffix='.content') self._fileobj_name = create_temporary_file() self._shared_date = SharedDate() self._string_builder = self._parent.strings_table_builder def get_temporary_file(self, filename): if filename in self._descriptors_cache: fobj = self._descriptors_cache[filename] # re-insert the value so it does not get evicted # from cache soon del self._descriptors_cache[filename] self._descriptors_cache[filename] = fobj return fobj else: if filename is None: raise WorkbookAlreadySaved('this workbook has already been saved ' 'and cannot be modified or saved anymore.') fobj = open(filename, 'r+') self._descriptors_cache[filename] = fobj if len(self._descriptors_cache) > DESCRIPTORS_CACHE_SIZE: filename, fileobj = self._descriptors_cache.popitem(last=False) fileobj.close() return fobj @property def _descriptors_cache(self): try: return self._parent._local_data.cache except AttributeError: self._parent._local_data.cache = OrderedDict() return self._parent._local_data.cache @property def filename(self): return self._fileobj_name @property def _temp_files(self): return (self._fileobj_content_name, self._fileobj_header_name, self._fileobj_name) def _unset_temp_files(self): self._fileobj_header_name = None self._fileobj_content_name = None self._fileobj_name = None def write_header(self): fobj = self.get_temporary_file(filename=self._fileobj_header_name) doc = XMLGenerator(fobj, 'utf-8') start_tag(doc, 'worksheet', {'xml:space': 'preserve', 'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}) start_tag(doc, 'sheetPr') tag(doc, 'outlinePr', {'summaryBelow': '1', 'summaryRight': '1'}) end_tag(doc, 'sheetPr') tag(doc, 'dimension', {'ref': 'A1:%s' % (self.get_dimensions())}) start_tag(doc, 'sheetViews') start_tag(doc, 'sheetView', {'workbookViewId': '0'}) tag(doc, 'selection', {'activeCell': 'A1', 'sqref': 'A1'}) end_tag(doc, 'sheetView') end_tag(doc, 'sheetViews') tag(doc, 'sheetFormatPr', {'defaultRowHeight': '15'}) start_tag(doc, 'sheetData') def close(self): self._close_content() self._fileobj = self.get_temporary_file(filename=self._fileobj_name) self._write_fileobj(self._fileobj_header_name) self._write_fileobj(self._fileobj_content_name) self._fileobj.close() def _write_fileobj(self, fobj_name): fobj = self.get_temporary_file(filename=fobj_name) fobj.flush() fobj.seek(0) while True: chunk = fobj.read(4096) if not chunk: break self._fileobj.write(chunk) fobj.close() self._fileobj.flush() def _close_content(self): doc = self._get_content_generator() end_tag(doc, 'sheetData') end_tag(doc, 'worksheet') def get_dimensions(self): if not self._max_col or not self._max_row: return 'A1' else: return '%s%d' % (get_column_letter(self._max_col), (self._max_row)) def _get_content_generator(self): """ XXX: this is ugly, but it allows to resume writing the file even after the handle is closed""" # when I'll recreate the XMLGenerator, it will start writing at the # begining of the file, erasing previously entered rows, so we have # to move to the end of the file before adding new tags handle = self.get_temporary_file(filename=self._fileobj_content_name) handle.seek(0, 2) doc = XMLGenerator(out=handle) return doc def append(self, row): """ :param row: iterable containing values to append :type row: iterable """ doc = self._get_content_generator() self._max_row += 1 span = len(row) self._max_col = max(self._max_col, span) row_idx = self._max_row attrs = {'r': '%d' % row_idx, 'spans': '1:%d' % span} start_tag(doc, 'row', attrs) for col_idx, cell in enumerate(row): if cell is None: continue coordinate = '%s%d' % (get_column_letter(col_idx + 1), row_idx) attributes = {'r': coordinate} if isinstance(cell, bool): dtype = 'boolean' elif isinstance(cell, NUMERIC_TYPES): dtype = 'numeric' elif isinstance(cell, (datetime.datetime, datetime.date)): dtype = 'datetime' cell = self._shared_date.datetime_to_julian(cell) attributes['s'] = STYLES[dtype]['style'] elif cell and cell[0] == '=': dtype = 'formula' else: dtype = 'string' cell = self._string_builder.add(cell) if dtype != 'formula': attributes['t'] = STYLES[dtype]['type'] start_tag(doc, 'c', attributes) if dtype == 'formula': tag(doc, 'f', body='%s' % cell[1:]) tag(doc, 'v') elif dtype == 'boolean': tag(doc, 'v', body='%d' % cell) else: tag(doc, 'v', body='%s' % cell) end_tag(doc, 'c') end_tag(doc, 'row') def save_dump(workbook, filename): writer = ExcelDumpWriter(workbook) writer.save(filename) return True class ExcelDumpWriter(ExcelWriter): def __init__(self, workbook): self.workbook = workbook self.style_writer = StyleDumpWriter(workbook) self.style_writer._style_list.append(DATETIME_STYLE) def _write_string_table(self, archive): shared_string_table = self.workbook.strings_table_builder.get_table() archive.writestr(ARC_SHARED_STRINGS, write_string_table(shared_string_table)) return shared_string_table def _write_worksheets(self, archive, shared_string_table, style_writer): for i, sheet in enumerate(self.workbook.worksheets): sheet.write_header() sheet.close() archive.write(sheet.filename, PACKAGE_WORKSHEETS + '/sheet%d.xml' % (i + 1)) for filename in sheet._temp_files: del sheet._descriptors_cache[filename] os.remove(filename) sheet._unset_temp_files() class StyleDumpWriter(StyleWriter): def _get_style_list(self, workbook): return [] openpyxl-1.7.0+ds1/openpyxl/writer/excel.py000066400000000000000000000157471224514475200207500ustar00rootroot00000000000000# file openpyxl/writer/excel.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Write a .xlsx file.""" # Python stdlib imports from zipfile import ZipFile, ZIP_DEFLATED # compatibility imports from openpyxl.shared.compat import BytesIO, StringIO # package imports from openpyxl.shared.ooxml import ARC_SHARED_STRINGS, ARC_CONTENT_TYPES, \ ARC_ROOT_RELS, ARC_WORKBOOK_RELS, ARC_APP, ARC_CORE, ARC_THEME, \ ARC_STYLE, ARC_WORKBOOK, ARC_VBA,\ PACKAGE_WORKSHEETS, PACKAGE_DRAWINGS, PACKAGE_CHARTS, PACKAGE_IMAGES from openpyxl.writer.strings import create_string_table, write_string_table from openpyxl.writer.workbook import write_content_types, write_root_rels, \ write_workbook_rels, write_properties_app, write_properties_core, \ write_workbook from openpyxl.writer.theme import write_theme from openpyxl.writer.styles import StyleWriter from openpyxl.writer.drawings import DrawingWriter, ShapeWriter from openpyxl.writer.charts import ChartWriter from openpyxl.writer.worksheet import write_worksheet, write_worksheet_rels class ExcelWriter(object): """Write a workbook object to an Excel file.""" def __init__(self, workbook): self.workbook = workbook self.style_writer = StyleWriter(self.workbook) def write_data(self, archive): """Write the various xml files into the zip archive.""" # cleanup all worksheets shared_string_table = self._write_string_table(archive) archive.writestr(ARC_CONTENT_TYPES, write_content_types(self.workbook)) if not self.workbook.vba_archive: archive.writestr(ARC_ROOT_RELS, write_root_rels(self.workbook)) archive.writestr(ARC_WORKBOOK_RELS, write_workbook_rels(self.workbook)) archive.writestr(ARC_APP, write_properties_app(self.workbook)) archive.writestr(ARC_CORE, write_properties_core(self.workbook.properties)) if self.workbook.loaded_theme: archive.writestr(ARC_THEME, self.workbook.loaded_theme) else: archive.writestr(ARC_THEME, write_theme()) archive.writestr(ARC_STYLE, self.style_writer.write_table()) archive.writestr(ARC_WORKBOOK, write_workbook(self.workbook)) if self.workbook.vba_archive: vba_archive = self.workbook.vba_archive for name in vba_archive.namelist(): for s in ARC_VBA: if name.startswith(s): archive.writestr(name, vba_archive.read(name)) break self._write_worksheets(archive, shared_string_table, self.style_writer) def _write_string_table(self, archive): for ws in self.workbook.worksheets: ws.garbage_collect() shared_string_table = create_string_table(self.workbook) archive.writestr(ARC_SHARED_STRINGS, write_string_table(shared_string_table)) return shared_string_table def _write_worksheets(self, archive, shared_string_table, style_writer): drawing_id = 1 chart_id = 1 image_id = 1 shape_id = 1 for i, sheet in enumerate(self.workbook.worksheets): archive.writestr(PACKAGE_WORKSHEETS + '/sheet%d.xml' % (i + 1), write_worksheet(sheet, shared_string_table, style_writer.get_style_by_hash())) if sheet._charts or sheet._images or sheet.relationships: archive.writestr(PACKAGE_WORKSHEETS + '/_rels/sheet%d.xml.rels' % (i + 1), write_worksheet_rels(sheet, drawing_id)) if sheet._charts or sheet._images: dw = DrawingWriter(sheet) archive.writestr(PACKAGE_DRAWINGS + '/drawing%d.xml' % drawing_id, dw.write()) archive.writestr(PACKAGE_DRAWINGS + '/_rels/drawing%d.xml.rels' % drawing_id, dw.write_rels(chart_id, image_id)) drawing_id += 1 for chart in sheet._charts: cw = ChartWriter(chart) archive.writestr(PACKAGE_CHARTS + '/chart%d.xml' % chart_id, cw.write()) if chart._shapes: archive.writestr(PACKAGE_CHARTS + '/_rels/chart%d.xml.rels' % chart_id, cw.write_rels(drawing_id)) sw = ShapeWriter(chart._shapes) archive.writestr(PACKAGE_DRAWINGS + '/drawing%d.xml' % drawing_id, sw.write(shape_id)) shape_id += len(chart._shapes) drawing_id += 1 chart_id += 1 for img in sheet._images: buf = StringIO() img.image.save(buf, format= 'PNG') archive.writestr(PACKAGE_IMAGES + '/image%d.png' % image_id, buf.getvalue()) image_id += 1 def save(self, filename): """Write data into the archive.""" archive = ZipFile(filename, 'w', ZIP_DEFLATED) self.write_data(archive) archive.close() def save_workbook(workbook, filename): """Save the given workbook on the filesystem under the name filename. :param workbook: the workbook to save :type workbook: :class:`openpyxl.workbook.Workbook` :param filename: the path to which save the workbook :type filename: string :rtype: bool """ writer = ExcelWriter(workbook) writer.save(filename) return True def save_virtual_workbook(workbook): """Return an in-memory workbook, suitable for a Django response.""" writer = ExcelWriter(workbook) temp_buffer = BytesIO() try: archive = ZipFile(temp_buffer, 'w', ZIP_DEFLATED) writer.write_data(archive) finally: archive.close() virtual_workbook = temp_buffer.getvalue() temp_buffer.close() return virtual_workbook openpyxl-1.7.0+ds1/openpyxl/writer/strings.py000066400000000000000000000056551224514475200213360ustar00rootroot00000000000000# file openpyxl/writer/strings.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Write the shared string table.""" # compatibility imports from openpyxl.shared.compat import BytesIO, StringIO # package imports from openpyxl.shared.xmltools import start_tag, end_tag, tag, XMLGenerator def create_string_table(workbook): """Compile the string table for a workbook.""" strings = set() for sheet in workbook.worksheets: for cell in sheet.get_cell_collection(): if cell.data_type == cell.TYPE_STRING and cell._value is not None: strings.add(cell.value) return dict((key, i) for i, key in enumerate(sorted(strings))) def write_string_table(string_table): """Write the string table xml.""" temp_buffer = StringIO() doc = XMLGenerator(out=temp_buffer, encoding='utf-8') start_tag(doc, 'sst', {'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', 'uniqueCount': '%d' % len(string_table)}) strings_to_write = sorted(string_table.items(), key=lambda pair: pair[1]) for key in [pair[0] for pair in strings_to_write]: start_tag(doc, 'si') if key.strip() != key: attr = {'xml:space': 'preserve'} else: attr = {} tag(doc, 't', attr, key) end_tag(doc, 'si') end_tag(doc, 'sst') string_table_xml = temp_buffer.getvalue() temp_buffer.close() return string_table_xml class StringTableBuilder(object): def __init__(self): self.counter = 0 self.dct = {} def add(self, key): key = key.strip() try: return self.dct[key] except KeyError: res = self.dct[key] = self.counter self.counter += 1 return res def get_table(self): return self.dct openpyxl-1.7.0+ds1/openpyxl/writer/styles.py000066400000000000000000000327741224514475200211720ustar00rootroot00000000000000# file openpyxl/writer/styles.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Write the shared style table.""" # package imports from openpyxl.shared.xmltools import Element, SubElement from openpyxl.shared.xmltools import get_document_content from openpyxl import style class StyleWriter(object): def __init__(self, workbook): self._style_list = self._get_style_list(workbook) self._root = Element('styleSheet', {'xmlns':'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}) def _get_style_list(self, workbook): crc = {} for worksheet in workbook.worksheets: uniqueStyles = dict((id(style), style) for style in worksheet._styles.values()).values() for style in uniqueStyles: crc[hash(style)] = style self.style_table = dict([(style, i + 1) \ for i, style in enumerate(crc.values())]) sorted_styles = sorted(self.style_table.items(), \ key=lambda pair:pair[1]) return [s[0] for s in sorted_styles] def get_style_by_hash(self): return dict([(hash(style), id) \ for style, id in self.style_table.items()]) def write_table(self): number_format_table = self._write_number_formats() fonts_table = self._write_fonts() fills_table = self._write_fills() borders_table = self._write_borders() self._write_cell_style_xfs() self._write_cell_xfs(number_format_table, fonts_table, fills_table, borders_table) self._write_cell_style() self._write_dxfs() self._write_table_styles() return get_document_content(xml_node=self._root) def _write_fonts(self): """ add fonts part to root return {font.crc => index} """ fonts = SubElement(self._root, 'fonts') # default font_node = SubElement(fonts, 'font') SubElement(font_node, 'sz', {'val':'11'}) SubElement(font_node, 'color', {'theme':'1'}) SubElement(font_node, 'name', {'val':'Calibri'}) SubElement(font_node, 'family', {'val':'2'}) SubElement(font_node, 'scheme', {'val':'minor'}) # others table = {} index = 1 for st in self._style_list: if hash(st.font) != hash(style.DEFAULTS.font) and hash(st.font) not in table: table[hash(st.font)] = str(index) font_node = SubElement(fonts, 'font') SubElement(font_node, 'sz', {'val':str(st.font.size)}) if str(st.font.color.index).split(':')[0] == 'theme': # strip prefix theme if marked as such if str(st.font.color.index).split(':')[2]: SubElement(font_node, 'color', {'theme':str(st.font.color.index).split(':')[1], 'tint':str(st.font.color.index).split(':')[2]}) else: SubElement(font_node, 'color', {'theme':str(st.font.color.index).split(':')[1]}) else: SubElement(font_node, 'color', {'rgb':str(st.font.color.index)}) SubElement(font_node, 'name', {'val':st.font.name}) SubElement(font_node, 'family', {'val':'2'}) # Don't write the 'scheme' element because it appears to prevent # the font name from being applied in Excel. #SubElement(font_node, 'scheme', {'val':'minor'}) if st.font.bold: SubElement(font_node, 'b') if st.font.italic: SubElement(font_node, 'i') if st.font.underline == 'single': SubElement(font_node, 'u') index += 1 fonts.attrib["count"] = str(index) return table def _write_fills(self): fills = SubElement(self._root, 'fills', {'count':'2'}) fill = SubElement(fills, 'fill') SubElement(fill, 'patternFill', {'patternType':'none'}) fill = SubElement(fills, 'fill') SubElement(fill, 'patternFill', {'patternType':'gray125'}) table = {} index = 2 for st in self._style_list: if hash(st.fill) != hash(style.DEFAULTS.fill) and hash(st.fill) not in table: table[hash(st.fill)] = str(index) fill = SubElement(fills, 'fill') if hash(st.fill.fill_type) != hash(style.DEFAULTS.fill.fill_type): node = SubElement(fill, 'patternFill', {'patternType':st.fill.fill_type}) if hash(st.fill.start_color) != hash(style.DEFAULTS.fill.start_color): if str(st.fill.start_color.index).split(':')[0] == 'theme': # strip prefix theme if marked as such if str(st.fill.start_color.index).split(':')[2]: SubElement(node, 'fgColor', {'theme':str(st.fill.start_color.index).split(':')[1], 'tint':str(st.fill.start_color.index).split(':')[2]}) else: SubElement(node, 'fgColor', {'theme':str(st.fill.start_color.index).split(':')[1]}) else: SubElement(node, 'fgColor', {'rgb':str(st.fill.start_color.index)}) if hash(st.fill.end_color) != hash(style.DEFAULTS.fill.end_color): if str(st.fill.end_color.index).split(':')[0] == 'theme': # strip prefix theme if marked as such if str(st.fill.end_color.index).split(':')[2]: SubElement(node, 'bgColor', {'theme':str(st.fill.end_color.index).split(':')[1], 'tint':str(st.fill.end_color.index).split(':')[2]}) else: SubElement(node, 'bgColor', {'theme':str(st.fill.end_color.index).split(':')[1]}) else: SubElement(node, 'bgColor', {'rgb':str(st.fill.end_color.index)}) index += 1 fills.attrib["count"] = str(index) return table def _write_borders(self): borders = SubElement(self._root, 'borders') # default border = SubElement(borders, 'border') SubElement(border, 'left') SubElement(border, 'right') SubElement(border, 'top') SubElement(border, 'bottom') SubElement(border, 'diagonal') # others table = {} index = 1 for st in self._style_list: if hash(st.borders) != hash(style.DEFAULTS.borders) and hash(st.borders) not in table: table[hash(st.borders)] = str(index) border = SubElement(borders, 'border') # caution: respect this order for side in ('left', 'right', 'top', 'bottom', 'diagonal'): obj = getattr(st.borders, side) if obj.border_style is None or obj.border_style == 'none': node = SubElement(border, side) attrs = {} else: node = SubElement(border, side, {'style':obj.border_style}) if str(obj.color.index).split(':')[0] == 'theme': # strip prefix theme if marked as such if str(obj.color.index).split(':')[2]: SubElement(node, 'color', {'theme':str(obj.color.index).split(':')[1], 'tint':str(obj.color.index).split(':')[2]}) else: SubElement(node, 'color', {'theme':str(obj.color.index).split(':')[1]}) else: SubElement(node, 'color', {'rgb':str(obj.color.index)}) index += 1 borders.attrib["count"] = str(index) return table def _write_cell_style_xfs(self): cell_style_xfs = SubElement(self._root, 'cellStyleXfs', {'count':'1'}) xf = SubElement(cell_style_xfs, 'xf', {'numFmtId':"0", 'fontId':"0", 'fillId':"0", 'borderId':"0"}) def _write_cell_xfs(self, number_format_table, fonts_table, fills_table, borders_table): """ write styles combinations based on ids found in tables """ # writing the cellXfs cell_xfs = SubElement(self._root, 'cellXfs', {'count':'%d' % (len(self._style_list) + 1)}) # default def _get_default_vals(): return dict(numFmtId='0', fontId='0', fillId='0', xfId='0', borderId='0') SubElement(cell_xfs, 'xf', _get_default_vals()) for st in self._style_list: vals = _get_default_vals() if hash(st.font) != hash(style.DEFAULTS.font): vals['fontId'] = fonts_table[hash(st.font)] vals['applyFont'] = '1' if hash(st.borders) != hash(style.DEFAULTS.borders): vals['borderId'] = borders_table[hash(st.borders)] vals['applyBorder'] = '1' if hash(st.fill) != hash(style.DEFAULTS.fill): vals['fillId'] = fills_table[hash(st.fill)] vals['applyFill'] = '1' if st.number_format != style.DEFAULTS.number_format: vals['numFmtId'] = '%d' % number_format_table[st.number_format] vals['applyNumberFormat'] = '1' if hash(st.alignment) != hash(style.DEFAULTS.alignment): vals['applyAlignment'] = '1' node = SubElement(cell_xfs, 'xf', vals) if hash(st.alignment) != hash(style.DEFAULTS.alignment): alignments = {} for align_attr in ['horizontal', 'vertical']: if hash(getattr(st.alignment, align_attr)) != hash(getattr(style.DEFAULTS.alignment, align_attr)): alignments[align_attr] = getattr(st.alignment, align_attr) if hash(st.alignment.wrap_text) != hash(style.DEFAULTS.alignment.wrap_text): alignments['wrapText'] = '1' if hash(st.alignment.shrink_to_fit) != hash(style.DEFAULTS.alignment.shrink_to_fit): alignments['shrinkToFit'] = '1' if st.alignment.indent > 0: alignments['indent'] = '%s' % st.alignment.indent if st.alignment.text_rotation > 0: alignments['textRotation'] = '%s' % st.alignment.text_rotation elif st.alignment.text_rotation < 0: alignments['textRotation'] = '%s' % (90 - st.alignment.text_rotation) SubElement(node, 'alignment', alignments) def _write_cell_style(self): cell_styles = SubElement(self._root, 'cellStyles', {'count':'1'}) cell_style = SubElement(cell_styles, 'cellStyle', {'name':"Normal", 'xfId':"0", 'builtinId':"0"}) def _write_dxfs(self): dxfs = SubElement(self._root, 'dxfs', {'count':'0'}) def _write_table_styles(self): table_styles = SubElement(self._root, 'tableStyles', {'count':'0', 'defaultTableStyle':'TableStyleMedium9', 'defaultPivotStyle':'PivotStyleLight16'}) def _write_number_formats(self): number_format_table = {} number_format_list = [] exceptions_list = [] num_fmt_id = 165 # start at a greatly higher value as any builtin can go num_fmt_offset = 0 for style in self._style_list: if not style.number_format in number_format_list : number_format_list.append(style.number_format) for number_format in number_format_list: if number_format.is_builtin(): btin = number_format.builtin_format_id(number_format.format_code) number_format_table[number_format] = btin else: number_format_table[number_format] = num_fmt_id + num_fmt_offset num_fmt_offset += 1 exceptions_list.append(number_format) num_fmts = SubElement(self._root, 'numFmts', {'count':'%d' % len(exceptions_list)}) for number_format in exceptions_list : SubElement(num_fmts, 'numFmt', {'numFmtId':'%d' % number_format_table[number_format], 'formatCode':'%s' % number_format.format_code}) return number_format_table openpyxl-1.7.0+ds1/openpyxl/writer/theme.py000066400000000000000000000253061224514475200207420ustar00rootroot00000000000000# -*- coding: utf-8 -*- # file openpyxl/writer/theme.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Write the theme xml based on a fixed string.""" # package imports from openpyxl.shared.xmltools import fromstring, get_document_content def write_theme(): """Write the theme xml.""" xml_node = fromstring( '\n' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '') return get_document_content(xml_node) openpyxl-1.7.0+ds1/openpyxl/writer/workbook.py000066400000000000000000000311551224514475200214740ustar00rootroot00000000000000# file openpyxl/writer/workbook.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Write the workbook global settings to the archive.""" # package imports from openpyxl.shared.xmltools import Element, SubElement from openpyxl.cell import absolute_coordinate from openpyxl.shared.xmltools import get_document_content, fromstring, register_namespace from openpyxl.shared.ooxml import NAMESPACES, ARC_CORE, ARC_WORKBOOK, \ ARC_APP, ARC_THEME, ARC_STYLE, ARC_SHARED_STRINGS, ARC_CONTENT_TYPES from openpyxl.shared.date_time import datetime_to_W3CDTF from openpyxl.namedrange import NamedRange, NamedRangeContainingValue def write_properties_core(properties): """Write the core properties to xml.""" root = Element('cp:coreProperties', {'xmlns:cp': NAMESPACES['cp'], 'xmlns:xsi': NAMESPACES['xsi'], 'xmlns:dc': NAMESPACES['dc'], 'xmlns:dcterms': NAMESPACES['dcterms'], 'xmlns:dcmitype': NAMESPACES['dcmitype'], }) SubElement(root, 'dc:creator').text = properties.creator SubElement(root, 'cp:lastModifiedBy').text = properties.last_modified_by SubElement(root, 'dcterms:created', \ {'xsi:type': 'dcterms:W3CDTF'}).text = \ datetime_to_W3CDTF(properties.created) SubElement(root, 'dcterms:modified', {'xsi:type': 'dcterms:W3CDTF'}).text = \ datetime_to_W3CDTF(properties.modified) SubElement(root, 'dc:title').text = properties.title SubElement(root, 'dc:description').text = properties.description SubElement(root, 'dc:subject').text = properties.subject SubElement(root, 'cp:keywords').text = properties.keywords SubElement(root, 'cp:category').text = properties.category return get_document_content(root) def write_content_types(workbook): """Write the content-types xml.""" seen = set() if workbook.vba_archive: root = fromstring(workbook.vba_archive.read(ARC_CONTENT_TYPES)) register_namespace('', 'http://schemas.openxmlformats.org/package/2006/content-types') for elem in root.findall('{http://schemas.openxmlformats.org/package/2006/content-types}Override'): seen.add(elem.attrib['PartName']) else: root = Element('Types', {'xmlns': 'http://schemas.openxmlformats.org/package/2006/content-types'}) SubElement(root, 'Override', {'PartName': '/' + ARC_THEME, 'ContentType': 'application/vnd.openxmlformats-officedocument.theme+xml'}) SubElement(root, 'Override', {'PartName': '/' + ARC_STYLE, 'ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml'}) SubElement(root, 'Default', {'Extension': 'rels', 'ContentType': 'application/vnd.openxmlformats-package.relationships+xml'}) SubElement(root, 'Default', {'Extension': 'xml', 'ContentType': 'application/xml'}) SubElement(root, 'Default', {'Extension': 'png', 'ContentType': 'image/png'}) SubElement(root, 'Override', {'PartName': '/' + ARC_WORKBOOK, 'ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml'}) SubElement(root, 'Override', {'PartName': '/' + ARC_APP, 'ContentType': 'application/vnd.openxmlformats-officedocument.extended-properties+xml'}) SubElement(root, 'Override', {'PartName': '/' + ARC_CORE, 'ContentType': 'application/vnd.openxmlformats-package.core-properties+xml'}) SubElement(root, 'Override', {'PartName': '/' + ARC_SHARED_STRINGS, 'ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml'}) drawing_id = 1 chart_id = 1 for sheet_id, sheet in enumerate(workbook.worksheets): part_name = '/xl/worksheets/sheet%d.xml' % (sheet_id + 1) if part_name not in seen: SubElement(root, 'Override', {'PartName': part_name, 'ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml'}) if sheet._charts or sheet._images: part_name = '/xl/drawings/drawing%d.xml' % drawing_id if part_name not in seen: SubElement(root, 'Override', {'PartName': part_name, 'ContentType': 'application/vnd.openxmlformats-officedocument.drawing+xml'}) drawing_id += 1 for chart in sheet._charts: part_name = '/xl/charts/chart%d.xml' % chart_id if part_name not in seen: SubElement(root, 'Override', {'PartName': part_name, 'ContentType': 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml'}) chart_id += 1 if chart._shapes: part_name = '/xl/drawings/drawing%d.xml' % drawing_id if part_name not in seen: SubElement(root, 'Override', {'PartName': part_name, 'ContentType': 'application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml'}) drawing_id += 1 return get_document_content(root) def write_properties_app(workbook): """Write the properties xml.""" worksheets_count = len(workbook.worksheets) root = Element('Properties', {'xmlns': 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties', 'xmlns:vt': 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'}) SubElement(root, 'Application').text = 'Microsoft Excel' SubElement(root, 'DocSecurity').text = '0' SubElement(root, 'ScaleCrop').text = 'false' SubElement(root, 'Company') SubElement(root, 'LinksUpToDate').text = 'false' SubElement(root, 'SharedDoc').text = 'false' SubElement(root, 'HyperlinksChanged').text = 'false' SubElement(root, 'AppVersion').text = '12.0000' # heading pairs part heading_pairs = SubElement(root, 'HeadingPairs') vector = SubElement(heading_pairs, 'vt:vector', {'size': '2', 'baseType': 'variant'}) variant = SubElement(vector, 'vt:variant') SubElement(variant, 'vt:lpstr').text = 'Worksheets' variant = SubElement(vector, 'vt:variant') SubElement(variant, 'vt:i4').text = '%d' % worksheets_count # title of parts title_of_parts = SubElement(root, 'TitlesOfParts') vector = SubElement(title_of_parts, 'vt:vector', {'size': '%d' % worksheets_count, 'baseType': 'lpstr'}) for ws in workbook.worksheets: SubElement(vector, 'vt:lpstr').text = '%s' % ws.title return get_document_content(root) def write_root_rels(workbook): """Write the relationships xml.""" root = Element('Relationships', {'xmlns': 'http://schemas.openxmlformats.org/package/2006/relationships'}) SubElement(root, 'Relationship', {'Id': 'rId1', 'Target': ARC_WORKBOOK, 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument'}) SubElement(root, 'Relationship', {'Id': 'rId2', 'Target': ARC_CORE, 'Type': 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties'}) SubElement(root, 'Relationship', {'Id': 'rId3', 'Target': ARC_APP, 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties'}) return get_document_content(root) def write_workbook(workbook): """Write the core workbook xml.""" root = Element('workbook', {'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', 'xml:space': 'preserve', 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}) SubElement(root, 'fileVersion', {'appName': 'xl', 'lastEdited': '4', 'lowestEdited': '4', 'rupBuild': '4505'}) SubElement(root, 'workbookPr', {'defaultThemeVersion': '124226', 'codeName': 'ThisWorkbook'}) book_views = SubElement(root, 'bookViews') SubElement(book_views, 'workbookView', {'activeTab': '%d' % workbook.get_index(workbook.get_active_sheet()), 'autoFilterDateGrouping': '1', 'firstSheet': '0', 'minimized': '0', 'showHorizontalScroll': '1', 'showSheetTabs': '1', 'showVerticalScroll': '1', 'tabRatio': '600', 'visibility': 'visible'}) # worksheets sheets = SubElement(root, 'sheets') for i, sheet in enumerate(workbook.worksheets): sheet_node = SubElement(sheets, 'sheet', {'name': sheet.title, 'sheetId': '%d' % (i + 1), 'r:id': 'rId%d' % (i + 1)}) if not sheet.sheet_state == sheet.SHEETSTATE_VISIBLE: sheet_node.set('state', sheet.sheet_state) # Defined names defined_names = SubElement(root, 'definedNames') # named ranges for named_range in workbook.get_named_ranges(): name = SubElement(defined_names, 'definedName', {'name': named_range.name}) if named_range.scope: name.set('localSheetId', '%s' % workbook.get_index(named_range.scope)) if isinstance(named_range, NamedRange): # as there can be many cells in one range, generate the list of ranges dest_cells = [] for worksheet, range_name in named_range.destinations: dest_cells.append("'%s'!%s" % (worksheet.title.replace("'", "''"), absolute_coordinate(range_name))) # finally write the cells list name.text = ','.join(dest_cells) else: assert isinstance(named_range, NamedRangeContainingValue) name.text = named_range.value # autoFilter for i, sheet in enumerate(workbook.worksheets): #continue auto_filter = sheet.auto_filter if not auto_filter: continue name = SubElement(defined_names, 'definedName', dict(name='_xlnm._FilterDatabase', localSheetId=str(i), hidden='1')) name.text = "'%s'!%s" % (sheet.title.replace("'", "''"), absolute_coordinate(auto_filter)) SubElement(root, 'calcPr', {'calcId': '124519', 'calcMode': 'auto', 'fullCalcOnLoad': '1'}) return get_document_content(root) def write_workbook_rels(workbook): """Write the workbook relationships xml.""" root = Element('Relationships', {'xmlns': 'http://schemas.openxmlformats.org/package/2006/relationships'}) for i in range(len(workbook.worksheets)): SubElement(root, 'Relationship', {'Id': 'rId%d' % (i + 1), 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet', 'Target': 'worksheets/sheet%s.xml' % (i + 1)}) rid = len(workbook.worksheets) + 1 SubElement(root, 'Relationship', {'Id': 'rId%d' % rid, 'Target': 'sharedStrings.xml', 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings'}) SubElement(root, 'Relationship', {'Id': 'rId%d' % (rid + 1), 'Target': 'styles.xml', 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles'}) SubElement(root, 'Relationship', {'Id': 'rId%d' % (rid + 2), 'Target': 'theme/theme1.xml', 'Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme'}) if workbook.vba_archive: SubElement(root, 'Relationship', {'Id': 'rId%d' % (rid + 3), 'Target': 'vbaProject.bin', 'Type': 'http://schemas.microsoft.com/office/2006/relationships/vbaProject'}) return get_document_content(root) openpyxl-1.7.0+ds1/openpyxl/writer/worksheet.py000066400000000000000000000317341224514475200216550ustar00rootroot00000000000000# file openpyxl/writer/worksheet.py # Copyright (c) 2010-2011 openpyxl # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # @license: http://www.opensource.org/licenses/mit-license.php # @author: see AUTHORS file """Write worksheets to xml representations.""" # Python stdlib imports import decimal, re # compatibility imports from openpyxl.shared.compat import BytesIO, StringIO try: # Python 2 isinstance(1, long) except NameError: # Python 3, all ints are long long = int # package imports from openpyxl.cell import coordinate_from_string, column_index_from_string from openpyxl.shared.xmltools import Element, SubElement, XMLGenerator, ElementTree, \ get_document_content, start_tag, end_tag, tag, fromstring, tostring, register_namespace from openpyxl.shared.compat.itertools import iteritems, iterkeys def row_sort(cell): """Translate column names for sorting.""" return column_index_from_string(cell.column) def write_etree(doc, element): start_tag(doc, element.tag, element) for e in element.getchildren(): write_etree(doc, e) end_tag(doc, element.tag) def write_worksheet(worksheet, string_table, style_table): """Write a worksheet to an xml file.""" if worksheet.xml_source: vba_root = fromstring(worksheet.xml_source) register_namespace("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships") register_namespace("", "http://schemas.openxmlformats.org/spreadsheetml/2006/main") else: vba_root = None xml_file = StringIO() doc = XMLGenerator(out=xml_file, encoding='utf-8') start_tag(doc, 'worksheet', {'xml:space': 'preserve', 'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}) if vba_root is not None: codename = vba_root.find('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}sheetPr').get('codeName', worksheet.title) start_tag(doc, 'sheetPr', {"codeName": codename}) else: start_tag(doc, 'sheetPr') tag(doc, 'outlinePr', {'summaryBelow': '%d' % (worksheet.show_summary_below), 'summaryRight': '%d' % (worksheet.show_summary_right)}) if worksheet.page_setup.fitToPage: tag(doc, 'pageSetUpPr', {'fitToPage':'1'}) end_tag(doc, 'sheetPr') tag(doc, 'dimension', {'ref': '%s' % worksheet.calculate_dimension()}) write_worksheet_sheetviews(doc, worksheet) tag(doc, 'sheetFormatPr', {'defaultRowHeight': '15'}) write_worksheet_cols(doc, worksheet) write_worksheet_data(doc, worksheet, string_table, style_table) if worksheet.auto_filter: tag(doc, 'autoFilter', {'ref': worksheet.auto_filter}) write_worksheet_mergecells(doc, worksheet) write_worksheet_datavalidations(doc, worksheet) write_worksheet_hyperlinks(doc, worksheet) options = worksheet.page_setup.options if options: tag(doc, 'printOptions', options) margins = worksheet.page_margins.margins if margins: tag(doc, 'pageMargins', margins) setup = worksheet.page_setup.setup if setup: tag(doc, 'pageSetup', setup) if worksheet.header_footer.hasHeader() or worksheet.header_footer.hasFooter(): start_tag(doc, 'headerFooter') if worksheet.header_footer.hasHeader(): tag(doc, 'oddHeader', None, worksheet.header_footer.getHeader()) if worksheet.header_footer.hasFooter(): tag(doc, 'oddFooter', None, worksheet.header_footer.getFooter()) end_tag(doc, 'headerFooter') if worksheet._charts or worksheet._images: tag(doc, 'drawing', {'r:id':'rId1'}) # if the sheet has an xml_source field then the workbook must have # been loaded with keep-vba true and we need to extract any control # elements. if vba_root is not None: for t in ('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}legacyDrawing', '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}controls'): for elem in vba_root.findall(t): xml_file.write(re.sub(r' xmlns[^ >]*', '', tostring(elem).decode("utf-8"))) breaks = worksheet.page_breaks if breaks: start_tag(doc, 'rowBreaks', {'count': str(len(breaks)), 'manualBreakCount': str(len(breaks))}) for b in breaks: tag(doc, 'brk', {'id': str(b), 'man': 'true', 'max': '16383', 'min': '0'}) end_tag(doc, 'rowBreaks') end_tag(doc, 'worksheet') doc.endDocument() xml_string = xml_file.getvalue() xml_file.close() return xml_string def write_worksheet_sheetviews(doc, worksheet): start_tag(doc, 'sheetViews') start_tag(doc, 'sheetView', {'workbookViewId': '0'}) selectionAttrs = {} topLeftCell = worksheet.freeze_panes if topLeftCell: colName, row = coordinate_from_string(topLeftCell) column = column_index_from_string(colName) pane = 'topRight' paneAttrs = {} if column > 1: paneAttrs['xSplit'] = str(column - 1) if row > 1: paneAttrs['ySplit'] = str(row - 1) pane = 'bottomLeft' if column > 1: pane = 'bottomRight' paneAttrs.update(dict(topLeftCell=topLeftCell, activePane=pane, state='frozen')) tag(doc, 'pane', paneAttrs) selectionAttrs['pane'] = pane if row > 1 and column > 1: tag(doc, 'selection', {'pane': 'topRight'}) tag(doc, 'selection', {'pane': 'bottomLeft'}) selectionAttrs.update({'activeCell': worksheet.active_cell, 'sqref': worksheet.selected_cell}) tag(doc, 'selection', selectionAttrs) end_tag(doc, 'sheetView') end_tag(doc, 'sheetViews') def write_worksheet_cols(doc, worksheet): """Write worksheet columns to xml.""" if worksheet.column_dimensions: start_tag(doc, 'cols') for column_string, columndimension in \ iteritems(worksheet.column_dimensions): col_index = column_index_from_string(column_string) col_def = {} col_def['min'] = str(col_index) col_def['max'] = str(col_index) if columndimension.width != \ worksheet.default_column_dimension.width: col_def['customWidth'] = 'true' if not columndimension.visible: col_def['hidden'] = 'true' if columndimension.outline_level > 0: col_def['outlineLevel'] = str(columndimension.outline_level) if columndimension.collapsed: col_def['collapsed'] = 'true' if columndimension.auto_size: col_def['bestFit'] = 'true' if columndimension.style_index: col_def['style'] = str(columndimension.style_index) if columndimension.width > 0: col_def['width'] = str(columndimension.width) else: col_def['width'] = '9.10' tag(doc, 'col', col_def) end_tag(doc, 'cols') def write_worksheet_data(doc, worksheet, string_table, style_table): """Write worksheet data to xml.""" start_tag(doc, 'sheetData') max_column = worksheet.get_highest_column() style_id_by_hash = style_table cells_by_row = {} for styleCoord in iterkeys(worksheet._styles): # Ensure a blank cell exists if it has a style worksheet.cell(styleCoord) for cell in worksheet.get_cell_collection(): cells_by_row.setdefault(cell.row, []).append(cell) for row_idx in sorted(cells_by_row): row_dimension = worksheet.row_dimensions[row_idx] attrs = {'r': '%d' % row_idx, 'spans': '1:%d' % max_column} if not row_dimension.visible: attrs['hidden'] = '1' if row_dimension.height > 0: attrs['ht'] = str(row_dimension.height) attrs['customHeight'] = '1' start_tag(doc, 'row', attrs) row_cells = cells_by_row[row_idx] sorted_cells = sorted(row_cells, key=row_sort) for cell in sorted_cells: value = cell._value coordinate = cell.get_coordinate() attributes = {'r': coordinate} if cell.data_type != cell.TYPE_FORMULA: attributes['t'] = cell.data_type if coordinate in worksheet._styles: attributes['s'] = '%d' % style_id_by_hash[ hash(worksheet._styles[coordinate])] if value in ('', None): tag(doc, 'c', attributes) else: start_tag(doc, 'c', attributes) if cell.data_type == cell.TYPE_STRING: tag(doc, 'v', body='%s' % string_table[value]) elif cell.data_type == cell.TYPE_FORMULA: tag(doc, 'f', body='%s' % value[1:]) tag(doc, 'v') elif cell.data_type == cell.TYPE_NUMERIC: if isinstance(value, (long, decimal.Decimal)): func = str else: func = repr tag(doc, 'v', body=func(value)) elif cell.data_type == cell.TYPE_BOOL: tag(doc, 'v', body='%d' % value) else: tag(doc, 'v', body='%s' % value) end_tag(doc, 'c') end_tag(doc, 'row') end_tag(doc, 'sheetData') def write_worksheet_mergecells(doc, worksheet): """Write merged cells to xml.""" if len(worksheet._merged_cells) > 0: start_tag(doc, 'mergeCells', {'count': str(len(worksheet._merged_cells))}) for range_string in worksheet._merged_cells: attrs = {'ref': range_string} tag(doc, 'mergeCell', attrs) end_tag(doc, 'mergeCells') def write_worksheet_datavalidations(doc, worksheet): """ Write data validation(s) to xml.""" # Filter out "empty" data-validation objects (i.e. with 0 cells) required_dvs = [x for x in worksheet._data_validations if len(x.cells) or len(x.ranges)] count = len(required_dvs) if count == 0: return start_tag(doc, 'dataValidations', {'count': str(count)}) for data_validation in required_dvs: start_tag(doc, 'dataValidation', data_validation.generate_attributes_map()) if data_validation.formula1: tag(doc, 'formula1', body=data_validation.formula1) if data_validation.formula2: tag(doc, 'formula2', body=data_validation.formula2) end_tag(doc, 'dataValidation') end_tag(doc, 'dataValidations') def write_worksheet_hyperlinks(doc, worksheet): """Write worksheet hyperlinks to xml.""" write_hyperlinks = False for cell in worksheet.get_cell_collection(): if cell.hyperlink_rel_id is not None: write_hyperlinks = True break if write_hyperlinks: start_tag(doc, 'hyperlinks') for cell in worksheet.get_cell_collection(): if cell.hyperlink_rel_id is not None: attrs = {'display': cell.hyperlink, 'ref': cell.get_coordinate(), 'r:id': cell.hyperlink_rel_id} tag(doc, 'hyperlink', attrs) end_tag(doc, 'hyperlinks') def write_worksheet_rels(worksheet, idx): """Write relationships for the worksheet to xml.""" root = Element('Relationships', {'xmlns': 'http://schemas.openxmlformats.org/package/2006/relationships'}) for rel in worksheet.relationships: attrs = {'Id': rel.id, 'Type': rel.type, 'Target': rel.target} if rel.target_mode: attrs['TargetMode'] = rel.target_mode SubElement(root, 'Relationship', attrs) if worksheet._charts or worksheet._images: attrs = {'Id' : 'rId1', 'Type' : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing', 'Target' : '../drawings/drawing%s.xml' % idx } SubElement(root, 'Relationship', attrs) return get_document_content(root) openpyxl-1.7.0+ds1/setup.py000077500000000000000000000033221224514475200156030ustar00rootroot00000000000000#!/usr/bin/env python """Setup script for packaging openpyxl. Requires setuptools. To build the setuptools egg use python setup.py bdist_egg and either upload it to the PyPI with: python setup.py upload or upload to your own server and register the release with PyPI: python setup.py register A source distribution (.zip) can be built with python setup.py sdist --format=zip That uses the manifest.in file for data files rather than searching for them here. """ import sys import warnings if sys.version_info < (2, 5): raise Exception("Python >= 2.6 is required.") elif sys.version_info[:2] == (2, 5): warnings.warn("Support for Python 2.5 will be dropped in the next release.") from setuptools import setup, Extension, find_packages import openpyxl # to fetch __version__ etc setup(name = 'openpyxl', packages = find_packages(), # metadata version = openpyxl.__version__, description = "A Python library to read/write Excel 2007 xlsx/xlsm files", long_description = 'openpyxl is a pure python reader and writer of ' 'Excel OpenXML files. It is ported from the PHPExcel project', author = openpyxl.__author__, author_email = openpyxl.__author_email__, url = openpyxl.__url__, license = openpyxl.__license__, download_url = openpyxl.__downloadUrl__, test_suite = 'nose.collector', tests_require = ['nose'], classifiers = ['Development Status :: 4 - Beta', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3'], ) openpyxl-1.7.0+ds1/tox.ini000066400000000000000000000005301224514475200153770ustar00rootroot00000000000000# Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py25, py26, py27, py33, py32 [testenv] commands = {envpython} setup.py test deps =