Compare commits

...

199 Commits

Author SHA1 Message Date
Kenneth Reitz 9146de36d4 Merge branch 'release/0.9.7' 2011-05-13 01:15:06 -04:00
Kenneth Reitz 9761ff5e9e hmmm 2011-05-13 01:13:50 -04:00
Kenneth Reitz e5259cbb58 version bump 2011-05-13 01:13:25 -04:00
Kenneth Reitz 56ef89424f fallback on from xml.etree.ElementTree for pypy 2011-05-13 01:05:11 -04:00
Kenneth Reitz 4a01299293 v0.9.7 release notes 2011-05-13 00:40:36 -04:00
Kenneth Reitz 9399bf2fe7 no vendored tests 2011-05-13 00:32:02 -04:00
Kenneth Reitz cbdaa09e83 success!! 2011-05-13 00:30:03 -04:00
Kenneth Reitz f30e760657 less compat reliance 2011-05-13 00:29:51 -04:00
Kenneth Reitz a60e2f132e Finally! :sparkles:Python 3 port of openpyxl 2011-05-13 00:28:50 -04:00
Kenneth Reitz 2b36d71554 additional compat mappings 2011-05-12 19:16:37 -04:00
Kenneth Reitz 690de63b7c ugh 2011-05-12 19:16:24 -04:00
Kenneth Reitz 6b6ef70c61 Convert openpyxl to relative imports 2011-05-12 17:54:00 -04:00
Kenneth Reitz 322283b8f9 Merge pull request #9 from f4nt/tablib
---

I thought I was going to wait til later to do this, but once I got started I couldnt stop myself I guess. I believe this fixes the row limit issues that I mentioned earlier by adding XLSX support. Ive tested it up to 75k rows. Believe the tests and setup.py should be squared away properly as well. Im a bit concerned about the pane freezing stuff, as thats completely undocumented in openpyxl. Truth be told, I barely even know what that functionality does. I hate spreadsheets :)

Anyways, let me know if you want any changes or anything made. Dont have to worry about hurting my feelings or anything!

Conflicts:
	tablib/core25.py
	test_tablib.py
2011-05-12 17:40:17 -04:00
Kenneth Reitz 3968729903 html out 2011-05-12 17:38:53 -04:00
Kenneth Reitz 7b1e533e39 Merge pull request #8 from cswegger/tablib
---

This pull request is to fix pickling / unpickling of Row within Dataset. __getstate__ resembled a dictionary comprehension (Python 2.7+) but it wasnt, and it caused wrong values to be pickled, leading to unusable objects after restoring.

This patch fixes the issues. All unit tests still pass.

Conflicts:
	tablib/core25.py
2011-05-12 17:35:54 -04:00
Kenneth Reitz 8dd7d73abc compat module notes 2011-05-12 17:27:17 -04:00
Kenneth Reitz 176c9615d6 Merge branch 'feature/compat' into develop
Conflicts:
	tablib/core25.py
2011-05-12 17:26:08 -04:00
Kenneth Reitz c65fd4201f Merge branch 'compat' into feature/compat 2011-05-12 17:24:35 -04:00
Kenneth Reitz 11bca4f7a2 callable check fix 2011-05-12 17:20:22 -04:00
Kenneth Reitz 2b5818598a compat module 2011-05-12 17:17:10 -04:00
Kenneth Reitz 79fb82d69d compat module 2011-05-12 17:00:13 -04:00
Mark Rogers 5350355fbe a bunch of cleanup from my previous commit 2011-05-12 15:59:57 -05:00
Kenneth Reitz 85673b365c no more core25 2011-05-12 16:26:22 -04:00
Mark Rogers 87ce64d4c8 Merely a proof of concept, soon to be tidied up 2011-05-12 15:12:46 -05:00
Kenneth Reitz 2cd381389c Merge pull request #8 from cswegger/tablib
---

This pull request is to fix pickling / unpickling of Row within Dataset. __getstate__ resembled a dictionary comprehension (Python 2.7+) but it wasnt, and it caused wrong values to be pickled, leading to unusable objects after restoring.

This patch fixes the issues. All unit tests still pass.
2011-05-12 10:14:39 -04:00
Luca Beltrame 35f21cf73e Fix pickling/unpickling of Dataset instances. 2011-05-12 16:04:46 +02:00
Kenneth Reitz 0ebc8f5e1b Merge branch 'release/0.9.6' into develop 2011-05-12 02:53:15 -04:00
Kenneth Reitz 865ce62782 Merge branch 'release/0.9.6' 2011-05-12 02:53:12 -04:00
Kenneth Reitz 3b961c59e7 version bump 2011-05-12 02:52:35 -04:00
Kenneth Reitz 4be341be4f history: unicode+csv support
refs #7
2011-05-12 02:35:07 -04:00
Kenneth Reitz 2c4337b317 Merge branch 'develop' into feature/compat 2011-05-12 02:31:16 -04:00
Kenneth Reitz 0e4128c73e Erik Youngren to authors 2011-05-12 02:30:39 -04:00
Kenneth Reitz 4ebd66cb09 Merge branch 'bug/csv-unicode' into develop
Closes #7

Conflicts:
	test_tablib.py
2011-05-12 02:28:03 -04:00
Kenneth Reitz bfcfa37ebb Python3 support for csv module.
Refs #7
2011-05-12 02:24:14 -04:00
Kenneth Reitz 5c50c1822e integration of unicodecsv module
refs #7
2011-05-12 02:01:07 -04:00
Kenneth Reitz 2e5577ee91 move csv-unicode branch to bug/csv-unicode
refs #7
2011-05-12 01:52:48 -04:00
Kenneth Reitz 84e4bd9a47 added csv/unicode test 2011-05-12 01:47:49 -04:00
Kenneth Reitz 7270ce49e1 testing webhook 2011-05-11 23:37:38 -04:00
Kenneth Reitz c3052cc02c kill fabfile 2011-05-11 22:57:12 -04:00
Kenneth Reitz 999c49a4f0 initial compat module 2011-05-11 19:01:35 -04:00
Kenneth Reitz 59c996f9df history update 2011-05-11 18:21:44 -04:00
Kenneth Reitz a2b62669b7 seperator => separator 2011-05-11 17:58:31 -04:00
Kenneth Reitz 15e25ef735 no more reqs 2011-05-10 21:24:56 -04:00
Kenneth Reitz 7ae7d3ff46 Update TODOs 2011-04-05 08:56:20 -04:00
Kenneth Reitz 69ed718191 make it mobile! 2011-03-24 16:46:00 -04:00
Kenneth Reitz 328d3880d5 added google analytics to generated docs 2011-03-24 13:53:03 -04:00
Kenneth Reitz ea3cc847a0 updated roadmap 2011-03-24 06:16:34 -04:00
Kenneth Reitz 8efab51355 Merge branch 'develop' 2011-03-24 06:11:02 -04:00
Kenneth Reitz e42d215833 sphinx version # fix. 2011-03-24 06:09:15 -04:00
Kenneth Reitz 10bc5549c9 Merge branch 'release/0.9.5' 2011-03-24 05:58:50 -04:00
Kenneth Reitz 1a5e2ecb33 release date bump 2011-03-24 05:57:22 -04:00
Kenneth Reitz e1bf189847 setup.py says 25,26,27,30,31,32 2011-03-23 05:47:49 -04:00
Kenneth Reitz 0785328e21 updated HISTORY 2011-03-23 04:53:24 -04:00
Kenneth Reitz 6ba0cc9af3 3.1, 3.2 support. 2011-03-23 04:49:07 -04:00
Kenneth Reitz 36876205e7 3.2 compatibility 2011-03-23 04:48:58 -04:00
Kenneth Reitz 1b97b7191e pre-version bump 2011-03-23 04:24:25 -04:00
Kenneth Reitz 8b575df419 version check in setup.py fix 2011-03-23 04:24:12 -04:00
Kenneth Reitz 6a3928759a 25 support 2011-03-23 04:13:49 -04:00
Kenneth Reitz 63348d883b fix imports for 2x 2011-03-23 04:04:03 -04:00
Kenneth Reitz 5dce600969 xls import fix 2011-03-23 04:00:05 -04:00
Kenneth Reitz 0913b54f47 this isn't an apple 2011-03-23 03:56:07 -04:00
Kenneth Reitz c5bbc74b96 import magic 2011-03-23 03:55:23 -04:00
Kenneth Reitz 7f5342a1b8 no need for simplejson anymore 2011-03-23 02:27:40 -04:00
Kenneth Reitz d42f9bc10f python3 tox config for jenkins 2011-03-23 02:23:43 -04:00
Kenneth Reitz c6565c9e29 2.5 compatible version checking 2011-03-23 02:22:10 -04:00
Kenneth Reitz 1a9343750e Merge branch 'feature/3.x' 2011-03-23 02:08:40 -04:00
Kenneth Reitz 8a393214c8 add tox tests for 3.x 2011-03-23 02:08:04 -04:00
Kenneth Reitz b8ed741a36 Same codebase for 2.x and 3.x! 2011-03-23 02:07:39 -04:00
Kenneth Reitz cddbd78a61 autoload 3 modules if using 3 2011-03-23 02:02:59 -04:00
Kenneth Reitz b113f49ce6 python3 AND python2 packages. 2011-03-23 01:59:19 -04:00
Kenneth Reitz 1429b9f8c4 xlwrt3 if python 3 2011-03-23 01:56:57 -04:00
Kenneth Reitz 42700f98a5 check python version 2011-03-23 01:47:10 -04:00
Kenneth Reitz 0e56db632a xlwt3 package 2011-03-23 01:47:01 -04:00
Kenneth Reitz b07512071e cyaml fixes 2011-03-23 01:38:31 -04:00
Kenneth Reitz e4881809d6 BytesIO in xls for 3.x 2011-03-23 01:38:20 -04:00
Kenneth Reitz 54ab300d2d markup changes for 3.x 2011-03-23 01:37:33 -04:00
Kenneth Reitz 4368d64317 xlwrt cleanups 2011-03-23 01:37:14 -04:00
Kenneth Reitz 117344de14 2to3! 2011-03-23 01:13:16 -04:00
Kenneth Reitz 58bc1c7dcf add xlwt 3.x 2011-03-23 01:13:03 -04:00
Kenneth Reitz 4c8b5e72e3 remove 2.x xlwt 2011-03-23 01:12:48 -04:00
Kenneth Reitz b900236157 testing whitespace cleanup 2011-03-23 00:50:27 -04:00
Kenneth Reitz dc14a16e04 added formatter tests 2011-03-23 00:49:32 -04:00
Kenneth Reitz 2d2ac9b708 col not key 2011-03-23 00:49:25 -04:00
Kenneth Reitz 1efcb7a63d Merge branch 'feature/formatters' 2011-03-23 00:39:16 -04:00
Kenneth Reitz 65c73dfc42 do some internal validation for adding formatters 2011-03-23 00:38:45 -04:00
Kenneth Reitz 3803a7a21b formatter execution in place upon export 2011-03-23 00:32:56 -04:00
Kenneth Reitz 8b5b29fc90 added Dataset.add_formatter 2011-03-23 00:20:39 -04:00
Kenneth Reitz e8ba765426 tablib.helpers is not longer needed 🍰 2011-03-23 00:03:33 -04:00
Kenneth Reitz 57001a5465 Merge https://github.com/mwhooker/tablib into develop 2011-03-22 23:57:42 -04:00
Matthew Hooker c8493ff047 fixes issue #38: python 2.5 support 2011-03-22 17:16:30 -04:00
Kenneth Reitz 03914323c2 Merge https://github.com/playpauseandstop/tablib into develop 2011-03-01 17:06:15 -05:00
Igor Davydenko 2f331cee8e Fix #24, add support of spaces in CSV files. 2011-03-01 19:36:19 +02:00
Kenneth Reitz e1734f2315 added __docformat__ 2011-02-21 14:07:42 -05:00
Kenneth Reitz 22cddbcd63 well then 2011-02-21 02:55:24 -05:00
Kenneth Reitz 76f09cd3b3 Added release number to documentation. 2011-02-21 02:37:53 -05:00
Kenneth Reitz d11c09febe Added License text. 2011-02-21 02:37:29 -05:00
Kenneth Reitz 9ab277a468 docs: Python 2.5 is supported now. 2011-02-21 02:32:20 -05:00
Kenneth Reitz 23c1831144 Added HACKING file. 2011-02-21 02:15:00 -05:00
Kenneth Reitz 1cf9bd14b4 ci.kennethreitz.com now 2011-02-21 02:12:41 -05:00
Kenneth Reitz c2331f7a23 Updated pythons list. 2011-02-21 02:11:59 -05:00
Kenneth Reitz bccf0d1ba1 no more test suite. 2011-02-18 04:37:00 -05:00
Kenneth Reitz c219972ccd added pypy location to tox config 2011-02-18 04:28:50 -05:00
Kenneth Reitz 52e9d44739 typo 2011-02-18 03:42:54 -05:00
Kenneth Reitz e94ecd8472 Small doc updates 2011-02-18 03:41:54 -05:00
Kenneth Reitz 96067e6380 Merge branch 'release/0.9.4' 2011-02-18 03:36:27 -05:00
Kenneth Reitz 1cc0f7d1f4 Version Bump (v0.9.4) 2011-02-18 03:34:59 -05:00
Kenneth Reitz f685bf548e latex stylee 2011-02-18 03:27:28 -05:00
Kenneth Reitz ca336926da junit's out 2011-02-18 03:27:12 -05:00
Kenneth Reitz 1aa3d3b06a removing coverage from scm 2011-02-18 03:15:39 -05:00
Kenneth Reitz be576135b2 Added 0.9.4 to History 2011-02-18 03:14:12 -05:00
Kenneth Reitz 0c05d0497e Added OrderedDict support. 2011-02-18 03:13:44 -05:00
Kenneth Reitz 52e307ea35 Docstring update 2011-02-18 02:59:07 -05:00
Kenneth Reitz 5cac9bd97e Python 2.5 added to compatible language list 2011-02-18 02:42:57 -05:00
Kenneth Reitz a285e993f1 add simplejson to requirements for 2.5 2011-02-18 02:42:37 -05:00
Kenneth Reitz 0ed367a31c I can see how that would cause a problem. 2011-02-18 02:34:59 -05:00
Kenneth Reitz c4815c24cc i haz the skillz 2011-02-18 02:07:42 -05:00
Kenneth Reitz 20fe1e0153 py.test now 2011-02-18 01:55:56 -05:00
Kenneth Reitz 5db8d1c3a6 that makes more sense 2011-02-18 01:44:59 -05:00
Kenneth Reitz 828017f9a7 wtf? is that even valid? 2011-02-18 01:34:18 -05:00
Kenneth Reitz cff8a6ac9a i've got to figure this out 2011-02-18 01:31:21 -05:00
Kenneth Reitz aa8590e8b8 json 2011-02-18 01:30:17 -05:00
Kenneth Reitz d2de647c47 added simplejson to tox config. 2011-02-18 01:29:16 -05:00
Kenneth Reitz 7afef680f5 fixed nose issue 2011-02-18 01:26:30 -05:00
Kenneth Reitz 35763f8c24 fix tox configuration 2011-02-17 20:31:02 -05:00
Kenneth Reitz cc3d020914 clear nosetests each time 2011-02-17 20:15:35 -05:00
Kenneth Reitz b8b5405f1c setup.py package fix 2011-02-17 20:13:01 -05:00
Kenneth Reitz b7aebbc74f anyjson in setup.py 2011-02-17 20:11:08 -05:00
Kenneth Reitz d776d78df5 Added AnyJSON to json format system 2011-02-17 20:09:07 -05:00
Kenneth Reitz 6f9365d376 Added AnyJSON 2011-02-17 20:02:14 -05:00
Kenneth Reitz 621b1bd45c Added AnyJSON license. 2011-02-17 20:02:07 -05:00
Kenneth Reitz be21b6fadd Remove vendorized SimpleJSON 2011-02-17 20:01:59 -05:00
Kenneth Reitz 832bfbbb1b this should work better 2011-02-17 19:54:10 -05:00
Kenneth Reitz 288b15fb54 tox coverage 2011-02-17 19:37:52 -05:00
Kenneth Reitz 73df22303b no more failing tests? 2011-02-17 16:47:42 -05:00
Kenneth Reitz 4c125bd206 8tabstop? really? 2011-02-17 16:36:28 -05:00
Kenneth Reitz ff0de1377a wow, that was ugly 2011-02-17 16:35:36 -05:00
Kenneth Reitz ccb29c68fa *shutter* making everyone else happy 2011-02-17 16:31:52 -05:00
Kenneth Reitz e077a7f2bc typo 2011-02-16 13:17:03 -05:00
Kenneth Reitz dcc52bdc18 Added Benjamin Wohlwend to AUTHORS 2011-02-14 04:16:21 -05:00
Benjamin Wohlwend 9cac54eefc Python 2.5 doesn't support @property.setter 2011-02-14 10:06:02 +01:00
Kenneth Reitz f69a96f07e Readme.rst improvements 2011-02-13 21:11:02 -05:00
Kenneth Reitz ca77ed6f64 documentation url style update 2011-02-10 10:18:58 -05:00
Kenneth Reitz 806aba9ef3 spelling corrections 2011-02-10 10:18:30 -05:00
Kenneth Reitz 23cbc0c333 More dynamic __slots__ 2011-02-03 13:52:53 -05:00
Kenneth Reitz 34ab54de77 Merge branch 'master' into develop 2011-02-02 21:34:08 -05:00
Kenneth Reitz 0843a15879 configuration fix for RTD 2011-02-02 21:33:41 -05:00
Kenneth Reitz 08ed309382 Merge branch 'release/0.9.3' into develop 2011-01-31 01:36:14 -05:00
Kenneth Reitz 26b6faa88d Merge branch 'release/0.9.3' 2011-01-31 01:35:52 -05:00
Kenneth Reitz 140736ff33 fabfile typo. 2011-01-31 01:34:40 -05:00
Kenneth Reitz 5379c5683d Markup license notice.
PD? Really?
2011-01-31 01:33:12 -05:00
Kenneth Reitz e8b44b5777 Version bump. 2011-01-31 01:33:00 -05:00
Kenneth Reitz a0822bc9b0 sorting update. 2011-01-31 01:29:41 -05:00
Kenneth Reitz 89b431213b Sorting update for headerless datasets. 2011-01-31 01:28:10 -05:00
Kenneth Reitz 695e8c5af7 Merge branch 'feature/sorting' into release/0.9.3 2011-01-31 00:58:27 -05:00
Kenneth Reitz 0797ec67d4 Prepping for new release (0.9.3) 2011-01-31 00:58:16 -05:00
Kenneth Reitz 1852624a7e Merge pull request #28 from cswegger/tablib
---

This patch provides simple column-based sorting to tablib. A (passing) unit-test is also included.
2011-01-22 18:35:34 -05:00
Luca Beltrame f81dc41a57 Support for sorting. Unit-tested. 2011-01-11 20:53:59 +01:00
Kenneth Reitz 34415b89b8 New Year! 2011-01-10 19:28:12 -05:00
Kenneth Reitz d25655588b TODO update. 2010-12-13 17:08:11 -05:00
Kenneth Reitz 22c4d185e1 Export HTML for Databooks. 2010-11-21 21:33:01 -05:00
Kenneth Reitz e3b3659ea4 whitespace fix 2010-11-21 21:32:00 -05:00
Kenneth Reitz 22d337790a small changes to html output 2010-11-21 18:58:30 -05:00
Kenneth Reitz 0784d4b32c Updated todo w/ new html output feature 2010-11-21 18:55:45 -05:00
Kenneth Reitz 332c5bccd9 Merge branch 'feature/html_out' into develop 2010-11-21 18:53:21 -05:00
Kenneth Reitz 7055d18a2e History update. 2010-11-21 18:53:18 -05:00
Kenneth Reitz 6a7c685111 Import path fix. 2010-11-21 18:49:02 -05:00
Kenneth Reitz 0e5b8f7058 Merge branch 'fix_databooks' into develop 2010-11-21 18:44:24 -05:00
Luca Beltrame e3e6b656e3 Fix the stupid mistake. 2010-11-21 13:17:36 +01:00
Luca Beltrame 99896a5f28 Fix Databook data leaks. 2010-11-21 13:14:47 +01:00
Luca Beltrame 25da44f569 Support for HTML (export only). Unit-tested. Depends on the "markup.py"
package(http://markup.sourceforge.net) which is included in packages/
Notice that the tests now depend on the presence of markup.py.
2010-11-21 13:00:56 +01:00
Kenneth Reitz 7727171379 Merge branch 'release/0.9.2' 2010-11-17 21:00:56 -05:00
Kenneth Reitz 91bd4eb9c7 Updated history 2010-11-17 21:00:13 -05:00
Kenneth Reitz 9b74b139fd Ordered dict in TODO 2010-11-17 21:00:01 -05:00
Kenneth Reitz 823a543f41 Version bump (v0.9.2) 2010-11-17 20:58:50 -05:00
Kenneth Reitz 1aa275bf99 Updated TODO. 2010-11-17 20:55:38 -05:00
Kenneth Reitz 17bb0d3b2c Merge branch 'feature/stacking' into develop 2010-11-17 20:49:08 -05:00
Kenneth Reitz 1a9aee9289 Column stacking only requires headers if headers exist. 2010-11-17 20:48:50 -05:00
Kenneth Reitz 196edb82cc trailing whitespae 2010-11-17 20:02:08 -05:00
Kenneth Reitz a2990d5852 Change stacking method names. 2010-11-17 20:01:31 -05:00
Kenneth Reitz d992ece86a Merge branch 'stacking' into feature/stacking 2010-11-17 19:56:04 -05:00
Kenneth Reitz 46f302255d Updated prophesy. 2010-11-17 19:54:50 -05:00
Kenneth Reitz 9e3ab4c13f Support for locked header row. 2010-11-17 19:50:22 -05:00
Kenneth Reitz eaed0e48c2 Formating. 2010-11-17 19:50:05 -05:00
Kenneth Reitz 501187b357 Merge branch 'feature/pickling' into develop 2010-11-17 19:15:51 -05:00
Kenneth Reitz ea4aef88b6 Subtle format fixes. 2010-11-17 19:15:36 -05:00
Luca Beltrame 24d800fac3 Support for pickling/unpickling Row objects. Makes Datasets pickleable. 2010-11-17 23:03:43 +01:00
Luca Beltrame d8136ab613 Whitespace 2010-11-17 22:51:43 +01:00
Luca Beltrame 36bbe2726b Remove unneded import 2010-11-15 09:00:57 +01:00
Luca Beltrame 1427be2901 Support for row and column stacking. Unit-tested. 2010-11-15 08:59:49 +01:00
Kenneth Reitz 10ce000d31 Updated changelog. 2010-11-11 11:02:14 -05:00
Kenneth Reitz a91254117c Added ordered dict library. 2010-11-11 11:02:07 -05:00
Kenneth Reitz b67762604f Merge branch 'transpose' into develop 2010-11-11 10:59:08 -05:00
Kenneth Reitz 83a8346e8f Added ordered dict license. 2010-11-11 10:58:48 -05:00
Luca Beltrame 657ab98d04 Support for Dataset transposition. Unit-tested. 2010-11-11 09:00:06 +01:00
Kenneth Reitz 9ddb4de942 Documentation typo. 2010-11-09 12:27:15 -05:00
Kenneth Reitz 5fad80a540 Update column append examples. 2010-11-09 08:43:34 -05:00
Kenneth Reitz cabab73045 Spacing fixes. 2010-11-09 08:42:51 -05:00
Kenneth Reitz 2bb0525990 Optimized set intersection for tag checking. 2010-11-05 09:46:14 -04:00
Kenneth Reitz f364bb576e Merge branch 'release/0.9.1' into develop 2010-11-04 12:08:08 -04:00
168 changed files with 32165 additions and 6132 deletions
+5
View File
@@ -18,3 +18,8 @@ profile
# vi noise
*.swp
docs/_build/*
coverage.xml
nosetests.xml
junit-py25.xml
junit-py26.xml
junit-py27.xml
+5 -2
View File
@@ -4,7 +4,7 @@ various contributors:
Development Lead
````````````````
- Kenneth Reitz <me@kennethreitz.com>
- Kenneth Reitz <_@kennethreitz.com>
Patches and Suggestions
@@ -12,4 +12,7 @@ Patches and Suggestions
- Luke Lee
- Josh Ourisman
- Luca Beltrame
- Luca Beltrame
- Benjamin Wohlwend
- Erik Youngren
- Mark Rogers
+14
View File
@@ -0,0 +1,14 @@
Where possible, please follow PEP8 with regard to coding style. Sometimes the line
length restriction is too hard to follow, so don't bend over backwards there.
Triple-quotes should always be """, single quotes are ' unless using "
would result in less escaping within the string.
All modules, functions, and methods should be well documented reStructuredText for
Sphinx AutoDoc.
All functionality should be available in pure Python. Optional C (via Cython)
implementations may be written for performance reasons, but should never
replace the Python implementation.
Lastly, don't take yourself too seriously :)
+59 -8
View File
@@ -1,10 +1,61 @@
History
-------
0.9.7 (2011-05-12)
++++++++++++++++++
* Full XLSX Support!
* Pickling Bugfix
* Compat Module
0.9.6 (2011-05-12)
++++++++++++++++++
* ``seperators`` renamed to ``separators``
* Full unicode CSV support
0.9.5 (2011-03-24)
++++++++++++++++++
* Python 3.1, Python 3.2 Support (same code base!)
* Formatter callback support
* Various bug fixes
0.9.4 (2011-02-18)
++++++++++++++++++
* Python 2.5 Support!
* Tox Testing for 2.5, 2.6, 2.7
* AnyJSON Integrated
* OrderedDict support
* Caved to community pressure (spaces)
0.9.3 (2011-01-31)
++++++++++++++++++
* Databook duplication leak fix.
* HTML Table output.
* Added column sorting.
0.9.2 (2010-11-17)
++++++++++++++++++
* Transpose method added to Datasets.
* New frozen top row in Excel output.
* Pickling support for Datasets and Rows.
* Support for row/column stacking.
0.9.1 (2010-11-04)
++++++++++++++++++
* Minor reference shadowing bugfix
* Minor reference shadowing bugfix.
0.9.0 (2010-11-04)
@@ -12,7 +63,7 @@ History
* Massive documentation update!
* Tablib.org!
* Row taggins and Dataset filtering!
* Row tagging and Dataset filtering!
* Column insert/delete support
* Column append API change (header required)
* Internal Changes (Row object and use thereof)
@@ -22,19 +73,19 @@ History
++++++++++++++++++
* New import system. All dependencies attempt to load from site-packages,
then fallback on vendorized modules.
then fallback on tenderized modules.
0.8.4 (2010-10-04)
++++++++++++++++++
* Upated XLS output: Only wrap if '\\n' in cell.
* Updated XLS output: Only wrap if '\\n' in cell.
0.8.3 (2010-10-04)
++++++++++++++++++
* Ability to append new column passing a callable
* Ability to append new column passing a callable
as the value that will be applied to every row.
@@ -62,14 +113,14 @@ History
0.7.1 (2010-09-20)
++++++++++++++++++
* Reverting methods back to properties.
* Windows bug compenated in documentation.
* Reverting methods back to properties.
* Windows bug compensated in documentation.
0.7.0 (2010-09-20)
++++++++++++++++++
* Renamed DataBook Databook for consistiency.
* Renamed DataBook Databook for consistency.
* Export properties changed to methods (XLS filename / StringIO bug).
* Optional Dataset.xls(path='filename') support (for writing on windows).
* Added utf-8 on the worksheet level.
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2010 Kenneth Reitz.
Copyright (c) 2011 Kenneth Reitz.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+100 -27
View File
@@ -1,4 +1,37 @@
Tablib includes some vendorized python libraries: pyyaml, simplejson, and xlwt.
Tablib includes some vendorized python libraries: ordereddict, pyyaml,
simplejson, unicodecsv, and xlwt.
Markup License
==============
Markup is in the public domain.
OrderedDict License
===================
Copyright (c) 2009 Raymond Hettinger
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.
@@ -27,28 +60,68 @@ SOFTWARE.
SimpleJSON License
AnyJSON License
==================
Copyright (c) 2006 Bob Ippolito
This software is licensed under the ``New BSD License``:
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:
Copyright (c) 2009, by the authors
All rights reserved.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
Neither the name of the authors nor the names of its contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
UnicodeCSV License
==================
Copyright 2010 Jeremy Dunck. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY JEREMY DUNCK ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JEREMY DUNCK OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those of the
authors and should not be interpreted as representing official policies, either expressed
or implied, of Jeremy Dunck.
@@ -63,15 +136,15 @@ Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
and/or other materials provided with the distribution.
3. None of the names of Stephen John Machin, Lingfo Pty Ltd and any
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
@@ -89,29 +162,29 @@ THE POSSIBILITY OF SUCH DAMAGE.
"""
Copyright (C) 2005 Roman V. Kiseliov
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
3. All advertising materials mentioning features or use of this
software must display the following acknowledgment:
"This product includes software developed by
Roman V. Kiseliov <roman@kiseliov.ru>."
4. Redistributions of any form whatsoever must retain the following
acknowledgment:
"This product includes software developed by
Roman V. Kiseliov <roman@kiseliov.ru>."
THIS SOFTWARE IS PROVIDED BY Roman V. Kiseliov ``AS IS'' AND ANY
EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+34 -69
View File
@@ -3,55 +3,49 @@ Tablib: format-agnostic tabular dataset library
::
_____ ______ ___________ ______
__ /_______ ____ /_ ___ /___(_)___ /_
_____ ______ ___________ ______
__ /_______ ____ /_ ___ /___(_)___ /_
_ __/_ __ `/__ __ \__ / __ / __ __ \
/ /_ / /_/ / _ /_/ /_ / _ / _ /_/ /
\__/ \__,_/ /_.___/ /_/ /_/ /_.___/
Tablib is a format-agnostic tabular dataset library, written in Python.
Tablib is a format-agnostic tabular dataset library, written in Python.
Output formats supported:
- Excel (Sets + Books)
- JSON (Sets + Books)
- YAML (Sets + Books)
- HTML (Sets)
- TSV (Sets)
- CSV (Sets)
Import formats supported:
- JSON (Sets + Books)
- YAML (Sets + Books)
- TSV (Sets)
- CSV (Sets)
Note that tablib *purposefully* excludes XML support. It always will.
Note that tablib *purposefully* excludes XML support. It always will. (Note: This is a joke. Pull requests are welcome.)
Overview
--------
`tablib.Dataset()`
A Dataset is a table of tabular data. It may or may not have a header row. They can be build and maniuplated as raw Python datatypes (Lists of tuples|dictonaries). Datasets can be imported from JSON, YAML, and CSV; they can be exported to Excel (XLS), JSON, YAML, and CSV.
A Dataset is a table of tabular data. It may or may not have a header row. They can be build and manipulated as raw Python datatypes (Lists of tuples|dictionaries). Datasets can be imported from JSON, YAML, and CSV; they can be exported to Excel (XLS), JSON, YAML, and CSV.
`tablib.Databook()`
A Databook is a set of Datasets. The most common form of a Databook is an Excel file with multiple spreadsheets. Databooks can be imported from JSON and YAML; they can be exported to Excel (XLS), JSON, and YAML.
Usage
-----
Populate fresh data files: ::
headers = ('first_name', 'last_name')
data = [
('John', 'Adams'),
('George', 'Washington')
]
data = tablib.Dataset(*data, headers=headers)
@@ -61,13 +55,13 @@ Intelligently add new rows: ::
Intelligently add new columns: ::
>>> data.append(col=('age', 90, 67, 83))
>>> data.append(col=(90, 67, 83), header='age')
Slice rows: ::
>>> print data[:2]
[('John', 'Adams', 90), ('George', 'Washington', 67)]
Slice columns by header: ::
@@ -83,7 +77,7 @@ Exports
Drumroll please...........
JSON!
JSON!
+++++
::
@@ -100,26 +94,26 @@ JSON!
"first_name": "Henry"
}
]
YAML!
YAML!
+++++
::
>>> print data.yaml
- {age: 90, first_name: John, last_name: Adams}
- {age: 83, first_name: Henry, last_name: Ford}
CSV...
CSV...
++++++
::
>>> print data.csv
first_name,last_name,age
John,Adams,90
Henry,Ford,83
EXCEL!
first_name,last_name,age
John,Adams,90
Henry,Ford,83
EXCEL!
++++++
::
@@ -127,39 +121,6 @@ EXCEL!
It's that easy.
Imports!
--------
JSON
++++
::
>>> data.json = '[{"last_name": "Adams","age": 90,"first_name": "John"}]'
>>> print data[0]
('John', 'Adams', 90)
YAML
++++
::
>>> data.yaml = '- {age: 90, first_name: John, last_name: Adams}'
>>> print data[0]
('John', 'Adams', 90)
CSV
+++
::
>>> data.csv = 'age, first_name, last_name\n90, John, Adams'
>>> print data[0]
('John', 'Adams', 90)
>>> print data.yaml
- {age: 90, first_name: John, last_name: Adams}
Installation
------------
@@ -167,24 +128,28 @@ Installation
To install tablib, simply: ::
$ pip install tablib
Or, if you absolutely must: ::
$ easy_install tablib
Contribute
----------
If you'd like to contribute, simply fork `the repository`_, commit your changes to the **develop** branch (or branch off of it), and send a pull request. Make sure you add yourself to AUTHORS_.
If you'd like to contribute, simply fork `the repository`_, commit your
changes to the **develop** branch (or branch off of it), and send a pull
request. Make sure you add yourself to AUTHORS_.
Roadmap
-------
- Release CLI Interface
- Auto-detect import format
- Add possible other exports (SQL?)
- Ability to assign types to rows (set, regex=, &c.)
v1.0.0:
- Add hooks system
- Tablib.ext namespace
- Better 2.x/3.x handling (currently internal codebase fork)
- Width detection on XLS out
.. _`the repository`: http://github.com/kennethreitz/tablib
.. _AUTHORS: http://github.com/kennethreitz/tablib/blob/master/AUTHORS
+9 -9
View File
@@ -1,9 +1,9 @@
* Polish *&* announce http://tablib.org.
* Write more exhausive unit-tests.
* Write stress tests.
* Make CSV write customizable.
* HTML Table exports.
* ``Dataset.traspose()`` support?
* Hooks System
- pre/post-append
- pre/post-import
- pre/post-export
* Add Tablib.ext namespace
* Fix 2.x/3.x handling (currently internal codebase fork)
* Make CSV write more customizable.
* Width detection for XLS output
* Documentation Improvements
+1 -1
View File
@@ -1,6 +1,6 @@
Modifications:
Copyright (c) 2010 Kenneth Reitz.
Copyright (c) 2011 Kenneth Reitz.
Original Project:
+15
View File
@@ -13,4 +13,19 @@
&copy; Copyright {{ copyright }}.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
</div>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-8742933-9']);
_gaq.push(['_setDomainName', 'none']);
_gaq.push(['_setAllowLinker', true]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
{%- endblock %}
+43
View File
@@ -385,3 +385,46 @@ a.footnote-reference:hover {
a:hover tt {
background: #EEE;
}
@media screen and (max-width: 600px) {
div.sphinxsidebar {
display: none;
}
div.documentwrapper {
margin-left: 0;
margin-top: 0;
margin-right: 0;
margin-bottom: 0;
}
div.bodywrapper {
margin-top: 0;
margin-right: 0;
margin-bottom: 0;
margin-left: 0;
}
ul {
margin-left: 0;
}
.document {
width: auto;
}
.bodywrapper {
margin: 0;
}
.footer {
width: auto;
}
}
+17 -4
View File
@@ -12,13 +12,12 @@
# serve to show the default.
import sys, os
import tablib
# 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('..'))
import tablib
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
@@ -42,14 +41,14 @@ master_doc = 'index'
# General information about the project.
project = u'Tablib'
copyright = u'2010, Kenneth Reitz. Styles (modified) &copy; Armin Ronacher'
copyright = u'2011, Kenneth Reitz. Styles (modified) &copy; Armin Ronacher'
# 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 = tablib.core.__version__
version = '0.9.7'
# The full version, including alpha/beta/rc tags.
release = version
@@ -183,6 +182,20 @@ latex_documents = [
u'Kenneth Reitz', 'manual'),
]
latex_use_modindex = False
latex_elements = {
'fontpkg': r'\usepackage{mathpazo}',
'papersize': 'a4paper',
'pointsize': '12pt',
'preamble': r'\usepackage{krstyle}'
}
latex_use_parts = True
latex_additional_files = ['krstyle.sty']
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
+6 -6
View File
@@ -87,7 +87,7 @@ Adding New Formats
Tablib welcomes new format additions! Format suggestions include:
* Tab Seperated Values
* Tab Separated Values
* MySQL Dump
* HTML Table
@@ -128,7 +128,7 @@ Tablib features a micro-framework for adding format support. The easiest way to
.. admonition:: Excluding Support
If the format excludes support for an import/export mechanism (*eg.* :class:`csv <tablib.Dataset.csv>` excludes :class:`Databook <tablib.Databook>` support), simply don't define the respecive functions. Appropriate errors will be raised.
If the format excludes support for an import/export mechanism (*eg.* :class:`csv <tablib.Dataset.csv>` excludes :class:`Databook <tablib.Databook>` support), simply don't define the respective functions. Appropriate errors will be raised.
2.
@@ -145,7 +145,7 @@ Tablib features a micro-framework for adding format support. The easiest way to
Testing Tablib
--------------
Testing is crucial to Tablib's stability. This stable project is used in production by many companies and developers, so it is important to be certian that every version released is fully operational. When developing a new feature for Tablib, be sure to write proper tests for it as well.
Testing is crucial to Tablib's stability. This stable project is used in production by many companies and developers, so it is important to be certain that every version released is fully operational. When developing a new feature for Tablib, be sure to write proper tests for it as well.
When developing a feature for Tablib, the easiest way to test your changes for potential issues is to simply run the test suite directly. ::
@@ -174,11 +174,11 @@ This will generate a **nosetests.xml** file, which can then be analyzed.
Continuous Integration
----------------------
Every commit made to the **develop** branch is automatically tested and inspected upon receipt with `Hudson CI`_. If you have access to the main respository and broke the build, you will receive an email accordingly.
Every commit made to the **develop** branch is automatically tested and inspected upon receipt with `Hudson CI`_. If you have access to the main repository and broke the build, you will receive an email accordingly.
Anyone may view the build status and history at any time.
http://git.kennethreitz.com/ci/
http://ci.kennethreitz.com/
If you are trustworthy and plan to contribute to tablib on a regular basis, please contact `Kenneth Reitz`_ to get an account on the Hudson Server.
@@ -197,7 +197,7 @@ Building the Docs
-----------------
Documentation is written in the powerful, flexible, and standard Python documentation format, `reStructured Text`_.
Documentation builds are powered by the powerful Pocoo project, Sphinx_. The :ref:`API Documentation <api>` is mostly documented inline throught the module.
Documentation builds are powered by the powerful Pocoo project, Sphinx_. The :ref:`API Documentation <api>` is mostly documented inline throughout the module.
The Docs live in ``tablib/docs``. In order to build them, you will first need to install Sphinx. ::
+5 -3
View File
@@ -3,8 +3,10 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Tablib: Pythonic Tabular Data
=============================
Tablib: Pythonic Tabular Datasets
=================================
Release |version|.
.. Contents:
..
@@ -20,7 +22,7 @@ Tablib: Pythonic Tabular Data
.. * :ref:`search`
Tablib is an :ref:`MIT Lisenced <mit>` format-agnostic tabular dataset library, written in Python. It allows you to import, export, and manipulate tabular data sets. Advanced features include, segregation, dynamic columns, tags & filtering, and seamless format import & exmport.
Tablib is an :ref:`MIT Licensed <mit>` format-agnostic tabular dataset library, written in Python. It allows you to import, export, and manipulate tabular data sets. Advanced features include, segregation, dynamic columns, tags & filtering, and seamless format import & export.
I recommend you start with :ref:`Installation <install>`.
+2 -2
View File
@@ -2,7 +2,7 @@
Installation
============
This part of the documentation covers the installation of Tablib. The first step to using any software package is getting it properly installed. Please read this section carefully, or you may miss out on some nice :ref:`speed enhancments <peed-extentions>`.
This part of the documentation covers the installation of Tablib. The first step to using any software package is getting it properly installed. Please read this section carefully, or you may miss out on some nice :ref:`speed enhancements <peed-extentions>`.
.. _installing:
@@ -55,7 +55,7 @@ However, if performance is important to you (and it should be), you can install
$ pip install PyYAML
If you're using Python 2.5 (currently unsupported), you should also install the **simplejson** module. If you're using Python 2.6+, the built-in **json** module is already optimized and in use. ::
If you're using Python 2.5, you should also install the **simplejson** module (pip will do this for you). If you're using Python 2.6+, the built-in **json** module is already optimized and in use. ::
$ pip install simplejson
+34 -5
View File
@@ -4,7 +4,7 @@ Introduction
============
This part of the documentation covers all the interfaces of Tablib.
Tablib is a format-agnostic tabular dataset library, written in Python. It allows you to Pythonically import, export, and manipulate tabular data sets. Advanced features include, segregation, dynamic columns, tags / filtering, and seamless format import/exmport.
Tablib is a format-agnostic tabular dataset library, written in Python. It allows you to Pythonically import, export, and manipulate tabular data sets. Advanced features include, segregation, dynamic columns, tags / filtering, and seamless format import/export.
Philosphy
@@ -26,9 +26,9 @@ All contributions to Tablib should keep these important rules in mind.
MIT License
-----------
A large number of open source projects you find today are `GPL Licensed`_. While the GPL certianly has essential applications, it should most certianly not be your go-to license for your next open source project.
A large number of open source projects you find today are `GPL Licensed`_. While the GPL has its time and place, it should most certainly not be your go-to license for your next open source project.
A project that is released as GPL cannot be usd in any commercial product without the product itself also being offered as open source. The MIT and BSD licenses are great alternatives to the GPL that allow your open-source software to be used in proprietary, closed-source software.
A project that is released as GPL cannot be used in any commercial product without the product itself also being offered as open source. The MIT and BSD licenses are great alternatives to the GPL that allow your open-source software to be used in proprietary, closed-source software.
Tablib is released under terms of `The MIT License`_.
@@ -36,6 +36,31 @@ Tablib is released under terms of `The MIT License`_.
.. _`The MIT License`: http://www.opensource.org/licenses/mit-license.php
.. _license:
Tablib License
--------------
Copyright (c) 2011 Kenneth Reitz.
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.
.. _pythonsupport:
@@ -44,8 +69,12 @@ Pythons Supported
At this time, the following Python platforms are officially supported:
* Python 2.6
* Python 2.7
* cPython 2.5
* cPython 2.6
* cPython 2.7
* cPython 3.1
* cPython 3.2
* PyPy-c 1.4
Support for other Pythons will be rolled out soon.
+118
View File
@@ -0,0 +1,118 @@
\definecolor{TitleColor}{rgb}{0,0,0}
\definecolor{InnerLinkColor}{rgb}{0,0,0}
\renewcommand{\maketitle}{%
\begin{titlepage}%
\let\footnotesize\small
\let\footnoterule\relax
\ifsphinxpdfoutput
\begingroup
% This \def is required to deal with multi-line authors; it
% changes \\ to ', ' (comma-space), making it pass muster for
% generating document info in the PDF file.
\def\\{, }
\pdfinfo{
/Author (\@author)
/Title (\@title)
}
\endgroup
\fi
\begin{flushright}%
%\sphinxlogo%
{\center
\vspace*{3cm}
\includegraphics{logo.pdf}
\vspace{3cm}
\par
{\rm\Huge \@title \par}%
{\em\LARGE \py@release\releaseinfo \par}
{\large
\@date \par
\py@authoraddress \par
}}%
\end{flushright}%\par
\@thanks
\end{titlepage}%
\cleardoublepage%
\setcounter{footnote}{0}%
\let\thanks\relax\let\maketitle\relax
%\gdef\@thanks{}\gdef\@author{}\gdef\@title{}
}
\fancypagestyle{normal}{
\fancyhf{}
\fancyfoot[LE,RO]{{\thepage}}
\fancyfoot[LO]{{\nouppercase{\rightmark}}}
\fancyfoot[RE]{{\nouppercase{\leftmark}}}
\fancyhead[LE,RO]{{ \@title, \py@release}}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\footrulewidth}{0.4pt}
}
\fancypagestyle{plain}{
\fancyhf{}
\fancyfoot[LE,RO]{{\thepage}}
\renewcommand{\headrulewidth}{0pt}
\renewcommand{\footrulewidth}{0.4pt}
}
\titleformat{\section}{\Large}%
{\py@TitleColor\thesection}{0.5em}{\py@TitleColor}{\py@NormalColor}
\titleformat{\subsection}{\large}%
{\py@TitleColor\thesubsection}{0.5em}{\py@TitleColor}{\py@NormalColor}
\titleformat{\subsubsection}{}%
{\py@TitleColor\thesubsubsection}{0.5em}{\py@TitleColor}{\py@NormalColor}
\titleformat{\paragraph}{\large}%
{\py@TitleColor}{0em}{\py@TitleColor}{\py@NormalColor}
\ChNameVar{\raggedleft\normalsize}
\ChNumVar{\raggedleft \bfseries\Large}
\ChTitleVar{\raggedleft \rm\Huge}
\renewcommand\thepart{\@Roman\c@part}
\renewcommand\part{%
\pagestyle{empty}
\if@noskipsec \leavevmode \fi
\cleardoublepage
\vspace*{6cm}%
\@afterindentfalse
\secdef\@part\@spart}
\def\@part[#1]#2{%
\ifnum \c@secnumdepth >\m@ne
\refstepcounter{part}%
\addcontentsline{toc}{part}{\thepart\hspace{1em}#1}%
\else
\addcontentsline{toc}{part}{#1}%
\fi
{\parindent \z@ %\center
\interlinepenalty \@M
\normalfont
\ifnum \c@secnumdepth >\m@ne
\rm\Large \partname~\thepart
\par\nobreak
\fi
\MakeUppercase{\rm\Huge #2}%
\markboth{}{}\par}%
\nobreak
\vskip 8ex
\@afterheading}
\def\@spart#1{%
{\parindent \z@ %\center
\interlinepenalty \@M
\normalfont
\huge \bfseries #1\par}%
\nobreak
\vskip 3ex
\@afterheading}
% use inconsolata font
\usepackage{inconsolata}
% fix single quotes, for inconsolata. (does not work)
%%\usepackage{textcomp}
%%\begingroup
%% \catcode`'=\active
%% \g@addto@macro\@noligs{\let'\textsinglequote}
%% \endgroup
%%\endinput
+33 -33
View File
@@ -30,11 +30,11 @@ A :class:`Dataset <tablib.Dataset>` is nothing more than what its name implies
Creating your own instance of the :class:`tablib.Dataset` object is simple. ::
data = tablib.Dataset()
You can now start filling this :class:`Dataset <tablib.Dataset>` object with data.
.. admonition:: Example Context
From here on out, if you see ``data``, assume that it's a fresh :class:`Dataset <tablib.Dataset>` object.
@@ -52,7 +52,7 @@ Let's say you want to collect a simple list of names. ::
for name in names:
# split name appropriately
fname, lname = name.split()
# add names to Dataset
data.append([fname, lname])
@@ -76,19 +76,19 @@ Now our data looks a little different. ::
>>> data.dict
[{'Last Name': 'Reitz', 'First Name': 'Kenneth'}, {'Last Name': 'Monke', 'First Name': 'Bessie'}]
--------------
Adding Columns
Adding Columns
--------------
Now that we have a basic :class:`Dataset` in place, let's add a column of **ages** to it. ::
data.append(col=['Age', 22, 20])
data.append(col=[22, 20], header='Age')
Let's view the data now. ::
>>> data.dict
@@ -103,11 +103,11 @@ Exporting Data
Tablib's killer feature is the ability to export your :class:`Dataset` objects into a number of formats.
**Comma-Seperated Values** ::
**Comma-Separated Values** ::
>>> data.csv
Last Name,First Name,Age
Reitz,Kenneth,22
Last Name,First Name,Age
Reitz,Kenneth,22
Monke,Bessie,20
**JavaScript Object Notation** ::
@@ -121,7 +121,7 @@ Tablib's killer feature is the ability to export your :class:`Dataset` objects i
>>> data.yaml
- {Age: 22, First Name: Kenneth, Last Name: Reitz}
- {Age: 20, First Name: Bessie, Last Name: Monke}
**Microsoft Excel** ::
@@ -190,11 +190,11 @@ Thanks to Josh Ourisman, Tablib now supports adding dynamic columns. A dynamic c
Let's add a dynamic column to our :class:`Dataset` object. In this example, we have a function that generates a random grade for our students. ::
import random
def random_grade(row):
"""Returns a random integer for entry."""
return (random.randint(60,100)/100.0)
data.append(col=[random_grade], header='Grade')
Let's have a look at our data. ::
@@ -209,7 +209,7 @@ Let's remove that column. ::
>>> del data['Grade']
When you add a dynamic column, the first argument that is passed in to the given callable is the current data row. You can use this to perform calculations against your data row.
When you add a dynamic column, the first argument that is passed in to the given callable is the current data row. You can use this to perform calculations against your data row.
For example, we can use the data available in the row to guess the gender of a student. ::
@@ -217,9 +217,9 @@ For example, we can use the data available in the row to guess the gender of a s
"""Calculates gender of given student data row."""
m_names = ('Kenneth', 'Mike', 'Yuri')
f_names = ('Bessie', 'Samantha', 'Heather')
name = row[0]
if name in m_names:
return 'Male'
elif name in f_names:
@@ -243,8 +243,8 @@ Filtering Datasets with Tags
.. versionadded:: 0.9.0
When constructing a :class:`Dataset` object, you can add tags to rows by speficying the ``tags`` parameter.
This allows you to filter your :class:`Dataset` later. This can be useful so seperate rows of data based on
When constructing a :class:`Dataset` object, you can add tags to rows by specifying the ``tags`` parameter.
This allows you to filter your :class:`Dataset` later. This can be useful so separate rows of data based on
arbitrary criteria (*e.g.* origin) that you don't want to include in your :class:`Dataset`.
Let's tag some students. ::
@@ -266,10 +266,10 @@ It's that simple. The original :class:`Dataset` is untouched.
Excel Workbook With Multiple Sheets
------------------------------------
------------------------------------
When dealing with a large number of :class:`Datasets <Dataset>` in spreadsheet format, it's quite common to group multiple spreadsheets into a single Excel file, known as a Workbook. Tablib makes it extremely easy to build workbooks with the handy, :class:`Databook` class.
When dealine with a large number of :class:`Datasets <Dataset>` in spreadsheet format, it's quite common to group mulitple spreadsheets into a single Excel file, known as a Workbook. Tablib makes it extremely easy to build webooks with the handy, :class:`Databook` class.
Let's say we have 3 different :class:`Datasets <Dataset>`. All we have to do is add then to a :class:`Databook` object... ::
@@ -280,22 +280,22 @@ Let's say we have 3 different :class:`Datasets <Dataset>`. All we have to do is
with open('students.xls', 'wb') as f:
f.write(book.xls)
The resulting **students.xls** file will contain a seperate spreadsheet for each :class:`Dataset` object in the :class:`Databook`.
The resulting **students.xls** file will contain a separate spreadsheet for each :class:`Dataset` object in the :class:`Databook`.
.. admonition:: Binary Warning
Make sure to open the output file in binary mode.
.. _seperators:
.. _separators:
----------
Seperators
Separators
----------
.. versionadded:: 0.8.2
When, it's often useful to create a blank row containing information on the upcomming data. So,
When, it's often useful to create a blank row containing information on the upcoming data. So,
@@ -305,24 +305,24 @@ When, it's often useful to create a blank row containing information on the upco
('11/24/09', 'Math 101 Mid-term Exam', 56.),
('05/24/10', 'Math 101 Final Exam', 62.)
]
suzie_tests = [
('11/24/09', 'Math 101 Mid-term Exam', 56.),
('05/24/10', 'Math 101 Final Exam', 62.)
]
# Create new dataset
tests = tablib.Dataset()
tests.headers = ['Date', 'Test Name', 'Grade']
# Daniel's Tests
tests.append_seperator('Daniel\'s Scores')
tests.append_separator('Daniel\'s Scores')
for test_row in daniel_tests:
tests.append(test_row)
# Susie's Tests
tests.append_seperator('Susie\'s Scores')
tests.append_separator('Susie\'s Scores')
for test_row in suzie_tests:
tests.append(test_row)
@@ -331,7 +331,7 @@ When, it's often useful to create a blank row containing information on the upco
with open('grades.xls', 'wb') as f:
f.write(tests.xls)
The resulting **tests.xls** will have the following layout:
The resulting **tests.xls** will have the following layout:
Daniel's Scores:
@@ -346,8 +346,8 @@ The resulting **tests.xls** will have the following layout:
.. admonition:: Format Support
At this time, only :class:`Excel <Dataset.xls>` output supports seperators.
At this time, only :class:`Excel <Dataset.xls>` output supports separators.
----
Now, go check out the :ref:`API Documentation <api>` or begin :ref:`Tablib Development <development>`.
Now, go check out the :ref:`API Documentation <api>` or begin :ref:`Tablib Development <development>`.
Vendored
-17
View File
@@ -1,17 +0,0 @@
import os
from fabric.api import *
def scrub():
""" Death to the bytecode! """
local('rm -fr dist build')
local("find . -name \"*.pyc\" -exec rm '{}' ';'")
def docs():
"""Build docs."""
os.system('make html')
os.chdir('_build/html')
os.system('sphinxtogithub .')
os.system('git add -A')
os.system('git commit -m \'documentation update\'')
os.system('git push origin gh-pages')
-3
View File
@@ -1,3 +0,0 @@
xlwt
simplejson
PyYAML
+37 -32
View File
@@ -8,43 +8,48 @@ from distutils.core import setup
def publish():
"""Publish to PyPi"""
os.system("python setup.py sdist upload")
"""Publish to PyPi"""
os.system("python setup.py sdist upload")
if sys.argv[-1] == "publish":
publish()
sys.exit()
publish()
sys.exit()
required = []
if sys.version_info[:2] < (2,6):
required.append('simplejson')
setup(
name='tablib',
version='0.9.1',
description='Format agnostic tabular data library (XLS, JSON, YAML, CSV)',
long_description=open('README.rst').read() + '\n\n' +
open('HISTORY.rst').read(),
author='Kenneth Reitz',
author_email='me@kennethreitz.com',
url='http://tablib.org',
packages= [
'tablib', 'tablib.formats',
'tablib.packages',
'tablib.packages.simplejson',
'tablib.packages.xlwt',
'tablib.packages.yaml',
],
install_requires=required,
license='MIT',
classifiers=(
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Natural Language :: English',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
# 'Programming Language :: Python :: 2.5',
name='tablib',
version='0.9.7',
description='Format agnostic tabular data library (XLS, JSON, YAML, CSV)',
long_description=open('README.rst').read() + '\n\n' +
open('HISTORY.rst').read(),
author='Kenneth Reitz',
author_email='me@kennethreitz.com',
url='http://tablib.org',
packages= [
'tablib', 'tablib.formats',
'tablib.packages',
'tablib.packages.xlwt',
'tablib.packages.openpyxl',
'tablib.packages.yaml',
'tablib.packages.unicodecsv'
],
install_requires=required,
license='MIT',
classifiers=(
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Natural Language :: English',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 2.5',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
# 'Programming Language :: Python :: 3.0',
# 'Programming Language :: Python :: 3.1',
),
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.0',
'Programming Language :: Python :: 3.1',
'Programming Language :: Python :: 3.2',
),
)
+2 -2
View File
@@ -2,7 +2,7 @@
"""
from tablib.core import (
Databook, Dataset, detect, import_set,
InvalidDatasetType, InvalidDimensions, UnsupportedFormat
Databook, Dataset, detect, import_set,
InvalidDatasetType, InvalidDimensions, UnsupportedFormat
)
+49
View File
@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
"""
tablib.compat
~~~~~~~~~~~~~
Tablib compatiblity module.
"""
import sys
is_py3 = (sys.version_info[0] > 2)
try:
from collections import OrderedDict
except ImportError:
from tablib.packages.ordereddict import OrderedDict
if is_py3:
from io import BytesIO
import tablib.packages.xlwt3 as xlwt
from tablib.packages import markup3 as markup
from tablib.packages import openpyxl3 as openpyxl
# py3 mappings
ifilter = filter
xrange = range
unicode = str
bytes = bytes
basestring = str
else:
from cStringIO import StringIO as BytesIO
import tablib.packages.xlwt as xlwt
from tablib.packages import markup
from itertools import ifilter
from tablib.packages import openpyxl
# py2 mappings
xrange = xrange
unicode = unicode
bytes = str
basestring = basestring
+671 -459
View File
File diff suppressed because it is too large Load Diff
+8 -6
View File
@@ -3,10 +3,12 @@
""" Tablib - formats
"""
import _csv as csv
import _json as json
import _xls as xls
import _yaml as yaml
import _tsv as tsv
from . import _csv as csv
from . import _json as json
from . import _xls as xls
from . import _yaml as yaml
from . import _tsv as tsv
from . import _html as html
from . import _xlsx as xlsx
available = (json, xls, yaml, csv, tsv)
available = (json, xls, yaml, csv, tsv, html, xlsx)
+43 -22
View File
@@ -3,8 +3,19 @@
""" Tablib - CSV Support.
"""
import cStringIO
import csv
import sys
if sys.version_info[0] > 2:
is_py3 = True
from io import StringIO
import csv
else:
is_py3 = False
from cStringIO import StringIO
import tablib.packages.unicodecsv as csv
import os
import tablib
@@ -14,36 +25,46 @@ title = 'csv'
extentions = ('csv',)
DEFAULT_ENCODING = 'utf-8'
def export_set(dataset):
"""Returns CSV representation of Dataset."""
stream = cStringIO.StringIO()
_csv = csv.writer(stream)
"""Returns CSV representation of Dataset."""
stream = StringIO()
for row in dataset._package(dicts=False):
_csv.writerow(row)
if is_py3:
_csv = csv.writer(stream)
else:
_csv = csv.writer(stream, encoding=DEFAULT_ENCODING)
return stream.getvalue()
for row in dataset._package(dicts=False):
_csv.writerow(row)
return stream.getvalue()
def import_set(dset, in_stream, headers=True):
"""Returns dataset from CSV stream."""
"""Returns dataset from CSV stream."""
dset.wipe()
dset.wipe()
rows = csv.reader(in_stream.split())
for i, row in enumerate(rows):
if is_py3:
rows = csv.reader(in_stream.splitlines())
else:
rows = csv.reader(in_stream.splitlines(), encoding=DEFAULT_ENCODING)
for i, row in enumerate(rows):
if (i == 0) and (headers):
dset.headers = row
else:
dset.append(row)
if (i == 0) and (headers):
dset.headers = row
else:
dset.append(row)
def detect(stream):
"""Returns True if given stream is valid CSV."""
try:
rows = dialect = csv.Sniffer().sniff(stream)
return True
except csv.Error:
return False
"""Returns True if given stream is valid CSV."""
try:
rows = dialect = csv.Sniffer().sniff(stream)
return True
except csv.Error:
return False
+60
View File
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
""" Tablib - HTML export support.
"""
import sys
if sys.version_info[0] > 2:
from io import StringIO
from tablib.packages import markup3 as markup
else:
from cStringIO import StringIO
from tablib.packages import markup
import tablib
BOOK_ENDINGS = 'h3'
title = 'html'
extentions = ('html', )
def export_set(dataset):
"""HTML representation of a Dataset."""
stream = StringIO()
page = markup.page()
page.table.open()
if dataset.headers is not None:
page.thead.open()
headers = markup.oneliner.th(dataset.headers)
page.tr(headers)
page.thead.close()
for row in dataset:
html_row = markup.oneliner.td(row)
page.tr(html_row)
page.table.close()
stream.writelines(str(page))
return stream.getvalue()
def export_book(databook):
"""HTML representation of a Databook."""
stream = StringIO()
for i, dset in enumerate(databook._datasets):
title = (dset.title if dset.title else 'Set %s' % (i))
stream.write('<%s>%s</%s>\n' % (BOOK_ENDINGS, title, BOOK_ENDINGS))
stream.write(dset.html)
stream.write('\n')
return stream.getvalue()
+29 -30
View File
@@ -3,15 +3,14 @@
""" Tablib - JSON Support
"""
try:
import json # load system JSON (Python >= 2.6)
except ImportError:
try:
import simplejson as json
except ImportError:
import tablib.packages.simplejson as json # use the vendorized copy
import tablib
import sys
if sys.version_info[:2] > (2, 5):
from tablib.packages import anyjson
else:
from tablib.packages import anyjson25 as anyjson
import tablib.core
title = 'json'
@@ -19,37 +18,37 @@ extentions = ('json', 'jsn')
def export_set(dataset):
"""Returns JSON representation of Dataset."""
return json.dumps(dataset.dict)
"""Returns JSON representation of Dataset."""
return anyjson.serialize(dataset.dict)
def export_book(databook):
"""Returns JSON representation of Databook."""
return json.dumps(databook._package())
"""Returns JSON representation of Databook."""
return anyjson.serialize(databook._package())
def import_set(dset, in_stream):
"""Returns dataset from JSON stream."""
dset.wipe()
dset.dict = json.loads(in_stream)
"""Returns dataset from JSON stream."""
dset.wipe()
dset.dict = anyjson.deserialize(in_stream)
def import_book(dbook, in_stream):
"""Returns databook from JSON stream."""
"""Returns databook from JSON stream."""
dbook.wipe()
for sheet in json.loads(in_stream):
data = tablib.core.Dataset()
data.title = sheet['title']
data.dict = sheet['data']
dbook.add_sheet(data)
dbook.wipe()
for sheet in anyjson.deserialize(in_stream):
data = tablib.Dataset()
data.title = sheet['title']
data.dict = sheet['data']
dbook.add_sheet(data)
def detect(stream):
"""Returns True if given stream is valid JSON."""
try:
json.loads(stream)
return True
except ValueError:
return False
"""Returns True if given stream is valid JSON."""
try:
anyjson.deserialize(stream)
return True
except ValueError:
return False
+29 -24
View File
@@ -3,7 +3,12 @@
""" Tablib - TSV (Tab Separated Values) Support.
"""
import cStringIO
import sys
if sys.version_info[0] > 2:
from io import StringIO
else:
from cStringIO import StringIO
import csv
import os
@@ -16,36 +21,36 @@ extentions = ('tsv',)
def export_set(dataset):
"""Returns a TSV representation of Dataset."""
stream = cStringIO.StringIO()
_tsv = csv.writer(stream, delimiter='\t')
"""Returns a TSV representation of Dataset."""
stream = StringIO()
_tsv = csv.writer(stream, delimiter='\t')
for row in dataset._package(dicts=False):
_tsv.writerow(row)
for row in dataset._package(dicts=False):
_tsv.writerow(row)
return stream.getvalue()
return stream.getvalue()
def import_set(dset, in_stream, headers=True):
"""Returns dataset from TSV stream."""
dset.wipe()
"""Returns dataset from TSV stream."""
dset.wipe()
rows = csv.reader(in_stream.split('\r\n'), delimiter='\t')
for i, row in enumerate(rows):
# Skip empty rows
if not row:
continue
rows = csv.reader(in_stream.split('\r\n'), delimiter='\t')
for i, row in enumerate(rows):
# Skip empty rows
if not row:
continue
if (i == 0) and (headers):
dset.headers = row
else:
dset.append(row)
if (i == 0) and (headers):
dset.headers = row
else:
dset.append(row)
def detect(stream):
"""Returns True if given stream is valid TSV."""
try:
rows = dialect = csv.Sniffer().sniff(stream, delimiters='\t')
return True
except csv.Error:
return False
"""Returns True if given stream is valid TSV."""
try:
rows = dialect = csv.Sniffer().sniff(stream, delimiters='\t')
return True
except csv.Error:
return False
+46 -44
View File
@@ -3,12 +3,9 @@
""" Tablib - XLS Support.
"""
import cStringIO
import sys
try:
import xlwt
except ImportError:
import tablib.packages.xlwt as xlwt
from tablib.compat import BytesIO, xlwt
title = 'xls'
@@ -20,61 +17,66 @@ bold = xlwt.easyxf("font: bold on")
def export_set(dataset):
"""Returns XLS representation of Dataset."""
"""Returns XLS representation of Dataset."""
wb = xlwt.Workbook(encoding='utf8')
ws = wb.add_sheet(dataset.title if dataset.title else 'Tabbed Dataset')
wb = xlwt.Workbook(encoding='utf8')
ws = wb.add_sheet(dataset.title if dataset.title else 'Tablib Dataset')
dset_sheet(dataset, ws)
stream = cStringIO.StringIO()
wb.save(stream)
return stream.getvalue()
dset_sheet(dataset, ws)
stream = BytesIO()
wb.save(stream)
return stream.getvalue()
def export_book(databook):
"""Returns XLS representation of DataBook."""
"""Returns XLS representation of DataBook."""
wb = xlwt.Workbook(encoding='utf8')
wb = xlwt.Workbook(encoding='utf8')
for i, dset in enumerate(databook._datasets):
ws = wb.add_sheet(dset.title if dset.title else 'Sheet%s' % (i))
for i, dset in enumerate(databook._datasets):
ws = wb.add_sheet(dset.title if dset.title else 'Sheet%s' % (i))
dset_sheet(dset, ws)
dset_sheet(dset, ws)
stream = cStringIO.StringIO()
wb.save(stream)
return stream.getvalue()
stream = BytesIO()
wb.save(stream)
return stream.getvalue()
def dset_sheet(dataset, ws):
"""Completes given worksheet from given Dataset."""
_package = dataset._package(dicts=False)
"""Completes given worksheet from given Dataset."""
_package = dataset._package(dicts=False)
for i, sep in enumerate(dataset._separators):
_offset = i
_package.insert((sep[0] + _offset), (sep[1],))
for i, sep in enumerate(dataset._separators):
_offset = i
_package.insert((sep[0] + _offset), (sep[1],))
for i, row in enumerate(_package):
for j, col in enumerate(row):
for i, row in enumerate(_package):
for j, col in enumerate(row):
# bold headers
if (i == 0) and dataset.headers:
ws.write(i, j, col, bold)
# bold headers
if (i == 0) and dataset.headers:
ws.write(i, j, col, bold)
# bold separators
elif len(row) < dataset.width:
ws.write(i, j, col, bold)
# frozen header row
ws.panes_frozen = True
ws.horz_split_pos = 1
# bold separators
elif len(row) < dataset.width:
ws.write(i, j, col, bold)
# wrap the rest
else:
try:
if '\n' in col:
ws.write(i, j, col, wrap)
else:
ws.write(i, j, col)
except TypeError:
ws.write(i, j, col)
# wrap the rest
else:
try:
if '\n' in col:
ws.write(i, j, col, wrap)
else:
ws.write(i, j, col)
except TypeError:
ws.write(i, j, col)
+101
View File
@@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
""" Tablib - XLSX Support.
"""
import sys
if sys.version_info[0] > 2:
from io import BytesIO
else:
from cStringIO import StringIO as BytesIO
from tablib.compat import openpyxl
Workbook = openpyxl.workbook.Workbook
ExcelWriter = openpyxl.writer.excel.ExcelWriter
get_column_letter = openpyxl.cell.get_column_letter
from tablib.compat import unicode
title = 'xlsx'
extentions = ('xlsx',)
def export_set(dataset):
"""Returns XLSX representation of Dataset."""
wb = Workbook()
ws = wb.worksheets[0]
ws.title = dataset.title if dataset.title else 'Tablib Dataset'
dset_sheet(dataset, ws)
stream = BytesIO()
wb.save(stream)
return stream.getvalue()
def export_book(databook):
"""Returns XLSX representation of DataBook."""
wb = Workbook()
ew = ExcelWriter(workbook = wb)
for i, dset in enumerate(databook._datasets):
ws = wb.create_sheet()
ws.title = dset.title if dset.title else 'Sheet%s' % (i)
dset_sheet(dset, ws)
stream = BytesIO()
ew.save(stream)
return stream.getvalue()
def dset_sheet(dataset, ws):
"""Completes given worksheet from given Dataset."""
_package = dataset._package(dicts=False)
for i, sep in enumerate(dataset._separators):
_offset = i
_package.insert((sep[0] + _offset), (sep[1],))
for i, row in enumerate(_package):
row_number = i + 1
for j, col in enumerate(row):
col_idx = get_column_letter(j + 1)
# bold headers
if (row_number == 1) and dataset.headers:
# ws.cell('%s%s'%(col_idx, row_number)).value = unicode(
# '%s' % col, errors='ignore')
ws.cell('%s%s'%(col_idx, row_number)).value = unicode(col)
style = ws.get_style('%s%s' % (col_idx, row_number))
style.font.bold = True
ws.freeze_panes = '%s%s' % (col_idx, row_number)
# bold separators
elif len(row) < dataset.width:
ws.cell('%s%s'%(col_idx, row_number)).value = unicode(
'%s' % col, errors='ignore')
style = ws.get_style('%s%s' % (col_idx, row_number))
style.font.bold = True
# wrap the rest
else:
try:
if '\n' in col:
ws.cell('%s%s'%(col_idx, row_number)).value = unicode(
'%s' % col, errors='ignore')
style = ws.get_style('%s%s' % (col_idx, row_number))
style.alignment.wrap_text
else:
ws.cell('%s%s'%(col_idx, row_number)).value = unicode(
'%s' % col, errors='ignore')
except TypeError:
ws.cell('%s%s'%(col_idx, row_number)).value = unicode(col)
+32 -26
View File
@@ -3,10 +3,16 @@
""" Tablib - YAML Support.
"""
import sys
try:
import yaml
import yaml
except ImportError:
import tablib.packages.yaml as yaml
if sys.version_info[0] > 2:
import tablib.packages.yaml3 as yaml
else:
import tablib.packages.yaml as yaml
import tablib
@@ -18,40 +24,40 @@ extentions = ('yaml', 'yml')
def export_set(dataset):
"""Returns YAML representation of Dataset."""
return yaml.dump(dataset.dict)
"""Returns YAML representation of Dataset."""
return yaml.dump(dataset.dict)
def export_book(databook):
"""Returns YAML representation of Databook."""
return yaml.dump(databook._package())
"""Returns YAML representation of Databook."""
return yaml.dump(databook._package())
def import_set(dset, in_stream):
"""Returns dataset from YAML stream."""
"""Returns dataset from YAML stream."""
dset.wipe()
dset.dict = yaml.load(in_stream)
dset.wipe()
dset.dict = yaml.load(in_stream)
def import_book(dbook, in_stream):
"""Returns databook from YAML stream."""
"""Returns databook from YAML stream."""
dbook.wipe()
dbook.wipe()
for sheet in yaml.load(in_stream):
data = tablib.core.Dataset()
data.title = sheet['title']
data.dict = sheet['data']
dbook.add_sheet(data)
for sheet in yaml.load(in_stream):
data = tablib.Dataset()
data.title = sheet['title']
data.dict = sheet['data']
dbook.add_sheet(data)
def detect(stream):
"""Returns True if given stream is valid YAML."""
try:
_yaml = yaml.load(stream)
if isinstance(_yaml, (list, tuple, dict)):
return True
else:
return False
except yaml.parser.ParserError:
return False
"""Returns True if given stream is valid YAML."""
try:
_yaml = yaml.load(stream)
if isinstance(_yaml, (list, tuple, dict)):
return True
else:
return False
except yaml.parser.ParserError:
return False
-37
View File
@@ -1,37 +0,0 @@
# -*- coding: utf-8 -*-
""" Tablib - General Helpers.
"""
import sys
class Struct(object):
"""Your attributes are belong to us."""
def __init__(self, **entries):
self.__dict__.update(entries)
def __getitem__(self, key):
return getattr(self, key, None)
def dictionary(self):
"""Returns dictionary representation of object."""
return self.__dict__
def items(self):
"""Returns items within object."""
return self.__dict__.items()
def keys(self):
"""Returns keys within object."""
return self.__dict__.keys()
def piped():
"""Returns piped input via stdin, else False."""
with sys.stdin as stdin:
# TTY is only way to detect if stdin contains data
return stdin.read() if not stdin.isatty() else None
+117
View File
@@ -0,0 +1,117 @@
"""
Wraps the best available JSON implementation available in a common interface
"""
__version__ = "0.2.0"
__author__ = "Rune Halvorsen <runefh@gmail.com>"
__homepage__ = "http://bitbucket.org/runeh/anyjson/"
__docformat__ = "restructuredtext"
"""
.. function:: serialize(obj)
Serialize the object to JSON.
.. function:: deserialize(str)
Deserialize JSON-encoded object to a Python object.
.. function:: force_implementation(name)
Load a specific json module. This is useful for testing and not much else
.. attribute:: implementation
The json implementation object. This is probably not useful to you,
except to get the name of the implementation in use. The name is
available through `implementation.name`.
"""
import sys
implementation = None
"""
.. data:: _modules
List of known json modules, and the names of their serialize/unserialize
methods, as well as the exception they throw. Exception can be either
an exception class or a string.
"""
_modules = [("cjson", "encode", "EncodeError", "decode", "DecodeError"),
("jsonlib2", "write", "WriteError", "read", "ReadError"),
("jsonlib", "write", "WriteError", "read", "ReadError"),
("simplejson", "dumps", TypeError, "loads", ValueError),
("json", "dumps", TypeError, "loads", ValueError),
("django.utils.simplejson", "dumps", TypeError, "loads",
ValueError)]
_fields = ("modname", "encoder", "encerror", "decoder", "decerror")
class _JsonImplementation(object):
"""Incapsulates a JSON implementation"""
def __init__(self, modspec):
modinfo = dict(list(zip(_fields, modspec)))
# No try block. We want importerror to end up at caller
module = self._attempt_load(modinfo["modname"])
self.implementation = modinfo["modname"]
self._encode = getattr(module, modinfo["encoder"])
self._decode = getattr(module, modinfo["decoder"])
self._encode_error = modinfo["encerror"]
self._decode_error = modinfo["decerror"]
if isinstance(modinfo["encerror"], str):
self._encode_error = getattr(module, modinfo["encerror"])
if isinstance(modinfo["decerror"], str):
self._decode_error = getattr(module, modinfo["decerror"])
self.name = modinfo["modname"]
def _attempt_load(self, modname):
"""Attempt to load module name modname, returning it on success,
throwing ImportError if module couldn't be imported"""
__import__(modname)
return sys.modules[modname]
def serialize(self, data):
"""Serialize the datastructure to json. Returns a string. Raises
TypeError if the object could not be serialized."""
try:
return self._encode(data)
except self._encode_error as exc:
raise TypeError(*exc.args)
def deserialize(self, s):
"""deserialize the string to python data types. Raises
ValueError if the string vould not be parsed."""
try:
return self._decode(s)
except self._decode_error as exc:
raise ValueError(*exc.args)
def force_implementation(modname):
"""Forces anyjson to use a specific json module if it's available"""
global implementation
for name, spec in [(e[0], e) for e in _modules]:
if name == modname:
implementation = _JsonImplementation(spec)
return
raise ImportError("No module named: %s" % modname)
for modspec in _modules:
try:
implementation = _JsonImplementation(modspec)
break
except ImportError:
pass
else:
raise ImportError("No supported JSON module found")
serialize = lambda value: implementation.serialize(value)
deserialize = lambda value: implementation.deserialize(value)
+118
View File
@@ -0,0 +1,118 @@
u"""
Wraps the best available JSON implementation available in a common interface
"""
__version__ = u"0.2.0"
__author__ = u"Rune Halvorsen <runefh@gmail.com>"
__homepage__ = u"http://bitbucket.org/runeh/anyjson/"
__docformat__ = u"restructuredtext"
u"""
.. function:: serialize(obj)
Serialize the object to JSON.
.. function:: deserialize(str)
Deserialize JSON-encoded object to a Python object.
.. function:: force_implementation(name)
Load a specific json module. This is useful for testing and not much else
.. attribute:: implementation
The json implementation object. This is probably not useful to you,
except to get the name of the implementation in use. The name is
available through `implementation.name`.
"""
import sys
from itertools import izip
implementation = None
u"""
.. data:: _modules
List of known json modules, and the names of their serialize/unserialize
methods, as well as the exception they throw. Exception can be either
an exception class or a string.
"""
_modules = [(u"cjson", u"encode", u"EncodeError", u"decode", u"DecodeError"),
(u"jsonlib2", u"write", u"WriteError", u"read", u"ReadError"),
(u"jsonlib", u"write", u"WriteError", u"read", u"ReadError"),
(u"simplejson", u"dumps", TypeError, u"loads", ValueError),
(u"json", u"dumps", TypeError, u"loads", ValueError),
(u"django.utils.simplejson", u"dumps", TypeError, u"loads",
ValueError)]
_fields = (u"modname", u"encoder", u"encerror", u"decoder", u"decerror")
class _JsonImplementation(object):
u"""Incapsulates a JSON implementation"""
def __init__(self, modspec):
modinfo = dict(list(izip(_fields, modspec)))
# No try block. We want importerror to end up at caller
module = self._attempt_load(modinfo[u"modname"])
self.implementation = modinfo[u"modname"]
self._encode = getattr(module, modinfo[u"encoder"])
self._decode = getattr(module, modinfo[u"decoder"])
self._encode_error = modinfo[u"encerror"]
self._decode_error = modinfo[u"decerror"]
if isinstance(modinfo[u"encerror"], unicode):
self._encode_error = getattr(module, modinfo[u"encerror"])
if isinstance(modinfo[u"decerror"], unicode):
self._decode_error = getattr(module, modinfo[u"decerror"])
self.name = modinfo[u"modname"]
def _attempt_load(self, modname):
u"""Attempt to load module name modname, returning it on success,
throwing ImportError if module couldn't be imported"""
__import__(modname)
return sys.modules[modname]
def serialize(self, data):
u"""Serialize the datastructure to json. Returns a string. Raises
TypeError if the object could not be serialized."""
try:
return self._encode(data)
except self._encode_error, exc:
raise TypeError(*exc.args)
def deserialize(self, s):
u"""deserialize the string to python data types. Raises
ValueError if the string vould not be parsed."""
try:
return self._decode(s)
except self._decode_error, exc:
raise ValueError(*exc.args)
def force_implementation(modname):
u"""Forces anyjson to use a specific json module if it's available"""
global implementation
for name, spec in [(e[0], e) for e in _modules]:
if name == modname:
implementation = _JsonImplementation(spec)
return
raise ImportError(u"No module named: %s" % modname)
for modspec in _modules:
try:
implementation = _JsonImplementation(modspec)
break
except ImportError:
pass
else:
raise ImportError(u"No supported JSON module found")
serialize = lambda value: implementation.serialize(value)
deserialize = lambda value: implementation.deserialize(value)
+484
View File
@@ -0,0 +1,484 @@
# This code is in the public domain, it comes
# with absolutely no warranty and you can do
# absolutely whatever you want with it.
__date__ = '17 May 2007'
__version__ = '1.7'
__doc__= """
This is markup.py - a Python module that attempts to
make it easier to generate HTML/XML from a Python program
in an intuitive, lightweight, customizable and pythonic way.
The code is in the public domain.
Version: %s as of %s.
Documentation and further info is at http://markup.sourceforge.net/
Please send bug reports, feature requests, enhancement
ideas or questions to nogradi at gmail dot com.
Installation: drop markup.py somewhere into your Python path.
""" % ( __version__, __date__ )
import string
class element:
"""This class handles the addition of a new element."""
def __init__( self, tag, case='lower', parent=None ):
self.parent = parent
if case == 'lower':
self.tag = tag.lower( )
else:
self.tag = tag.upper( )
def __call__( self, *args, **kwargs ):
if len( args ) > 1:
raise ArgumentError( self.tag )
# if class_ was defined in parent it should be added to every element
if self.parent is not None and self.parent.class_ is not None:
if 'class_' not in kwargs:
kwargs['class_'] = self.parent.class_
if self.parent is None and len( args ) == 1:
x = [ self.render( self.tag, False, myarg, mydict ) for myarg, mydict in _argsdicts( args, kwargs ) ]
return '\n'.join( x )
elif self.parent is None and len( args ) == 0:
x = [ self.render( self.tag, True, myarg, mydict ) for myarg, mydict in _argsdicts( args, kwargs ) ]
return '\n'.join( x )
if self.tag in self.parent.twotags:
for myarg, mydict in _argsdicts( args, kwargs ):
self.render( self.tag, False, myarg, mydict )
elif self.tag in self.parent.onetags:
if len( args ) == 0:
for myarg, mydict in _argsdicts( args, kwargs ):
self.render( self.tag, True, myarg, mydict ) # here myarg is always None, because len( args ) = 0
else:
raise ClosingError( self.tag )
elif self.parent.mode == 'strict_html' and self.tag in self.parent.deptags:
raise DeprecationError( self.tag )
else:
raise InvalidElementError( self.tag, self.parent.mode )
def render( self, tag, single, between, kwargs ):
"""Append the actual tags to content."""
out = "<%s" % tag
for key, value in kwargs.iteritems( ):
if value is not None: # when value is None that means stuff like <... checked>
key = key.strip('_') # strip this so class_ will mean class, etc.
if key == 'http_equiv': # special cases, maybe change _ to - overall?
key = 'http-equiv'
elif key == 'accept_charset':
key = 'accept-charset'
out = "%s %s=\"%s\"" % ( out, key, escape( value ) )
else:
out = "%s %s" % ( out, key )
if between is not None:
out = "%s>%s</%s>" % ( out, between, tag )
else:
if single:
out = "%s />" % out
else:
out = "%s>" % out
if self.parent is not None:
self.parent.content.append( out )
else:
return out
def close( self ):
"""Append a closing tag unless element has only opening tag."""
if self.tag in self.parent.twotags:
self.parent.content.append( "</%s>" % self.tag )
elif self.tag in self.parent.onetags:
raise ClosingError( self.tag )
elif self.parent.mode == 'strict_html' and self.tag in self.parent.deptags:
raise DeprecationError( self.tag )
def open( self, **kwargs ):
"""Append an opening tag."""
if self.tag in self.parent.twotags or self.tag in self.parent.onetags:
self.render( self.tag, False, None, kwargs )
elif self.mode == 'strict_html' and self.tag in self.parent.deptags:
raise DeprecationError( self.tag )
class page:
"""This is our main class representing a document. Elements are added
as attributes of an instance of this class."""
def __init__( self, mode='strict_html', case='lower', onetags=None, twotags=None, separator='\n', class_=None ):
"""Stuff that effects the whole document.
mode -- 'strict_html' for HTML 4.01 (default)
'html' alias for 'strict_html'
'loose_html' to allow some deprecated elements
'xml' to allow arbitrary elements
case -- 'lower' element names will be printed in lower case (default)
'upper' they will be printed in upper case
onetags -- list or tuple of valid elements with opening tags only
twotags -- list or tuple of valid elements with both opening and closing tags
these two keyword arguments may be used to select
the set of valid elements in 'xml' mode
invalid elements will raise appropriate exceptions
separator -- string to place between added elements, defaults to newline
class_ -- a class that will be added to every element if defined"""
valid_onetags = [ "AREA", "BASE", "BR", "COL", "FRAME", "HR", "IMG", "INPUT", "LINK", "META", "PARAM" ]
valid_twotags = [ "A", "ABBR", "ACRONYM", "ADDRESS", "B", "BDO", "BIG", "BLOCKQUOTE", "BODY", "BUTTON",
"CAPTION", "CITE", "CODE", "COLGROUP", "DD", "DEL", "DFN", "DIV", "DL", "DT", "EM", "FIELDSET",
"FORM", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEAD", "HTML", "I", "IFRAME", "INS",
"KBD", "LABEL", "LEGEND", "LI", "MAP", "NOFRAMES", "NOSCRIPT", "OBJECT", "OL", "OPTGROUP",
"OPTION", "P", "PRE", "Q", "SAMP", "SCRIPT", "SELECT", "SMALL", "SPAN", "STRONG", "STYLE",
"SUB", "SUP", "TABLE", "TBODY", "TD", "TEXTAREA", "TFOOT", "TH", "THEAD", "TITLE", "TR",
"TT", "UL", "VAR" ]
deprecated_onetags = [ "BASEFONT", "ISINDEX" ]
deprecated_twotags = [ "APPLET", "CENTER", "DIR", "FONT", "MENU", "S", "STRIKE", "U" ]
self.header = [ ]
self.content = [ ]
self.footer = [ ]
self.case = case
self.separator = separator
# init( ) sets it to True so we know that </body></html> has to be printed at the end
self._full = False
self.class_= class_
if mode == 'strict_html' or mode == 'html':
self.onetags = valid_onetags
self.onetags += map( string.lower, self.onetags )
self.twotags = valid_twotags
self.twotags += map( string.lower, self.twotags )
self.deptags = deprecated_onetags + deprecated_twotags
self.deptags += map( string.lower, self.deptags )
self.mode = 'strict_html'
elif mode == 'loose_html':
self.onetags = valid_onetags + deprecated_onetags
self.onetags += map( string.lower, self.onetags )
self.twotags = valid_twotags + deprecated_twotags
self.twotags += map( string.lower, self.twotags )
self.mode = mode
elif mode == 'xml':
if onetags and twotags:
self.onetags = onetags
self.twotags = twotags
elif ( onetags and not twotags ) or ( twotags and not onetags ):
raise CustomizationError( )
else:
self.onetags = russell( )
self.twotags = russell( )
self.mode = mode
else:
raise ModeError( mode )
def __getattr__( self, attr ):
if attr.startswith("__") and attr.endswith("__"):
raise AttributeError, attr
return element( attr, case=self.case, parent=self )
def __str__( self ):
if self._full and ( self.mode == 'strict_html' or self.mode == 'loose_html' ):
end = [ '</body>', '</html>' ]
else:
end = [ ]
return self.separator.join( self.header + self.content + self.footer + end )
def __call__( self, escape=False ):
"""Return the document as a string.
escape -- False print normally
True replace < and > by &lt; and &gt;
the default escape sequences in most browsers"""
if escape:
return _escape( self.__str__( ) )
else:
return self.__str__( )
def add( self, text ):
"""This is an alias to addcontent."""
self.addcontent( text )
def addfooter( self, text ):
"""Add some text to the bottom of the document"""
self.footer.append( text )
def addheader( self, text ):
"""Add some text to the top of the document"""
self.header.append( text )
def addcontent( self, text ):
"""Add some text to the main part of the document"""
self.content.append( text )
def init( self, lang='en', css=None, metainfo=None, title=None, header=None,
footer=None, charset=None, encoding=None, doctype=None, bodyattrs=None, script=None ):
"""This method is used for complete documents with appropriate
doctype, encoding, title, etc information. For an HTML/XML snippet
omit this method.
lang -- language, usually a two character string, will appear
as <html lang='en'> in html mode (ignored in xml mode)
css -- Cascading Style Sheet filename as a string or a list of
strings for multiple css files (ignored in xml mode)
metainfo -- a dictionary in the form { 'name':'content' } to be inserted
into meta element(s) as <meta name='name' content='content'>
(ignored in xml mode)
bodyattrs --a dictionary in the form { 'key':'value', ... } which will be added
as attributes of the <body> element as <body key='value' ... >
(ignored in xml mode)
script -- dictionary containing src:type pairs, <script type='text/type' src=src></script>
title -- the title of the document as a string to be inserted into
a title element as <title>my title</title> (ignored in xml mode)
header -- some text to be inserted right after the <body> element
(ignored in xml mode)
footer -- some text to be inserted right before the </body> element
(ignored in xml mode)
charset -- a string defining the character set, will be inserted into a
<meta http-equiv='Content-Type' content='text/html; charset=myset'>
element (ignored in xml mode)
encoding -- a string defining the encoding, will be put into to first line of
the document as <?xml version='1.0' encoding='myencoding' ?> in
xml mode (ignored in html mode)
doctype -- the document type string, defaults to
<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>
in html mode (ignored in xml mode)"""
self._full = True
if self.mode == 'strict_html' or self.mode == 'loose_html':
if doctype is None:
doctype = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>"
self.header.append( doctype )
self.html( lang=lang )
self.head( )
if charset is not None:
self.meta( http_equiv='Content-Type', content="text/html; charset=%s" % charset )
if metainfo is not None:
self.metainfo( metainfo )
if css is not None:
self.css( css )
if title is not None:
self.title( title )
if script is not None:
self.scripts( script )
self.head.close()
if bodyattrs is not None:
self.body( **bodyattrs )
else:
self.body( )
if header is not None:
self.content.append( header )
if footer is not None:
self.footer.append( footer )
elif self.mode == 'xml':
if doctype is None:
if encoding is not None:
doctype = "<?xml version='1.0' encoding='%s' ?>" % encoding
else:
doctype = "<?xml version='1.0' ?>"
self.header.append( doctype )
def css( self, filelist ):
"""This convenience function is only useful for html.
It adds css stylesheet(s) to the document via the <link> element."""
if isinstance( filelist, basestring ):
self.link( href=filelist, rel='stylesheet', type='text/css', media='all' )
else:
for file in filelist:
self.link( href=file, rel='stylesheet', type='text/css', media='all' )
def metainfo( self, mydict ):
"""This convenience function is only useful for html.
It adds meta information via the <meta> element, the argument is
a dictionary of the form { 'name':'content' }."""
if isinstance( mydict, dict ):
for name, content in mydict.iteritems( ):
self.meta( name=name, content=content )
else:
raise TypeError, "Metainfo should be called with a dictionary argument of name:content pairs."
def scripts( self, mydict ):
"""Only useful in html, mydict is dictionary of src:type pairs will
be rendered as <script type='text/type' src=src></script>"""
if isinstance( mydict, dict ):
for src, type in mydict.iteritems( ):
self.script( '', src=src, type='text/%s' % type )
else:
raise TypeError, "Script should be given a dictionary of src:type pairs."
class _oneliner:
"""An instance of oneliner returns a string corresponding to one element.
This class can be used to write 'oneliners' that return a string
immediately so there is no need to instantiate the page class."""
def __init__( self, case='lower' ):
self.case = case
def __getattr__( self, attr ):
if attr.startswith("__") and attr.endswith("__"):
raise AttributeError, attr
return element( attr, case=self.case, parent=None )
oneliner = _oneliner( case='lower' )
upper_oneliner = _oneliner( case='upper' )
def _argsdicts( args, mydict ):
"""A utility generator that pads argument list and dictionary values, will only be called with len( args ) = 0, 1."""
if len( args ) == 0:
args = None,
elif len( args ) == 1:
args = _totuple( args[0] )
else:
raise Exception, "We should have never gotten here."
mykeys = mydict.keys( )
myvalues = map( _totuple, mydict.values( ) )
maxlength = max( map( len, [ args ] + myvalues ) )
for i in xrange( maxlength ):
thisdict = { }
for key, value in zip( mykeys, myvalues ):
try:
thisdict[ key ] = value[i]
except IndexError:
thisdict[ key ] = value[-1]
try:
thisarg = args[i]
except IndexError:
thisarg = args[-1]
yield thisarg, thisdict
def _totuple( x ):
"""Utility stuff to convert string, int, float, None or anything to a usable tuple."""
if isinstance( x, basestring ):
out = x,
elif isinstance( x, ( int, float ) ):
out = str( x ),
elif x is None:
out = None,
else:
out = tuple( x )
return out
def escape( text, newline=False ):
"""Escape special html characters."""
if isinstance( text, basestring ):
if '&' in text:
text = text.replace( '&', '&amp;' )
if '>' in text:
text = text.replace( '>', '&gt;' )
if '<' in text:
text = text.replace( '<', '&lt;' )
if '\"' in text:
text = text.replace( '\"', '&quot;' )
if '\'' in text:
text = text.replace( '\'', '&quot;' )
if newline:
if '\n' in text:
text = text.replace( '\n', '<br>' )
return text
_escape = escape
def unescape( text ):
"""Inverse of escape."""
if isinstance( text, basestring ):
if '&amp;' in text:
text = text.replace( '&amp;', '&' )
if '&gt;' in text:
text = text.replace( '&gt;', '>' )
if '&lt;' in text:
text = text.replace( '&lt;', '<' )
if '&quot;' in text:
text = text.replace( '&quot;', '\"' )
return text
class dummy:
"""A dummy class for attaching attributes."""
pass
doctype = dummy( )
doctype.frameset = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Frameset//EN' 'http://www.w3.org/TR/html4/frameset.dtd'>"
doctype.strict = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>"
doctype.loose = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN' 'http://www.w3.org/TR/html4/loose.dtd'>"
class russell:
"""A dummy class that contains anything."""
def __contains__( self, item ):
return True
class MarkupError( Exception ):
"""All our exceptions subclass this."""
def __str__( self ):
return self.message
class ClosingError( MarkupError ):
def __init__( self, tag ):
self.message = "The element '%s' does not accept non-keyword arguments (has no closing tag)." % tag
class OpeningError( MarkupError ):
def __init__( self, tag ):
self.message = "The element '%s' can not be opened." % tag
class ArgumentError( MarkupError ):
def __init__( self, tag ):
self.message = "The element '%s' was called with more than one non-keyword argument." % tag
class InvalidElementError( MarkupError ):
def __init__( self, tag, mode ):
self.message = "The element '%s' is not valid for your mode '%s'." % ( tag, mode )
class DeprecationError( MarkupError ):
def __init__( self, tag ):
self.message = "The element '%s' is deprecated, instantiate markup.page with mode='loose_html' to allow it." % tag
class ModeError( MarkupError ):
def __init__( self, mode ):
self.message = "Mode '%s' is invalid, possible values: strict_html, loose_html, xml." % mode
class CustomizationError( MarkupError ):
def __init__( self ):
self.message = "If you customize the allowed elements, you must define both types 'onetags' and 'twotags'."
if __name__ == '__main__':
print __doc__
+484
View File
@@ -0,0 +1,484 @@
# This code is in the public domain, it comes
# with absolutely no warranty and you can do
# absolutely whatever you want with it.
__date__ = '17 May 2007'
__version__ = '1.7'
__doc__= """
This is markup.py - a Python module that attempts to
make it easier to generate HTML/XML from a Python program
in an intuitive, lightweight, customizable and pythonic way.
The code is in the public domain.
Version: %s as of %s.
Documentation and further info is at http://markup.sourceforge.net/
Please send bug reports, feature requests, enhancement
ideas or questions to nogradi at gmail dot com.
Installation: drop markup.py somewhere into your Python path.
""" % ( __version__, __date__ )
import string
class element:
"""This class handles the addition of a new element."""
def __init__( self, tag, case='lower', parent=None ):
self.parent = parent
if case == 'lower':
self.tag = tag.lower( )
else:
self.tag = tag.upper( )
def __call__( self, *args, **kwargs ):
if len( args ) > 1:
raise ArgumentError( self.tag )
# if class_ was defined in parent it should be added to every element
if self.parent is not None and self.parent.class_ is not None:
if 'class_' not in kwargs:
kwargs['class_'] = self.parent.class_
if self.parent is None and len( args ) == 1:
x = [ self.render( self.tag, False, myarg, mydict ) for myarg, mydict in _argsdicts( args, kwargs ) ]
return '\n'.join( x )
elif self.parent is None and len( args ) == 0:
x = [ self.render( self.tag, True, myarg, mydict ) for myarg, mydict in _argsdicts( args, kwargs ) ]
return '\n'.join( x )
if self.tag in self.parent.twotags:
for myarg, mydict in _argsdicts( args, kwargs ):
self.render( self.tag, False, myarg, mydict )
elif self.tag in self.parent.onetags:
if len( args ) == 0:
for myarg, mydict in _argsdicts( args, kwargs ):
self.render( self.tag, True, myarg, mydict ) # here myarg is always None, because len( args ) = 0
else:
raise ClosingError( self.tag )
elif self.parent.mode == 'strict_html' and self.tag in self.parent.deptags:
raise DeprecationError( self.tag )
else:
raise InvalidElementError( self.tag, self.parent.mode )
def render( self, tag, single, between, kwargs ):
"""Append the actual tags to content."""
out = "<%s" % tag
for key, value in kwargs.items( ):
if value is not None: # when value is None that means stuff like <... checked>
key = key.strip('_') # strip this so class_ will mean class, etc.
if key == 'http_equiv': # special cases, maybe change _ to - overall?
key = 'http-equiv'
elif key == 'accept_charset':
key = 'accept-charset'
out = "%s %s=\"%s\"" % ( out, key, escape( value ) )
else:
out = "%s %s" % ( out, key )
if between is not None:
out = "%s>%s</%s>" % ( out, between, tag )
else:
if single:
out = "%s />" % out
else:
out = "%s>" % out
if self.parent is not None:
self.parent.content.append( out )
else:
return out
def close( self ):
"""Append a closing tag unless element has only opening tag."""
if self.tag in self.parent.twotags:
self.parent.content.append( "</%s>" % self.tag )
elif self.tag in self.parent.onetags:
raise ClosingError( self.tag )
elif self.parent.mode == 'strict_html' and self.tag in self.parent.deptags:
raise DeprecationError( self.tag )
def open( self, **kwargs ):
"""Append an opening tag."""
if self.tag in self.parent.twotags or self.tag in self.parent.onetags:
self.render( self.tag, False, None, kwargs )
elif self.mode == 'strict_html' and self.tag in self.parent.deptags:
raise DeprecationError( self.tag )
class page:
"""This is our main class representing a document. Elements are added
as attributes of an instance of this class."""
def __init__( self, mode='strict_html', case='lower', onetags=None, twotags=None, separator='\n', class_=None ):
"""Stuff that effects the whole document.
mode -- 'strict_html' for HTML 4.01 (default)
'html' alias for 'strict_html'
'loose_html' to allow some deprecated elements
'xml' to allow arbitrary elements
case -- 'lower' element names will be printed in lower case (default)
'upper' they will be printed in upper case
onetags -- list or tuple of valid elements with opening tags only
twotags -- list or tuple of valid elements with both opening and closing tags
these two keyword arguments may be used to select
the set of valid elements in 'xml' mode
invalid elements will raise appropriate exceptions
separator -- string to place between added elements, defaults to newline
class_ -- a class that will be added to every element if defined"""
valid_onetags = [ "AREA", "BASE", "BR", "COL", "FRAME", "HR", "IMG", "INPUT", "LINK", "META", "PARAM" ]
valid_twotags = [ "A", "ABBR", "ACRONYM", "ADDRESS", "B", "BDO", "BIG", "BLOCKQUOTE", "BODY", "BUTTON",
"CAPTION", "CITE", "CODE", "COLGROUP", "DD", "DEL", "DFN", "DIV", "DL", "DT", "EM", "FIELDSET",
"FORM", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEAD", "HTML", "I", "IFRAME", "INS",
"KBD", "LABEL", "LEGEND", "LI", "MAP", "NOFRAMES", "NOSCRIPT", "OBJECT", "OL", "OPTGROUP",
"OPTION", "P", "PRE", "Q", "SAMP", "SCRIPT", "SELECT", "SMALL", "SPAN", "STRONG", "STYLE",
"SUB", "SUP", "TABLE", "TBODY", "TD", "TEXTAREA", "TFOOT", "TH", "THEAD", "TITLE", "TR",
"TT", "UL", "VAR" ]
deprecated_onetags = [ "BASEFONT", "ISINDEX" ]
deprecated_twotags = [ "APPLET", "CENTER", "DIR", "FONT", "MENU", "S", "STRIKE", "U" ]
self.header = [ ]
self.content = [ ]
self.footer = [ ]
self.case = case
self.separator = separator
# init( ) sets it to True so we know that </body></html> has to be printed at the end
self._full = False
self.class_= class_
if mode == 'strict_html' or mode == 'html':
self.onetags = valid_onetags
self.onetags += list(map( str.lower, self.onetags ))
self.twotags = valid_twotags
self.twotags += list(map( str.lower, self.twotags ))
self.deptags = deprecated_onetags + deprecated_twotags
self.deptags += list(map( str.lower, self.deptags ))
self.mode = 'strict_html'
elif mode == 'loose_html':
self.onetags = valid_onetags + deprecated_onetags
self.onetags += list(map( str.lower, self.onetags ))
self.twotags = valid_twotags + deprecated_twotags
self.twotags += list(map( str.lower, self.twotags ))
self.mode = mode
elif mode == 'xml':
if onetags and twotags:
self.onetags = onetags
self.twotags = twotags
elif ( onetags and not twotags ) or ( twotags and not onetags ):
raise CustomizationError( )
else:
self.onetags = russell( )
self.twotags = russell( )
self.mode = mode
else:
raise ModeError( mode )
def __getattr__( self, attr ):
if attr.startswith("__") and attr.endswith("__"):
raise AttributeError(attr)
return element( attr, case=self.case, parent=self )
def __str__( self ):
if self._full and ( self.mode == 'strict_html' or self.mode == 'loose_html' ):
end = [ '</body>', '</html>' ]
else:
end = [ ]
return self.separator.join( self.header + self.content + self.footer + end )
def __call__( self, escape=False ):
"""Return the document as a string.
escape -- False print normally
True replace < and > by &lt; and &gt;
the default escape sequences in most browsers"""
if escape:
return _escape( self.__str__( ) )
else:
return self.__str__( )
def add( self, text ):
"""This is an alias to addcontent."""
self.addcontent( text )
def addfooter( self, text ):
"""Add some text to the bottom of the document"""
self.footer.append( text )
def addheader( self, text ):
"""Add some text to the top of the document"""
self.header.append( text )
def addcontent( self, text ):
"""Add some text to the main part of the document"""
self.content.append( text )
def init( self, lang='en', css=None, metainfo=None, title=None, header=None,
footer=None, charset=None, encoding=None, doctype=None, bodyattrs=None, script=None ):
"""This method is used for complete documents with appropriate
doctype, encoding, title, etc information. For an HTML/XML snippet
omit this method.
lang -- language, usually a two character string, will appear
as <html lang='en'> in html mode (ignored in xml mode)
css -- Cascading Style Sheet filename as a string or a list of
strings for multiple css files (ignored in xml mode)
metainfo -- a dictionary in the form { 'name':'content' } to be inserted
into meta element(s) as <meta name='name' content='content'>
(ignored in xml mode)
bodyattrs --a dictionary in the form { 'key':'value', ... } which will be added
as attributes of the <body> element as <body key='value' ... >
(ignored in xml mode)
script -- dictionary containing src:type pairs, <script type='text/type' src=src></script>
title -- the title of the document as a string to be inserted into
a title element as <title>my title</title> (ignored in xml mode)
header -- some text to be inserted right after the <body> element
(ignored in xml mode)
footer -- some text to be inserted right before the </body> element
(ignored in xml mode)
charset -- a string defining the character set, will be inserted into a
<meta http-equiv='Content-Type' content='text/html; charset=myset'>
element (ignored in xml mode)
encoding -- a string defining the encoding, will be put into to first line of
the document as <?xml version='1.0' encoding='myencoding' ?> in
xml mode (ignored in html mode)
doctype -- the document type string, defaults to
<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>
in html mode (ignored in xml mode)"""
self._full = True
if self.mode == 'strict_html' or self.mode == 'loose_html':
if doctype is None:
doctype = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>"
self.header.append( doctype )
self.html( lang=lang )
self.head( )
if charset is not None:
self.meta( http_equiv='Content-Type', content="text/html; charset=%s" % charset )
if metainfo is not None:
self.metainfo( metainfo )
if css is not None:
self.css( css )
if title is not None:
self.title( title )
if script is not None:
self.scripts( script )
self.head.close()
if bodyattrs is not None:
self.body( **bodyattrs )
else:
self.body( )
if header is not None:
self.content.append( header )
if footer is not None:
self.footer.append( footer )
elif self.mode == 'xml':
if doctype is None:
if encoding is not None:
doctype = "<?xml version='1.0' encoding='%s' ?>" % encoding
else:
doctype = "<?xml version='1.0' ?>"
self.header.append( doctype )
def css( self, filelist ):
"""This convenience function is only useful for html.
It adds css stylesheet(s) to the document via the <link> element."""
if isinstance( filelist, str ):
self.link( href=filelist, rel='stylesheet', type='text/css', media='all' )
else:
for file in filelist:
self.link( href=file, rel='stylesheet', type='text/css', media='all' )
def metainfo( self, mydict ):
"""This convenience function is only useful for html.
It adds meta information via the <meta> element, the argument is
a dictionary of the form { 'name':'content' }."""
if isinstance( mydict, dict ):
for name, content in mydict.items( ):
self.meta( name=name, content=content )
else:
raise TypeError("Metainfo should be called with a dictionary argument of name:content pairs.")
def scripts( self, mydict ):
"""Only useful in html, mydict is dictionary of src:type pairs will
be rendered as <script type='text/type' src=src></script>"""
if isinstance( mydict, dict ):
for src, type in mydict.items( ):
self.script( '', src=src, type='text/%s' % type )
else:
raise TypeError("Script should be given a dictionary of src:type pairs.")
class _oneliner:
"""An instance of oneliner returns a string corresponding to one element.
This class can be used to write 'oneliners' that return a string
immediately so there is no need to instantiate the page class."""
def __init__( self, case='lower' ):
self.case = case
def __getattr__( self, attr ):
if attr.startswith("__") and attr.endswith("__"):
raise AttributeError(attr)
return element( attr, case=self.case, parent=None )
oneliner = _oneliner( case='lower' )
upper_oneliner = _oneliner( case='upper' )
def _argsdicts( args, mydict ):
"""A utility generator that pads argument list and dictionary values, will only be called with len( args ) = 0, 1."""
if len( args ) == 0:
args = None,
elif len( args ) == 1:
args = _totuple( args[0] )
else:
raise Exception("We should have never gotten here.")
mykeys = list(mydict.keys( ))
myvalues = list(map( _totuple, list(mydict.values( )) ))
maxlength = max( list(map( len, [ args ] + myvalues )) )
for i in range( maxlength ):
thisdict = { }
for key, value in zip( mykeys, myvalues ):
try:
thisdict[ key ] = value[i]
except IndexError:
thisdict[ key ] = value[-1]
try:
thisarg = args[i]
except IndexError:
thisarg = args[-1]
yield thisarg, thisdict
def _totuple( x ):
"""Utility stuff to convert string, int, float, None or anything to a usable tuple."""
if isinstance( x, str ):
out = x,
elif isinstance( x, ( int, float ) ):
out = str( x ),
elif x is None:
out = None,
else:
out = tuple( x )
return out
def escape( text, newline=False ):
"""Escape special html characters."""
if isinstance( text, str ):
if '&' in text:
text = text.replace( '&', '&amp;' )
if '>' in text:
text = text.replace( '>', '&gt;' )
if '<' in text:
text = text.replace( '<', '&lt;' )
if '\"' in text:
text = text.replace( '\"', '&quot;' )
if '\'' in text:
text = text.replace( '\'', '&quot;' )
if newline:
if '\n' in text:
text = text.replace( '\n', '<br>' )
return text
_escape = escape
def unescape( text ):
"""Inverse of escape."""
if isinstance( text, str ):
if '&amp;' in text:
text = text.replace( '&amp;', '&' )
if '&gt;' in text:
text = text.replace( '&gt;', '>' )
if '&lt;' in text:
text = text.replace( '&lt;', '<' )
if '&quot;' in text:
text = text.replace( '&quot;', '\"' )
return text
class dummy:
"""A dummy class for attaching attributes."""
pass
doctype = dummy( )
doctype.frameset = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Frameset//EN' 'http://www.w3.org/TR/html4/frameset.dtd'>"
doctype.strict = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>"
doctype.loose = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN' 'http://www.w3.org/TR/html4/loose.dtd'>"
class russell:
"""A dummy class that contains anything."""
def __contains__( self, item ):
return True
class MarkupError( Exception ):
"""All our exceptions subclass this."""
def __str__( self ):
return self.message
class ClosingError( MarkupError ):
def __init__( self, tag ):
self.message = "The element '%s' does not accept non-keyword arguments (has no closing tag)." % tag
class OpeningError( MarkupError ):
def __init__( self, tag ):
self.message = "The element '%s' can not be opened." % tag
class ArgumentError( MarkupError ):
def __init__( self, tag ):
self.message = "The element '%s' was called with more than one non-keyword argument." % tag
class InvalidElementError( MarkupError ):
def __init__( self, tag, mode ):
self.message = "The element '%s' is not valid for your mode '%s'." % ( tag, mode )
class DeprecationError( MarkupError ):
def __init__( self, tag ):
self.message = "The element '%s' is deprecated, instantiate markup.page with mode='loose_html' to allow it." % tag
class ModeError( MarkupError ):
def __init__( self, mode ):
self.message = "Mode '%s' is invalid, possible values: strict_html, loose_html, xml." % mode
class CustomizationError( MarkupError ):
def __init__( self ):
self.message = "If you customize the allowed elements, you must define both types 'onetags' and 'twotags'."
if __name__ == '__main__':
print(__doc__)
+53
View File
@@ -0,0 +1,53 @@
# file openpyxl/__init__.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Imports for the openpyxl package."""
# package imports
from . import cell
from . import namedrange
from . import style
from . import workbook
from . import worksheet
from . import reader
from . import shared
from . import writer
# constants
__major__ = 1 # for major interface/format changes
__minor__ = 5 # for minor interface/format changes
__release__ = 2 # 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://bitbucket.org/ericgazoni/openpyxl/wiki/Home'
__downloadUrl__ = "http://bitbucket.org/ericgazoni/openpyxl/downloads"
__all__ = ('reader', 'shared', 'writer',)
+384
View File
@@ -0,0 +1,384 @@
# file openpyxl/cell.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""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
import datetime
import re
# package imports
from .shared.date_time import SharedDate
from .shared.exc import CellCoordinatesException, \
ColumnStringIndexException, DataTypeException
from .style import NumberFormat
# 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()
return (column, int(row))
def absolute_coordinate(coord_string):
"""Convert a coordinate to an absolute coordinate string (B12 -> $B$12)"""
parts = ABSOLUTE_RE.match(coord_string).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])
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')
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'
VALID_TYPES = [TYPE_STRING, TYPE_FORMULA, TYPE_NUMERIC, TYPE_BOOL,
TYPE_NULL, TYPE_INLINE, TYPE_ERROR]
RE_PATTERNS = {
'percentage': re.compile('^\-?[0-9]*\.?[0-9]*\s?\%$'),
'time': re.compile('^(\d|[0-1]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$'),
'numeric': re.compile('^\-?([0-9]+\\.?[0-9]*|[0-9]*\\.?[0-9]+)((E|e)\-?[0-9]+)?$'), }
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
def __repr__(self):
return "<Cell %s.%s>" % (self.parent.title, self.get_coordinate())
def check_string(self, value):
"""Check string coding, length, and line break character"""
# convert to unicode string
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, (int, float)):
try:
value = int(value)
except ValueError:
value = float(value)
return value
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: unicode,
self.TYPE_NUMERIC: self.check_numeric,
self.TYPE_BOOL: bool, }
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, (int, float)):
data_type = self.TYPE_NUMERIC
elif not value:
data_type = self.TYPE_STRING
elif isinstance(value, (datetime.datetime, datetime.date)):
data_type = self.TYPE_NUMERIC
elif isinstance(value, basestring) and value[0] == '=':
data_type = self.TYPE_FORMULA
elif self.RE_PATTERNS['numeric'].match(value):
data_type = self.TYPE_NUMERIC
elif value.strip() in self.ERROR_CODES:
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
percentage_search = self.RE_PATTERNS['percentage'].match(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
time_search = self.RE_PATTERNS['time'].match(value)
if time_search:
sep_count = value.count(':') #pylint: disable-msg=E1103
if sep_count == 1:
hours, minutes = [int(bit) for bit in value.split(':')] #pylint: disable-msg=E1103
seconds = 0
elif sep_count == 2:
hours, minutes, seconds = \
[int(bit) for bit in value.split(':')] #pylint: disable-msg=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):
value = SharedDate().datetime_to_julian(date = value)
self.set_value_explicit(value, self.TYPE_NUMERIC)
self._set_number_format(NumberFormat.FORMAT_DATE_YYYYMMDD2)
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 = SharedDate().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-msg=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, (int, float)))
+340
View File
@@ -0,0 +1,340 @@
'''
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.
@license: http://www.opensource.org/licenses/mit-license.php
@author: Eric Gazoni
'''
import math
from .style import NumberFormat
from .drawing import Drawing, Shape
from .shared.units import pixels_to_EMU, short_color
from .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
@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.cache[0], basestring):
return 'str'
else:
return 'num'
def _get_ref(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):
""" read data in sheet - to be used at writing time """
cache = []
if self.pos2:
for row in range(self.pos1[0], self.pos2[0]+1):
for col in range(self.pos1[1], self.pos2[1]+1):
cache.append(self.sheet.cell(row=row, column=col).value)
else:
cell = self.sheet.cell(row=self.pos1[0], column=self.pos1[1])
cache.append(cell.value)
return cache
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
self._color = 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_min_max(self):
if self.error_bar:
err_cache = self.error_bar.values._get_cache()
vals = [v + err_cache[i] \
for i,v in enumerate(self.values._get_cache())]
else:
vals = self.values._get_cache()
return min(vals), max(vals)
def __len__(self):
return len(self.values.cache)
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
class Chart(object):
""" raw chart class """
GROUPING_CLUSTERED = 'clustered'
GROUPING_STANDARD = 'standard'
BAR_CHART = 1
LINE_CHART = 2
SCATTER_CHART = 3
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.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 max([len(s.values._get_cache()) 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([max(s.values._get_cache()) for s in self._series])
return len(str(int(_max)))
def _compute_min_max(self):
""" compute y axis limits and units """
maxi = max([max(s.values._get_cache()) 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.y_axis.max = maxi
self.y_axis.unit = unit
def _compute_xmin_xmax(self):
""" compute x axis limits and units """
maxi = max([max(s.xvalues._get_cache()) 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)
+401
View File
@@ -0,0 +1,401 @@
'''
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.
@license: http://www.opensource.org/licenses/mit-license.php
@author: Eric Gazoni
'''
import math
from .style import Color
from .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 = width
else:
self._width = math.ceil(yratio * self._width)
self._height = height
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)
# print self._border_width
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))
+68
View File
@@ -0,0 +1,68 @@
# file openpyxl/namedrange.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Track named groups of cells in a worksheet"""
# Python stdlib imports
import re
# package imports
from .shared.exc import NamedRangeException
# constants
NAMED_RANGE_RE = re.compile("'?([^']*)'?!((\$([A-Za-z]+))?\$([0-9]+)(:(\$([A-Za-z]+))?(\$([0-9]+)))?)$")
class NamedRange(object):
"""A named group of cells"""
__slots__ = ('name', 'destinations', 'local_only')
def __init__(self, name, destinations):
self.name = name
self.destinations = destinations
self.local_only = False
def __str__(self):
return ','.join(['%s!%s' % (sheet, name) for sheet, name in self.destinations])
def __repr__(self):
return '<%s "%s">' % (self.__class__.__name__, str(self))
def split_named_range(range_string):
"""Separate a named range into its component parts"""
destinations = []
for range_string in range_string.split(','):
match = NAMED_RANGE_RE.match(range_string)
if not match:
raise NamedRangeException('Invalid named range string: "%s"' % range_string)
else:
sheet_name, xlrange = match.groups()[:2]
destinations.append((sheet_name, xlrange))
return destinations
@@ -0,0 +1,33 @@
# file openpyxl/reader/__init__.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Imports for the openpyxl.reader namespace."""
# package imports
from ..reader import excel
from ..reader import strings
from ..reader import style
from ..reader import workbook
from ..reader import worksheet
+109
View File
@@ -0,0 +1,109 @@
# file openpyxl/reader/excel.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Read an xlsx file into Python"""
# Python stdlib imports
from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile
# package imports
from ..shared.exc import OpenModeError, InvalidFileException
from ..shared.ooxml import ARC_SHARED_STRINGS, ARC_CORE, ARC_APP, \
ARC_WORKBOOK, PACKAGE_WORKSHEETS, ARC_STYLE
from ..workbook import Workbook
from ..reader.strings import read_string_table
from ..reader.style import read_style_table
from ..reader.workbook import read_sheets_titles, read_named_ranges, \
read_properties_core, get_sheet_ids
from ..reader.worksheet import read_worksheet
from ..reader.iter_worksheet import unpack_worksheet
def load_workbook(filename, use_iterators = False):
"""Open the given filename and return the workbook
:param filename: the path to open
:type filename: string
:param use_iterators: use lazy load for cells
:type use_iterators: 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.
"""
if isinstance(filename, file):
# 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, RuntimeError, IOError, ValueError):
raise InvalidFileException()
wb = Workbook()
if use_iterators:
wb._set_optimized_read()
try:
_load_workbook(wb, archive, filename, use_iterators)
except KeyError:
raise InvalidFileException()
finally:
archive.close()
return wb
def _load_workbook(wb, archive, filename, use_iterators):
# get workbook-level information
wb.properties = read_properties_core(archive.read(ARC_CORE))
try:
string_table = read_string_table(archive.read(ARC_SHARED_STRINGS))
except KeyError:
string_table = {}
style_table = read_style_table(archive.read(ARC_STYLE))
# get worksheets
wb.worksheets = [] # remove preset worksheet
sheet_names = read_sheets_titles(archive.read(ARC_APP))
for i, sheet_name in enumerate(sheet_names):
sheet_codename = 'sheet%d.xml' % (i + 1)
worksheet_path = '%s/%s' % (PACKAGE_WORKSHEETS, sheet_codename)
if not use_iterators:
new_ws = read_worksheet(archive.read(worksheet_path), wb, sheet_name, string_table, style_table)
else:
xml_source = unpack_worksheet(archive, worksheet_path)
new_ws = read_worksheet(xml_source, wb, sheet_name, string_table, style_table, filename, sheet_codename)
#new_ws = read_worksheet(archive.read(worksheet_path), 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)
@@ -0,0 +1,348 @@
# file openpyxl/reader/iter_worksheet.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
""" Iterators-based worksheet reader
*Still very raw*
"""
from ....compat import BytesIO as StringIO
import warnings
import operator
from functools import partial
from itertools import groupby, ifilter
from ..worksheet import Worksheet
from ..cell import coordinate_from_string, get_column_letter, Cell
from ..reader.excel import get_sheet_ids
from ..reader.strings import read_string_table
from ..reader.style import read_style_table, NumberFormat
from ..shared.date_time import SharedDate
from ..reader.worksheet import read_dimension
from ..shared.ooxml import (MIN_COLUMN, MAX_COLUMN, PACKAGE_WORKSHEETS,
MAX_ROW, MIN_ROW, ARC_SHARED_STRINGS, ARC_APP, ARC_STYLE)
try:
from xml.etree.cElementTree import iterparse
except ImportError:
from xml.etree.ElementTree import iterparse
from zipfile import ZipFile
from .. import cell
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
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):
res = (self.data_type == Cell.TYPE_NUMERIC
and self.number_format is not None
and ('d' in self.number_format
or 'm' in self.number_format
or 'y' in self.number_format
or 'h' in self.number_format
or 's' in self.number_format
))
return res
def iter_rows(workbook_name, sheet_name, xml_source, 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
try:
string_table = read_string_table(archive.read(ARC_SHARED_STRINGS))
except KeyError:
string_table = {}
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)
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):
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 == Cell.TYPE_STRING:
cell = cell._replace(internal_value = 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 == 'True')
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))
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):
Worksheet.__init__(self, parent_workbook, title)
self._workbook_name = workbook_name
self._sheet_codename = sheet_codename
self._xml_source = xml_source
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)
def cell(self, *args, **kwargs):
raise NotImplementedError("use 'iter_rows()' instead")
def range(self, *args, **kwargs):
raise NotImplementedError("use 'iter_rows()' instead")
def unpack_worksheet(archive, filename):
temp_file = tempfile.TemporaryFile(mode='r+', 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)
if decoder:
temp_file.write(decoder.decompress('Z'))
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("<HH", archive.fp.read(4))
return zinfo.header_offset + 30 + file_name_len + extra_len
@@ -0,0 +1,64 @@
# file openpyxl/reader/strings.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Read the shared strings table."""
# package imports
from ..shared.xmltools import fromstring, QName
from ..shared.ooxml import NAMESPACES
def read_string_table(xml_source):
"""Read in all shared strings in the table"""
table = {}
xmlns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
root = fromstring(text=xml_source)
string_index_nodes = root.findall(QName(xmlns, 'si').text)
for index, string_index_node in enumerate(string_index_nodes):
table[index] = get_string(xmlns, string_index_node)
return table
def get_string(xmlns, string_index_node):
"""Read the contents of a specific string index"""
rich_nodes = string_index_node.findall(QName(xmlns, 'r').text)
if rich_nodes:
reconstructed_text = []
for rich_node in rich_nodes:
partial_text = get_text(xmlns, rich_node)
reconstructed_text.append(partial_text)
return ''.join(reconstructed_text)
else:
return get_text(xmlns, string_index_node)
def get_text(xmlns, rich_node):
"""Read rich text, discarding formatting if not disallowed"""
text_node = rich_node.find(QName(xmlns, 't').text)
partial_text = text_node.text or ''
if text_node.get(QName(NAMESPACES['xml'], 'space').text) != 'preserve':
partial_text = partial_text.strip()
return unicode(partial_text)
+69
View File
@@ -0,0 +1,69 @@
# file openpyxl/reader/style.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Read shared style definitions"""
# package imports
from ..shared.xmltools import fromstring, QName
from ..shared.exc import MissingNumberFormat
from ..style import Style, NumberFormat
def read_style_table(xml_source):
"""Read styles from the shared style table"""
table = {}
xmlns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
root = fromstring(xml_source)
custom_num_formats = parse_custom_num_formats(root, xmlns)
builtin_formats = NumberFormat._BUILTIN_FORMATS
cell_xfs = root.find(QName(xmlns, 'cellXfs').text)
cell_xfs_nodes = cell_xfs.findall(QName(xmlns, 'xf').text)
for index, cell_xfs_node in enumerate(cell_xfs_nodes):
new_style = Style()
number_format_id = int(cell_xfs_node.get('numFmtId'))
if number_format_id < 164:
new_style.number_format.format_code = \
builtin_formats.get(number_format_id, 'General')
else:
if number_format_id in custom_num_formats:
new_style.number_format.format_code = \
custom_num_formats[number_format_id]
else:
raise MissingNumberFormat('%s' % number_format_id)
table[index] = new_style
return table
def parse_custom_num_formats(root, xmlns):
"""Read in custom numeric formatting rules from the shared style table"""
custom_formats = {}
num_fmts = root.find(QName(xmlns, 'numFmts').text)
if num_fmts is not None:
num_fmt_nodes = num_fmts.findall(QName(xmlns, 'numFmt').text)
for num_fmt_node in num_fmt_nodes:
custom_formats[int(num_fmt_node.get('numFmtId'))] = \
num_fmt_node.get('formatCode')
return custom_formats
+156
View File
@@ -0,0 +1,156 @@
# file openpyxl/reader/workbook.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Read in global settings to be maintained by the workbook object."""
# package imports
from ..shared.xmltools import fromstring, QName
from ..shared.ooxml import NAMESPACES
from ..workbook import DocumentProperties
from ..shared.date_time import W3CDTF_to_datetime
from ..namedrange import NamedRange, split_named_range
import datetime
# constants
BUGGY_NAMED_RANGES = ['NA()', '#REF!']
DISCARDED_RANGES = ['Excel_BuiltIn', 'Print_Area']
def get_sheet_ids(xml_source):
sheet_names = read_sheets_titles(xml_source)
return dict((sheet, 'sheet%d.xml' % (i + 1)) for i, sheet in enumerate(sheet_names))
def read_properties_core(xml_source):
"""Read assorted file properties."""
properties = DocumentProperties()
root = fromstring(xml_source)
creator_node = root.find(QName(NAMESPACES['dc'], 'creator').text)
if creator_node is not None:
properties.creator = creator_node.text
else:
properties.creator = ''
last_modified_by_node = root.find(
QName(NAMESPACES['cp'], 'lastModifiedBy').text)
if last_modified_by_node is not None:
properties.last_modified_by = last_modified_by_node.text
else:
properties.last_modified_by = ''
created_node = root.find(QName(NAMESPACES['dcterms'], 'created').text)
if created_node is not None:
properties.created = W3CDTF_to_datetime(created_node.text)
else:
properties.created = datetime.datetime.now()
modified_node = root.find(QName(NAMESPACES['dcterms'], 'modified').text)
if modified_node is not None:
properties.modified = W3CDTF_to_datetime(modified_node.text)
else:
properties.modified = properties.created
return properties
def get_number_of_parts(xml_source):
"""Get a list of contents of the workbook."""
parts_size = {}
parts_names = []
root = fromstring(xml_source)
heading_pairs = root.find(QName('http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
'HeadingPairs').text)
vector = heading_pairs.find(QName(NAMESPACES['vt'], 'vector').text)
children = vector.getchildren()
for child_id in range(0, len(children), 2):
part_name = children[child_id].find(QName(NAMESPACES['vt'],
'lpstr').text).text
if not part_name in parts_names:
parts_names.append(part_name)
part_size = int(children[child_id + 1].find(QName(
NAMESPACES['vt'], 'i4').text).text)
parts_size[part_name] = part_size
return parts_size, parts_names
def read_sheets_titles(xml_source):
"""Read titles for all sheets."""
root = fromstring(xml_source)
titles_root = root.find(QName('http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
'TitlesOfParts').text)
vector = titles_root.find(QName(NAMESPACES['vt'], 'vector').text)
parts, names = get_number_of_parts(xml_source)
# we can't assume 'Worksheets' to be written in english,
# but it's always the first item of the parts list (see bug #22)
size = parts[names[0]]
children = [c.text for c in vector.getchildren()]
return children[:size]
def read_named_ranges(xml_source, workbook):
"""Read named ranges, excluding poorly defined ranges."""
named_ranges = []
root = fromstring(xml_source)
names_root = root.find(QName('http://schemas.openxmlformats.org/spreadsheetml/2006/main',
'definedNames').text)
if names_root is not None:
for name_node in names_root.getchildren():
range_name = name_node.get('name')
if name_node.get("hidden", '0') == '1':
continue
valid = True
for discarded_range in DISCARDED_RANGES:
if discarded_range in range_name:
valid = False
for bad_range in BUGGY_NAMED_RANGES:
if bad_range in name_node.text:
valid = False
if valid:
destinations = split_named_range(name_node.text)
new_destinations = []
for worksheet, cells_range in destinations:
# it can happen that a valid named range references
# a missing worksheet, when Excel didn't properly maintain
# the named range list
#
# we just ignore them here
worksheet = workbook.get_sheet_by_name(worksheet)
if worksheet:
new_destinations.append((worksheet, cells_range))
named_range = NamedRange(range_name, new_destinations)
named_ranges.append(named_range)
return named_ranges
@@ -0,0 +1,114 @@
# file openpyxl/reader/worksheet.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Reader for a single worksheet."""
# Python stdlib imports
try:
from xml.etree.cElementTree import iterparse
except ImportError:
from xml.etree.ElementTree import iterparse
from ....compat import ifilter
from ....compat import BytesIO as StringIO
# package imports
from ..cell import Cell, coordinate_from_string
from ..worksheet import Worksheet
def _get_xml_iter(xml_source):
if not hasattr(xml_source, 'name'):
return StringIO(xml_source)
else:
xml_source.seek(0)
return xml_source
def read_dimension(xml_source):
source = _get_xml_iter(xml_source)
it = iterparse(source)
for event, element in it:
if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}dimension':
ref = element.get('ref')
min_range, max_range = ref.split(':')
min_col, min_row = coordinate_from_string(min_range)
max_col, max_row = coordinate_from_string(max_range)
return min_col, min_row, max_col, max_row
else:
element.clear()
return None
def filter_cells(x):
(event, element) = x
return element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c'
def fast_parse(ws, xml_source, string_table, style_table):
source = _get_xml_iter(xml_source)
it = iterparse(source)
for event, element in ifilter(filter_cells, it):
value = element.findtext('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v')
if value is not None:
coordinate = element.get('r')
data_type = element.get('t', 'n')
style_id = element.get('s')
if data_type == Cell.TYPE_STRING:
value = string_table.get(int(value))
ws.cell(coordinate).value = value
if style_id is not None:
ws._styles[coordinate] = style_table.get(int(style_id))
# to avoid memory exhaustion, clear the item after use
element.clear()
from ..reader.iter_worksheet import IterableWorksheet
def read_worksheet(xml_source, parent, preset_title, string_table,
style_table, workbook_name = None, sheet_codename = None):
"""Read an xml worksheet"""
if workbook_name and sheet_codename:
ws = IterableWorksheet(parent, preset_title, workbook_name,
sheet_codename, xml_source)
else:
ws = Worksheet(parent, preset_title)
fast_parse(ws, xml_source, string_table, style_table)
return ws
@@ -0,0 +1,33 @@
# file openpyxl/shared/__init__.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Imports for the openpyxl.shared namespace."""
# package imports
from . import date_time
from . import exc
from . import ooxml
from . import password_hasher
from . import xmltools
@@ -0,0 +1,154 @@
# file openpyxl/shared/date_time.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Manage Excel date weirdness."""
# Python stdlib imports
from __future__ import division
from math import floor
import calendar
import datetime
import time
import re
# constants
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.
"""
CALENDAR_WINDOWS_1900 = 1900
CALENDAR_MAC_1904 = 1904
datetime_object_type = 'DateTime'
def __init__(self):
self.excel_base_date = self.CALENDAR_WINDOWS_1900
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)
elif isinstance(date, datetime.date):
return self.to_julian(date.year, date.month, date.day)
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 == self.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
else:
raise NotImplementedError('Mac dates are not yet supported.')
#excel_base_date = 2416481
#excel_1900_leap_year = False
# 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 == self.CALENDAR_WINDOWS_1900 \
and excel_date == 60:
msg = 'Error: Excel believes 1900 was a leap year'
raise ValueError(msg)
excel_time = ((hours * 3600) + (minutes * 60) + seconds) / 86400
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 == self.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)
else:
raise NotImplementedError('Mac dates are not yet supported.')
#excel_base_date = 24107
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)
+59
View File
@@ -0,0 +1,59 @@
# file openpyxl/shared/exc.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""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"""
+60
View File
@@ -0,0 +1,60 @@
# file openpyxl/shared/ooxml.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""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'
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'
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'
}
@@ -0,0 +1,47 @@
# file openpyxl/shared/password_hasher.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""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 <xnoguer@rezebra.com>.
"""
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:]
+67
View File
@@ -0,0 +1,67 @@
# file openpyxl/shared/units.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
import math
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
+114
View File
@@ -0,0 +1,114 @@
# file openpyxl/shared/xmltools.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""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 xml.sax.xmlreader import AttributesNSImpl
from xml.sax.saxutils import XMLGenerator
try:
from xml.etree.ElementTree import ElementTree, Element, SubElement, \
QName, fromstring, tostring
except ImportError:
from cElementTree import ElementTree, Element, SubElement, \
QName, fromstring, tostring
# package imports
from .. import __name__ as prefix
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 = {}
# name = bytes(name, 'utf-8')
# if namespace is not None:
# namespace = bytes(namespace, 'utf-8')
attr_vals = {}
attr_keys = {}
for key, val in attr.items():
# if key is not None:
# key = bytes(key, 'utf-8')
# if val is not None:
# val = bytes(val, 'utf-8')
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)
+392
View File
@@ -0,0 +1,392 @@
# file openpyxl/style.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Style and formatting option tracking."""
# Python stdlib imports
import re
try:
from hashlib import md5
except ImportError:
from md5 import md5
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_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'
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
return any([x in format for x in self.DATE_INDICATORS])
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):
super(Style, self).__init__()
self.font = Font()
self.fill = Fill()
self.borders = Borders()
self.alignment = Alignment()
self.number_format = NumberFormat()
self.protection = Protection()
DEFAULTS = Style()
+186
View File
@@ -0,0 +1,186 @@
# file openpyxl/workbook.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Workbook is the top-level container for all document information."""
__docformat__ = "restructuredtext en"
# Python stdlib imports
import datetime
import os
# package imports
from .worksheet import Worksheet
from .writer.dump_worksheet import DumpWorksheet, save_dump
from .writer.strings import StringTableBuilder
from .namedrange import NamedRange
from .style import Style
from .writer.excel import save_workbook
from .shared.exc import ReadOnlyWorkbookException
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'
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):
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.strings_table_builder = StringTableBuilder()
if not optimized_write:
self.worksheets.append(Worksheet(self))
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):
"""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 = DumpWorksheet(parent_workbook = self)
else:
new_ws = Worksheet(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)."""
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):
"""Create a new named_range on a worksheet"""
assert isinstance(worksheet, Worksheet)
named_range = NamedRange(name, [(worksheet, range)])
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):
""" shortcut """
if self.__optimized_write:
save_dump(self, filename)
else:
save_workbook(self, filename)
+534
View File
@@ -0,0 +1,534 @@
# file openpyxl/worksheet.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Worksheet is the 2nd-level container in Excel."""
# Python stdlib imports
import re
# package imports
from . import cell
from .cell import coordinate_from_string, \
column_index_from_string, get_column_letter
from .shared.exc import SheetTitleException, \
InsufficientCoordinatesException, CellCoordinatesException, \
NamedRangeException
from .shared.password_hasher import hash_password
from .style import Style, DEFAULTS as DEFAULTS_STYLE
from .drawing import Drawing
_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',
#'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"""
pass
class HeaderFooter(object):
"""Information about the header/footer for this sheet."""
pass
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."""
def __init__(self):
self.left = self.right = 0.7
self.top = self.bottom = 0.75
self.header = self.footer = 0.3
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
"""
BREAK_NONE = 0
BREAK_ROW = 1
BREAK_COLUMN = 2
SHEETSTATE_VISIBLE = 'visible'
SHEETSTATE_HIDDEN = 'hidden'
SHEETSTATE_VERYHIDDEN = 'veryHidden'
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._cells = {}
self._styles = {}
self._charts = []
self.relationships = []
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
def __repr__(self):
return '<Worksheet "%s">' % self.title
def garbage_collect(self):
"""Delete cells that are not storing a value."""
delete_list = [coordinate for coordinate, cell in \
self._cells.items() if (cell.value in ('', None) and \
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 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 = 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)
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()
return self._styles[coordinate]
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_chart(self, chart):
""" Add a chart to the sheet """
chart._sheet = self
self._charts.append(chart)
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 list_or_dict.items():
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)
@@ -0,0 +1,34 @@
# file openpyxl/writer/__init__.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Imports for the openpyxl.writer namespace."""
# package imports
from . import excel
from . import strings
from . import styles
from . import theme
from . import workbook
from . import worksheet
+261
View File
@@ -0,0 +1,261 @@
# coding=UTF-8
'''
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.
@license: http://www.opensource.org/licenses/mit-license.php
@author: Eric Gazoni
'''
from ..shared.xmltools import Element, SubElement, get_document_content
from ..chart import Chart, ErrorBar
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(axis.max)})
SubElement(scaling, 'c:min', {'val':str(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'})
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(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.xvalues)
yval = SubElement(ser, 'c:yVal')
self._write_serial(yval, serie.values)
else:
val = SubElement(ser, 'c:val')
self._write_serial(val, serie.values)
def _write_serial(self, node, serie, literal=False):
cache = serie._get_cache()
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 = serie._get_ref()
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)})
SubElement(point, 'c:v').text = str(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):
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')
margins = dict([(k, str(v)) for (k,v) in self.chart.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)
+192
View File
@@ -0,0 +1,192 @@
# coding=UTF-8
'''
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.
@license: http://www.opensource.org/licenses/mit-license.php
@author: Eric Gazoni
'''
from ..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')
return get_document_content(root)
def write_rels(self, chart_id):
root = Element('Relationships',
{'xmlns' : 'http://schemas.openxmlformats.org/package/2006/relationships'})
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)
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'})
@@ -0,0 +1,256 @@
# file openpyxl/writer/straight_worksheet.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Write worksheets to xml representations in an optimized way"""
import datetime
import os
from ..cell import column_index_from_string, get_column_letter, Cell
from ..worksheet import Worksheet
from ..shared.xmltools import XMLGenerator, get_document_content, \
start_tag, end_tag, tag
from ..shared.date_time import SharedDate
from ..shared.ooxml import MAX_COLUMN, MAX_ROW
from tempfile import NamedTemporaryFile
from ..writer.excel import ExcelWriter
from ..writer.strings import write_string_table
from ..writer.styles import StyleWriter
from ..style import Style, NumberFormat
from ..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, \
PACKAGE_WORKSHEETS, PACKAGE_DRAWINGS, PACKAGE_CHARTS
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'},
}
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)
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):
Worksheet.__init__(self, parent_workbook)
self._max_col = 0
self._max_row = 0
self._parent = parent_workbook
self._fileobj_header = NamedTemporaryFile(mode='r+', prefix='openpyxl.', suffix='.header', delete=False)
self._fileobj_content = NamedTemporaryFile(mode='r+', prefix='openpyxl.', suffix='.content', delete=False)
self._fileobj = NamedTemporaryFile(mode='w', prefix='openpyxl.', delete=False)
self.doc = XMLGenerator(self._fileobj_content, 'utf-8')
self.header = XMLGenerator(self._fileobj_header, 'utf-8')
self.title = 'Sheet'
self._shared_date = SharedDate()
self._string_builder = self._parent.strings_table_builder
@property
def filename(self):
return self._fileobj.name
def write_header(self):
doc = self.header
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._close_header()
self._write_fileobj(self._fileobj_header)
self._write_fileobj(self._fileobj_content)
self._fileobj.close()
def _write_fileobj(self, fobj):
fobj.flush()
fobj.seek(0)
while True:
chunk = fobj.read(4096)
if not chunk:
break
self._fileobj.write(chunk)
fobj.close()
os.remove(fobj.name)
self._fileobj.flush()
def _close_header(self):
doc = self.header
#doc.endDocument()
def _close_content(self):
doc = self.doc
end_tag(doc, 'sheetData')
end_tag(doc, 'worksheet')
#doc.endDocument()
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 append(self, row):
"""
:param row: iterable containing values to append
:type row: iterable
"""
doc = self.doc
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, (int, float)):
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)
attributes['t'] = STYLES[dtype]['type']
start_tag(doc, 'c', attributes)
if dtype == 'formula':
tag(doc, 'f', body = '%s' % cell[1:])
tag(doc, 'v')
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))
os.remove(sheet.filename)
class StyleDumpWriter(StyleWriter):
def _get_style_list(self, workbook):
return []
+161
View File
@@ -0,0 +1,161 @@
# file openpyxl/writer/excel.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Write a .xlsx file."""
# Python stdlib imports
from zipfile import ZipFile, ZIP_DEFLATED
from ....compat import BytesIO as StringIO
# package imports
from ..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, \
PACKAGE_WORKSHEETS, PACKAGE_DRAWINGS, PACKAGE_CHARTS
from ..writer.strings import create_string_table, write_string_table
from ..writer.workbook import write_content_types, write_root_rels, \
write_workbook_rels, write_properties_app, write_properties_core, \
write_workbook
from ..writer.theme import write_theme
from ..writer.styles import StyleWriter
from ..writer.drawings import DrawingWriter, ShapeWriter
from ..writer.charts import ChartWriter
from ..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))
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))
archive.writestr(ARC_THEME, write_theme())
archive.writestr(ARC_STYLE, self.style_writer.write_table())
archive.writestr(ARC_WORKBOOK, write_workbook(self.workbook))
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))
for k, v in shared_string_table.items():
shared_string_table[k] = bytes(v)
return shared_string_table
def _write_worksheets(self, archive, shared_string_table, style_writer):
drawing_id = 1
chart_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.relationships:
archive.writestr(PACKAGE_WORKSHEETS +
'/_rels/sheet%d.xml.rels' % (i + 1),
write_worksheet_rels(sheet, drawing_id))
if sheet._charts:
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))
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
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 = StringIO()
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
@@ -0,0 +1,86 @@
# file openpyxl/writer/strings.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Write the shared string table."""
# Python stdlib imports
from ....compat import BytesIO as StringIO
# package imports
from ..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(strings))
def write_string_table(string_table):
"""Write the string table xml."""
temp_buffer = StringIO()
doc = XMLGenerator(temp_buffer, '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
+256
View File
@@ -0,0 +1,256 @@
# file openpyxl/writer/styles.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Write the shared style table."""
# package imports
from ..shared.xmltools import Element, SubElement
from ..shared.xmltools import get_document_content
from .. 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:
for style in worksheet._styles.values():
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)})
SubElement(font_node, 'color', {'rgb':str(st.font.color.index)})
SubElement(font_node, 'name', {'val':st.font.name})
SubElement(font_node, 'family', {'val':'2'})
SubElement(font_node, 'scheme', {'val':'minor'})
if st.font.bold:
SubElement(font_node, 'b')
if st.font.italic:
SubElement(font_node, 'i')
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):
SubElement(node, 'fgColor', {'rgb':str(st.fill.start_color.index)})
if hash(st.fill.end_color) != hash(style.DEFAULTS.fill.end_color):
SubElement(node, 'bgColor', {'rgb':str(st.fill.start_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)
node = SubElement(border, side, {'style':obj.border_style})
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['applyFillId'] = '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)
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
+202
View File
@@ -0,0 +1,202 @@
# -*- coding: utf-8 -*-
# file openpyxl/writer/theme.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Write the theme xml based on a fixed string."""
# package imports
from ..shared.xmltools import fromstring, get_document_content
def write_theme():
"""Write the theme xml."""
xml_node = fromstring(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
'<a:theme xmlns:a="http://schemas.openxmlformats.org/'
'drawingml/2006/main" name="Office Theme">'
'<a:themeElements>'
'<a:clrScheme name="Office">'
'<a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>'
'<a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>'
'<a:dk2><a:srgbClr val="1F497D"/></a:dk2>'
'<a:lt2><a:srgbClr val="EEECE1"/></a:lt2>'
'<a:accent1><a:srgbClr val="4F81BD"/></a:accent1>'
'<a:accent2><a:srgbClr val="C0504D"/></a:accent2>'
'<a:accent3><a:srgbClr val="9BBB59"/></a:accent3>'
'<a:accent4><a:srgbClr val="8064A2"/></a:accent4>'
'<a:accent5><a:srgbClr val="4BACC6"/></a:accent5>'
'<a:accent6><a:srgbClr val="F79646"/></a:accent6>'
'<a:hlink><a:srgbClr val="0000FF"/></a:hlink>'
'<a:folHlink><a:srgbClr val="800080"/></a:folHlink>'
'</a:clrScheme>'
'<a:fontScheme name="Office">'
'<a:majorFont>'
'<a:latin typeface="Cambria"/>'
'<a:ea typeface=""/>'
'<a:cs typeface=""/>'
'<a:font script="Jpan" typeface="MS Pゴシック"/>'
'<a:font script="Hang" typeface="맑은 고딕"/>'
'<a:font script="Hans" typeface="宋体"/>'
'<a:font script="Hant" typeface="新細明體"/>'
'<a:font script="Arab" typeface="Times New Roman"/>'
'<a:font script="Hebr" typeface="Times New Roman"/>'
'<a:font script="Thai" typeface="Tahoma"/>'
'<a:font script="Ethi" typeface="Nyala"/>'
'<a:font script="Beng" typeface="Vrinda"/>'
'<a:font script="Gujr" typeface="Shruti"/>'
'<a:font script="Khmr" typeface="MoolBoran"/>'
'<a:font script="Knda" typeface="Tunga"/>'
'<a:font script="Guru" typeface="Raavi"/>'
'<a:font script="Cans" typeface="Euphemia"/>'
'<a:font script="Cher" typeface="Plantagenet Cherokee"/>'
'<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>'
'<a:font script="Tibt" typeface="Microsoft Himalaya"/>'
'<a:font script="Thaa" typeface="MV Boli"/>'
'<a:font script="Deva" typeface="Mangal"/>'
'<a:font script="Telu" typeface="Gautami"/>'
'<a:font script="Taml" typeface="Latha"/>'
'<a:font script="Syrc" typeface="Estrangelo Edessa"/>'
'<a:font script="Orya" typeface="Kalinga"/>'
'<a:font script="Mlym" typeface="Kartika"/>'
'<a:font script="Laoo" typeface="DokChampa"/>'
'<a:font script="Sinh" typeface="Iskoola Pota"/>'
'<a:font script="Mong" typeface="Mongolian Baiti"/>'
'<a:font script="Viet" typeface="Times New Roman"/>'
'<a:font script="Uigh" typeface="Microsoft Uighur"/>'
'</a:majorFont>'
'<a:minorFont>'
'<a:latin typeface="Calibri"/>'
'<a:ea typeface=""/>'
'<a:cs typeface=""/>'
'<a:font script="Jpan" typeface="MS Pゴシック"/>'
'<a:font script="Hang" typeface="맑은 고딕"/>'
'<a:font script="Hans" typeface="宋体"/>'
'<a:font script="Hant" typeface="新細明體"/>'
'<a:font script="Arab" typeface="Arial"/>'
'<a:font script="Hebr" typeface="Arial"/>'
'<a:font script="Thai" typeface="Tahoma"/>'
'<a:font script="Ethi" typeface="Nyala"/>'
'<a:font script="Beng" typeface="Vrinda"/>'
'<a:font script="Gujr" typeface="Shruti"/>'
'<a:font script="Khmr" typeface="DaunPenh"/>'
'<a:font script="Knda" typeface="Tunga"/>'
'<a:font script="Guru" typeface="Raavi"/>'
'<a:font script="Cans" typeface="Euphemia"/>'
'<a:font script="Cher" typeface="Plantagenet Cherokee"/>'
'<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>'
'<a:font script="Tibt" typeface="Microsoft Himalaya"/>'
'<a:font script="Thaa" typeface="MV Boli"/>'
'<a:font script="Deva" typeface="Mangal"/>'
'<a:font script="Telu" typeface="Gautami"/>'
'<a:font script="Taml" typeface="Latha"/>'
'<a:font script="Syrc" typeface="Estrangelo Edessa"/>'
'<a:font script="Orya" typeface="Kalinga"/>'
'<a:font script="Mlym" typeface="Kartika"/>'
'<a:font script="Laoo" typeface="DokChampa"/>'
'<a:font script="Sinh" typeface="Iskoola Pota"/>'
'<a:font script="Mong" typeface="Mongolian Baiti"/>'
'<a:font script="Viet" typeface="Arial"/>'
'<a:font script="Uigh" typeface="Microsoft Uighur"/>'
'</a:minorFont>'
'</a:fontScheme>'
'<a:fmtScheme name="Office">'
'<a:fillStyleLst>'
'<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>'
'<a:gradFill rotWithShape="1"><a:gsLst>'
'<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="50000"/>'
'<a:satMod val="300000"/></a:schemeClr></a:gs>'
'<a:gs pos="35000"><a:schemeClr val="phClr"><a:tint val="37000"/>'
'<a:satMod val="300000"/></a:schemeClr></a:gs>'
'<a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="15000"/>'
'<a:satMod val="350000"/></a:schemeClr></a:gs></a:gsLst>'
'<a:lin ang="16200000" scaled="1"/></a:gradFill>'
'<a:gradFill rotWithShape="1"><a:gsLst>'
'<a:gs pos="0"><a:schemeClr val="phClr"><a:shade val="51000"/>'
'<a:satMod val="130000"/></a:schemeClr></a:gs>'
'<a:gs pos="80000"><a:schemeClr val="phClr"><a:shade val="93000"/>'
'<a:satMod val="130000"/></a:schemeClr></a:gs>'
'<a:gs pos="100000"><a:schemeClr val="phClr">'
'<a:shade val="94000"/>'
'<a:satMod val="135000"/></a:schemeClr></a:gs></a:gsLst>'
'<a:lin ang="16200000" scaled="0"/></a:gradFill></a:fillStyleLst>'
'<a:lnStyleLst>'
'<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr">'
'<a:solidFill><a:schemeClr val="phClr"><a:shade val="95000"/>'
'<a:satMod val="105000"/></a:schemeClr></a:solidFill>'
'<a:prstDash val="solid"/></a:ln>'
'<a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill>'
'<a:schemeClr val="phClr"/></a:solidFill>'
'<a:prstDash val="solid"/></a:ln>'
'<a:ln w="38100" cap="flat" cmpd="sng" algn="ctr"><a:solidFill>'
'<a:schemeClr val="phClr"/></a:solidFill>'
'<a:prstDash val="solid"/></a:ln></a:lnStyleLst>'
'<a:effectStyleLst><a:effectStyle><a:effectLst>'
'<a:outerShdw blurRad="40000" dist="20000" dir="5400000" '
'rotWithShape="0"><a:srgbClr val="000000">'
'<a:alpha val="38000"/></a:srgbClr></a:outerShdw></a:effectLst>'
'</a:effectStyle><a:effectStyle><a:effectLst>'
'<a:outerShdw blurRad="40000" dist="23000" dir="5400000" '
'rotWithShape="0"><a:srgbClr val="000000">'
'<a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst>'
'</a:effectStyle><a:effectStyle><a:effectLst>'
'<a:outerShdw blurRad="40000" dist="23000" dir="5400000" '
'rotWithShape="0"><a:srgbClr val="000000">'
'<a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst>'
'<a:scene3d><a:camera prst="orthographicFront">'
'<a:rot lat="0" lon="0" rev="0"/></a:camera>'
'<a:lightRig rig="threePt" dir="t">'
'<a:rot lat="0" lon="0" rev="1200000"/></a:lightRig>'
'</a:scene3d><a:sp3d><a:bevelT w="63500" h="25400"/>'
'</a:sp3d></a:effectStyle></a:effectStyleLst>'
'<a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/>'
'</a:solidFill><a:gradFill rotWithShape="1"><a:gsLst>'
'<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="40000"/>'
'<a:satMod val="350000"/></a:schemeClr></a:gs>'
'<a:gs pos="40000"><a:schemeClr val="phClr"><a:tint val="45000"/>'
'<a:shade val="99000"/><a:satMod val="350000"/>'
'</a:schemeClr></a:gs>'
'<a:gs pos="100000"><a:schemeClr val="phClr">'
'<a:shade val="20000"/><a:satMod val="255000"/>'
'</a:schemeClr></a:gs></a:gsLst>'
'<a:path path="circle">'
'<a:fillToRect l="50000" t="-80000" r="50000" b="180000"/>'
'</a:path>'
'</a:gradFill><a:gradFill rotWithShape="1"><a:gsLst>'
'<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="80000"/>'
'<a:satMod val="300000"/></a:schemeClr></a:gs>'
'<a:gs pos="100000"><a:schemeClr val="phClr">'
'<a:shade val="30000"/><a:satMod val="200000"/>'
'</a:schemeClr></a:gs></a:gsLst>'
'<a:path path="circle">'
'<a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path>'
'</a:gradFill></a:bgFillStyleLst></a:fmtScheme>'
'</a:themeElements>'
'<a:objectDefaults/><a:extraClrSchemeLst/>'
'</a:theme>')
return get_document_content(xml_node)
+204
View File
@@ -0,0 +1,204 @@
# file openpyxl/writer/workbook.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Write the workbook global settings to the archive."""
# package imports
from ..shared.xmltools import Element, SubElement
from ..cell import absolute_coordinate
from ..shared.xmltools import get_document_content
from ..shared.ooxml import NAMESPACES, ARC_CORE, ARC_WORKBOOK, \
ARC_APP, ARC_THEME, ARC_STYLE, ARC_SHARED_STRINGS
from ..shared.date_time import datetime_to_W3CDTF
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)
return get_document_content(root)
def write_content_types(workbook):
"""Write the content-types xml."""
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, '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):
SubElement(root, 'Override',
{'PartName': '/xl/worksheets/sheet%d.xml' % (sheet_id + 1),
'ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml'})
if sheet._charts:
SubElement(root, 'Override',
{'PartName' : '/xl/drawings/drawing%d.xml' % (sheet_id + 1),
'ContentType' : 'application/vnd.openxmlformats-officedocument.drawing+xml'})
drawing_id += 1
for chart in sheet._charts:
SubElement(root, 'Override',
{'PartName' : '/xl/charts/chart%d.xml' % chart_id,
'ContentType' : 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml'})
chart_id += 1
if chart._shapes:
SubElement(root, 'Override',
{'PartName' : '/xl/drawings/drawing%d.xml' % drawing_id,
'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)
# named ranges
defined_names = SubElement(root, 'definedNames')
for named_range in workbook.get_named_ranges():
name = SubElement(defined_names, 'definedName',
{'name': named_range.name})
# as there can be many cells in one range, generate the list of ranges
dest_cells = []
cell_ids = []
for worksheet, range_name in named_range.destinations:
cell_ids.append(workbook.get_index(worksheet))
dest_cells.append("'%s'!%s" % (worksheet.title.replace("'", "''"),
absolute_coordinate(range_name)))
# for local ranges, we must check all the cells belong to the same sheet
base_id = cell_ids[0]
if named_range.local_only and all([x == base_id for x in cell_ids]):
name.set('localSheetId', '%s' % base_id)
# finally write the cells list
name.text = ','.join(dest_cells)
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'})
return get_document_content(root)
@@ -0,0 +1,209 @@
# file openpyxl/writer/worksheet.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Write worksheets to xml representations."""
# Python stdlib imports
from ....compat import BytesIO as StringIO # cStringIO doesn't handle unicode
# package imports
from ..cell import coordinate_from_string, column_index_from_string
from ..shared.xmltools import Element, SubElement, XMLGenerator, \
get_document_content, start_tag, end_tag, tag
def row_sort(cell):
"""Translate column names for sorting."""
return column_index_from_string(cell.column)
def write_worksheet(worksheet, string_table, style_table):
"""Write a worksheet to an xml file."""
xml_file = StringIO()
doc = XMLGenerator(xml_file, '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': '%d' % (worksheet.show_summary_below),
'summaryRight': '%d' % (worksheet.show_summary_right)})
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_hyperlinks(doc, worksheet)
if worksheet._charts:
tag(doc, 'drawing', {'r:id':'rId1'})
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 \
worksheet.column_dimensions.items():
col_index = column_index_from_string(column_string)
col_def = {}
col_def['collapsed'] = str(columndimension.style_index)
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.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 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 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}
attributes['t'] = cell.data_type
if coordinate in worksheet._styles:
attributes['s'] = '%d' % style_id_by_hash[
hash(worksheet._styles[coordinate])]
start_tag(doc, 'c', attributes)
if value is None:
tag(doc, 'v', body='')
elif 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:
tag(doc, 'v', body = '%s' % value)
else:
tag(doc, 'v', body = '%s' % value)
end_tag(doc, 'c')
end_tag(doc, 'row')
end_tag(doc, 'sheetData')
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:
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)
+53
View File
@@ -0,0 +1,53 @@
# file openpyxl/__init__.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Imports for the openpyxl package."""
# package imports
from . import cell
from . import namedrange
from . import style
from . import workbook
from . import worksheet
from . import reader
from . import shared
from . import writer
# constants
__major__ = 1 # for major interface/format changes
__minor__ = 5 # for minor interface/format changes
__release__ = 2 # 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://bitbucket.org/ericgazoni/openpyxl/wiki/Home'
__downloadUrl__ = "http://bitbucket.org/ericgazoni/openpyxl/downloads"
__all__ = ('reader', 'shared', 'writer',)
+384
View File
@@ -0,0 +1,384 @@
# file openpyxl/cell.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""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
import datetime
import re
# package imports
from .shared.date_time import SharedDate
from .shared.exc import CellCoordinatesException, \
ColumnStringIndexException, DataTypeException
from .style import NumberFormat
# 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()
return (column, int(row))
def absolute_coordinate(coord_string):
"""Convert a coordinate to an absolute coordinate string (B12 -> $B$12)"""
parts = ABSOLUTE_RE.match(coord_string).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])
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')
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'
VALID_TYPES = [TYPE_STRING, TYPE_FORMULA, TYPE_NUMERIC, TYPE_BOOL,
TYPE_NULL, TYPE_INLINE, TYPE_ERROR]
RE_PATTERNS = {
'percentage': re.compile('^\-?[0-9]*\.?[0-9]*\s?\%$'),
'time': re.compile('^(\d|[0-1]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$'),
'numeric': re.compile('^\-?([0-9]+\\.?[0-9]*|[0-9]*\\.?[0-9]+)((E|e)\-?[0-9]+)?$'), }
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
def __repr__(self):
return "<Cell %s.%s>" % (self.parent.title, self.get_coordinate())
def check_string(self, value):
"""Check string coding, length, and line break character"""
# convert to unicode string
value = str(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, (int, float)):
try:
value = int(value)
except ValueError:
value = float(value)
return value
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: str,
self.TYPE_NUMERIC: self.check_numeric,
self.TYPE_BOOL: bool, }
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, (int, float)):
data_type = self.TYPE_NUMERIC
elif not value:
data_type = self.TYPE_STRING
elif isinstance(value, (datetime.datetime, datetime.date)):
data_type = self.TYPE_NUMERIC
elif isinstance(value, str) and value[0] == '=':
data_type = self.TYPE_FORMULA
elif self.RE_PATTERNS['numeric'].match(value):
data_type = self.TYPE_NUMERIC
elif value.strip() in self.ERROR_CODES:
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
percentage_search = self.RE_PATTERNS['percentage'].match(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
time_search = self.RE_PATTERNS['time'].match(value)
if time_search:
sep_count = value.count(':') #pylint: disable-msg=E1103
if sep_count == 1:
hours, minutes = [int(bit) for bit in value.split(':')] #pylint: disable-msg=E1103
seconds = 0
elif sep_count == 2:
hours, minutes, seconds = \
[int(bit) for bit in value.split(':')] #pylint: disable-msg=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):
value = SharedDate().datetime_to_julian(date = value)
self.set_value_explicit(value, self.TYPE_NUMERIC)
self._set_number_format(NumberFormat.FORMAT_DATE_YYYYMMDD2)
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 = SharedDate().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-msg=W0212
@property
def style(self):
"""Returns the :class:`.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:`.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, (int, float)))
+340
View File
@@ -0,0 +1,340 @@
'''
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.
@license: http://www.opensource.org/licenses/mit-license.php
@author: Eric Gazoni
'''
import math
from .style import NumberFormat
from .drawing import Drawing, Shape
from .shared.units import pixels_to_EMU, short_color
from .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
@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.cache[0], str):
return 'str'
else:
return 'num'
def _get_ref(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):
""" read data in sheet - to be used at writing time """
cache = []
if self.pos2:
for row in range(self.pos1[0], self.pos2[0]+1):
for col in range(self.pos1[1], self.pos2[1]+1):
cache.append(self.sheet.cell(row=row, column=col).value)
else:
cell = self.sheet.cell(row=self.pos1[0], column=self.pos1[1])
cache.append(cell.value)
return cache
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
self._color = 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_min_max(self):
if self.error_bar:
err_cache = self.error_bar.values._get_cache()
vals = [v + err_cache[i] \
for i,v in enumerate(self.values._get_cache())]
else:
vals = self.values._get_cache()
return min(vals), max(vals)
def __len__(self):
return len(self.values.cache)
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
class Chart(object):
""" raw chart class """
GROUPING_CLUSTERED = 'clustered'
GROUPING_STANDARD = 'standard'
BAR_CHART = 1
LINE_CHART = 2
SCATTER_CHART = 3
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.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 max([len(s.values._get_cache()) 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([max(s.values._get_cache()) for s in self._series])
return len(str(int(_max)))
def _compute_min_max(self):
""" compute y axis limits and units """
maxi = max([max(s.values._get_cache()) 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.y_axis.max = maxi
self.y_axis.unit = unit
def _compute_xmin_xmax(self):
""" compute x axis limits and units """
maxi = max([max(s.xvalues._get_cache()) 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)
+402
View File
@@ -0,0 +1,402 @@
'''
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.
@license: http://www.opensource.org/licenses/mit-license.php
@author: Eric Gazoni
'''
import math
from .style import Color
from .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 = width
else:
self._width = math.ceil(yratio * self._width)
self._height = height
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)
print(self._border_width)
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))
+68
View File
@@ -0,0 +1,68 @@
# file openpyxl/namedrange.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Track named groups of cells in a worksheet"""
# Python stdlib imports
import re
# package imports
from .shared.exc import NamedRangeException
# constants
NAMED_RANGE_RE = re.compile("'?([^']*)'?!((\$([A-Za-z]+))?\$([0-9]+)(:(\$([A-Za-z]+))?(\$([0-9]+)))?)$")
class NamedRange(object):
"""A named group of cells"""
__slots__ = ('name', 'destinations', 'local_only')
def __init__(self, name, destinations):
self.name = name
self.destinations = destinations
self.local_only = False
def __str__(self):
return ','.join(['%s!%s' % (sheet, name) for sheet, name in self.destinations])
def __repr__(self):
return '<%s "%s">' % (self.__class__.__name__, str(self))
def split_named_range(range_string):
"""Separate a named range into its component parts"""
destinations = []
for range_string in range_string.split(','):
match = NAMED_RANGE_RE.match(range_string)
if not match:
raise NamedRangeException('Invalid named range string: "%s"' % range_string)
else:
sheet_name, xlrange = match.groups()[:2]
destinations.append((sheet_name, xlrange))
return destinations
@@ -0,0 +1,33 @@
# file openpyxl/reader/__init__.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Imports for the openpyxl.reader namespace."""
# package imports
from . import excel
from . import strings
from . import style
from . import workbook
from . import worksheet
+117
View File
@@ -0,0 +1,117 @@
# file openpyxl/reader/excel.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Read an xlsx file into Python"""
# Python stdlib imports
from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile
# package imports
from ..shared.exc import OpenModeError, InvalidFileException
from ..shared.ooxml import ARC_SHARED_STRINGS, ARC_CORE, ARC_APP, \
ARC_WORKBOOK, PACKAGE_WORKSHEETS, ARC_STYLE
from ..workbook import Workbook
from .strings import read_string_table
from .style import read_style_table
from .workbook import read_sheets_titles, read_named_ranges, \
read_properties_core, get_sheet_ids
from .worksheet import read_worksheet
from .iter_worksheet import unpack_worksheet
def load_workbook(filename, use_iterators = False):
"""Open the given filename and return the workbook
:param filename: the path to open
:type filename: string
:param use_iterators: use lazy load for cells
:type use_iterators: bool
:rtype: :class:`..workbook.Workbook`
.. note::
When using lazy load, all worksheets will be :class:`.iter_worksheet.IterableWorksheet`
and the returned workbook will be read-only.
"""
if isinstance(filename, file):
# 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, RuntimeError, IOError, ValueError) as e:
raise InvalidFileException(str(e))
wb = Workbook()
if use_iterators:
wb._set_optimized_read()
try:
_load_workbook(wb, archive, filename, use_iterators)
except KeyError as e:
raise InvalidFileException(str(e))
except Exception as e:
raise e
finally:
archive.close()
return wb
def _load_workbook(wb, archive, filename, use_iterators):
valid_files = archive.namelist()
# get workbook-level information
wb.properties = read_properties_core(archive.read(ARC_CORE))
try:
string_table = read_string_table(archive.read(ARC_SHARED_STRINGS))
except KeyError:
string_table = {}
style_table = read_style_table(archive.read(ARC_STYLE))
# get worksheets
wb.worksheets = [] # remove preset worksheet
sheet_names = read_sheets_titles(archive.read(ARC_APP))
for i, sheet_name in enumerate(sheet_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)
else:
xml_source = unpack_worksheet(archive, worksheet_path)
new_ws = read_worksheet(xml_source, wb, sheet_name, string_table, style_table, filename, sheet_codename)
#new_ws = read_worksheet(archive.read(worksheet_path), 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)
@@ -0,0 +1,343 @@
# file openpyxl/reader/iter_worksheet.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
""" Iterators-based worksheet reader
*Still very raw*
"""
from io import StringIO
import warnings
import operator
from functools import partial
from itertools import groupby
from ..worksheet import Worksheet
from ..cell import coordinate_from_string, get_column_letter, Cell
from .excel import get_sheet_ids
from .strings import read_string_table
from .style import read_style_table, NumberFormat
from ..shared.date_time import SharedDate
from .worksheet import read_dimension
from ..shared.ooxml import (MIN_COLUMN, MAX_COLUMN, PACKAGE_WORKSHEETS,
MAX_ROW, MIN_ROW, ARC_SHARED_STRINGS, ARC_APP, ARC_STYLE)
from xml.etree.cElementTree import iterparse
from zipfile import ZipFile
from .. import cell
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 range(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
class RawCell(BaseRawCell):
"""Optimized version of the :class:`..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):
res = (self.data_type == Cell.TYPE_NUMERIC
and self.number_format is not None
and ('d' in self.number_format
or 'm' in self.number_format
or 'y' in self.number_format
or 'h' in self.number_format
or 's' in self.number_format
))
return res
def iter_rows(workbook_name, sheet_name, xml_source, 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
try:
string_table = read_string_table(archive.read(ARC_SHARED_STRINGS))
except KeyError:
string_table = {}
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)
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):
expected_columns = [get_column_letter(ci) for ci in range(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 range(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 == Cell.TYPE_STRING:
cell = cell._replace(internal_value = 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 == 'True')
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))
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):
Worksheet.__init__(self, parent_workbook, title)
self._workbook_name = workbook_name
self._sheet_codename = sheet_codename
self._xml_source = xml_source
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)
def cell(self, *args, **kwargs):
raise NotImplementedError("use 'iter_rows()' instead")
def range(self, *args, **kwargs):
raise NotImplementedError("use 'iter_rows()' instead")
def unpack_worksheet(archive, filename):
temp_file = tempfile.TemporaryFile(mode='r+', 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)
if decoder:
temp_file.write(decoder.decompress('Z'))
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("<HH", archive.fp.read(4))
return zinfo.header_offset + 30 + file_name_len + extra_len
@@ -0,0 +1,64 @@
# file openpyxl/reader/strings.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Read the shared strings table."""
# package imports
from ..shared.xmltools import fromstring, QName
from ..shared.ooxml import NAMESPACES
def read_string_table(xml_source):
"""Read in all shared strings in the table"""
table = {}
xmlns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
root = fromstring(text=xml_source)
string_index_nodes = root.findall(QName(xmlns, 'si').text)
for index, string_index_node in enumerate(string_index_nodes):
table[index] = get_string(xmlns, string_index_node)
return table
def get_string(xmlns, string_index_node):
"""Read the contents of a specific string index"""
rich_nodes = string_index_node.findall(QName(xmlns, 'r').text)
if rich_nodes:
reconstructed_text = []
for rich_node in rich_nodes:
partial_text = get_text(xmlns, rich_node)
reconstructed_text.append(partial_text)
return ''.join(reconstructed_text)
else:
return get_text(xmlns, string_index_node)
def get_text(xmlns, rich_node):
"""Read rich text, discarding formatting if not disallowed"""
text_node = rich_node.find(QName(xmlns, 't').text)
partial_text = text_node.text or ''
if text_node.get(QName(NAMESPACES['xml'], 'space').text) != 'preserve':
partial_text = partial_text.strip()
return str(partial_text)
+69
View File
@@ -0,0 +1,69 @@
# file openpyxl/reader/style.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Read shared style definitions"""
# package imports
from ..shared.xmltools import fromstring, QName
from ..shared.exc import MissingNumberFormat
from ..style import Style, NumberFormat
def read_style_table(xml_source):
"""Read styles from the shared style table"""
table = {}
xmlns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
root = fromstring(xml_source)
custom_num_formats = parse_custom_num_formats(root, xmlns)
builtin_formats = NumberFormat._BUILTIN_FORMATS
cell_xfs = root.find(QName(xmlns, 'cellXfs').text)
cell_xfs_nodes = cell_xfs.findall(QName(xmlns, 'xf').text)
for index, cell_xfs_node in enumerate(cell_xfs_nodes):
new_style = Style()
number_format_id = int(cell_xfs_node.get('numFmtId'))
if number_format_id < 164:
new_style.number_format.format_code = \
builtin_formats.get(number_format_id, 'General')
else:
if number_format_id in custom_num_formats:
new_style.number_format.format_code = \
custom_num_formats[number_format_id]
else:
raise MissingNumberFormat('%s' % number_format_id)
table[index] = new_style
return table
def parse_custom_num_formats(root, xmlns):
"""Read in custom numeric formatting rules from the shared style table"""
custom_formats = {}
num_fmts = root.find(QName(xmlns, 'numFmts').text)
if num_fmts is not None:
num_fmt_nodes = num_fmts.findall(QName(xmlns, 'numFmt').text)
for num_fmt_node in num_fmt_nodes:
custom_formats[int(num_fmt_node.get('numFmtId'))] = \
num_fmt_node.get('formatCode')
return custom_formats
@@ -0,0 +1,156 @@
# file openpyxl/reader/workbook.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Read in global settings to be maintained by the workbook object."""
# package imports
from ..shared.xmltools import fromstring, QName
from ..shared.ooxml import NAMESPACES
from ..workbook import DocumentProperties
from ..shared.date_time import W3CDTF_to_datetime
from ..namedrange import NamedRange, split_named_range
import datetime
# constants
BUGGY_NAMED_RANGES = ['NA()', '#REF!']
DISCARDED_RANGES = ['Excel_BuiltIn', 'Print_Area']
def get_sheet_ids(xml_source):
sheet_names = read_sheets_titles(xml_source)
return dict((sheet, 'sheet%d.xml' % (i + 1)) for i, sheet in enumerate(sheet_names))
def read_properties_core(xml_source):
"""Read assorted file properties."""
properties = DocumentProperties()
root = fromstring(xml_source)
creator_node = root.find(QName(NAMESPACES['dc'], 'creator').text)
if creator_node is not None:
properties.creator = creator_node.text
else:
properties.creator = ''
last_modified_by_node = root.find(
QName(NAMESPACES['cp'], 'lastModifiedBy').text)
if last_modified_by_node is not None:
properties.last_modified_by = last_modified_by_node.text
else:
properties.last_modified_by = ''
created_node = root.find(QName(NAMESPACES['dcterms'], 'created').text)
if created_node is not None:
properties.created = W3CDTF_to_datetime(created_node.text)
else:
properties.created = datetime.datetime.now()
modified_node = root.find(QName(NAMESPACES['dcterms'], 'modified').text)
if modified_node is not None:
properties.modified = W3CDTF_to_datetime(modified_node.text)
else:
properties.modified = properties.created
return properties
def get_number_of_parts(xml_source):
"""Get a list of contents of the workbook."""
parts_size = {}
parts_names = []
root = fromstring(xml_source)
heading_pairs = root.find(QName('http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
'HeadingPairs').text)
vector = heading_pairs.find(QName(NAMESPACES['vt'], 'vector').text)
children = vector.getchildren()
for child_id in range(0, len(children), 2):
part_name = children[child_id].find(QName(NAMESPACES['vt'],
'lpstr').text).text
if not part_name in parts_names:
parts_names.append(part_name)
part_size = int(children[child_id + 1].find(QName(
NAMESPACES['vt'], 'i4').text).text)
parts_size[part_name] = part_size
return parts_size, parts_names
def read_sheets_titles(xml_source):
"""Read titles for all sheets."""
root = fromstring(xml_source)
titles_root = root.find(QName('http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
'TitlesOfParts').text)
vector = titles_root.find(QName(NAMESPACES['vt'], 'vector').text)
parts, names = get_number_of_parts(xml_source)
# we can't assume 'Worksheets' to be written in english,
# but it's always the first item of the parts list (see bug #22)
size = parts[names[0]]
children = [c.text for c in vector.getchildren()]
return children[:size]
def read_named_ranges(xml_source, workbook):
"""Read named ranges, excluding poorly defined ranges."""
named_ranges = []
root = fromstring(xml_source)
names_root = root.find(QName('http://schemas.openxmlformats.org/spreadsheetml/2006/main',
'definedNames').text)
if names_root is not None:
for name_node in names_root.getchildren():
range_name = name_node.get('name')
if name_node.get("hidden", '0') == '1':
continue
valid = True
for discarded_range in DISCARDED_RANGES:
if discarded_range in range_name:
valid = False
for bad_range in BUGGY_NAMED_RANGES:
if bad_range in name_node.text:
valid = False
if valid:
destinations = split_named_range(name_node.text)
new_destinations = []
for worksheet, cells_range in destinations:
# it can happen that a valid named range references
# a missing worksheet, when Excel didn't properly maintain
# the named range list
#
# we just ignore them here
worksheet = workbook.get_sheet_by_name(worksheet)
if worksheet:
new_destinations.append((worksheet, cells_range))
named_range = NamedRange(range_name, new_destinations)
named_ranges.append(named_range)
return named_ranges
@@ -0,0 +1,117 @@
# file openpyxl/reader/worksheet.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Reader for a single worksheet."""
# Python stdlib imports
try:
from xml.etree.cElementTree import iterparse
except ImportError:
from xml.etree.ElementTree import iterparse
from io import StringIO
# package imports
from ..cell import Cell, coordinate_from_string
from ..worksheet import Worksheet
def _get_xml_iter(xml_source):
if not hasattr(xml_source, 'name'):
return StringIO(xml_source)
else:
xml_source.seek(0)
return xml_source
def read_dimension(xml_source):
source = _get_xml_iter(xml_source)
it = iterparse(source)
for event, element in it:
if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}dimension':
ref = element.get('ref')
if ':' in ref:
min_range, max_range = ref.split(':')
else:
min_range = max_range = ref
min_col, min_row = coordinate_from_string(min_range)
max_col, max_row = coordinate_from_string(max_range)
return min_col, min_row, max_col, max_row
else:
element.clear()
return None
def filter_cells(xxx_todo_changeme):
(event, element) = xxx_todo_changeme
return element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c'
def fast_parse(ws, xml_source, string_table, style_table):
source = _get_xml_iter(xml_source)
it = iterparse(source)
for event, element in filter(filter_cells, it):
value = element.findtext('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v')
if value is not None:
coordinate = element.get('r')
data_type = element.get('t', 'n')
style_id = element.get('s')
if data_type == Cell.TYPE_STRING:
value = string_table.get(int(value))
ws.cell(coordinate).value = value
if style_id is not None:
ws._styles[coordinate] = style_table.get(int(style_id))
# to avoid memory exhaustion, clear the item after use
element.clear()
from ..reader.iter_worksheet import IterableWorksheet
def read_worksheet(xml_source, parent, preset_title, string_table,
style_table, workbook_name = None, sheet_codename = None):
"""Read an xml worksheet"""
if workbook_name and sheet_codename:
ws = IterableWorksheet(parent, preset_title, workbook_name,
sheet_codename, xml_source)
else:
ws = Worksheet(parent, preset_title)
fast_parse(ws, xml_source, string_table, style_table)
return ws
@@ -0,0 +1,33 @@
# file openpyxl/shared/__init__.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Imports for the . namespace."""
# package imports
from . import date_time
from . import exc
from . import ooxml
from . import password_hasher
from . import xmltools
@@ -0,0 +1,154 @@
# file openpyxl/shared/date_time.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Manage Excel date weirdness."""
# Python stdlib imports
from math import floor
import calendar
import datetime
import time
import re
# constants
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 = list(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.
"""
CALENDAR_WINDOWS_1900 = 1900
CALENDAR_MAC_1904 = 1904
datetime_object_type = 'DateTime'
def __init__(self):
self.excel_base_date = self.CALENDAR_WINDOWS_1900
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)
elif isinstance(date, datetime.date):
return self.to_julian(date.year, date.month, date.day)
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 == self.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
else:
raise NotImplementedError('Mac dates are not yet supported.')
#excel_base_date = 2416481
#excel_1900_leap_year = False
# 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 == self.CALENDAR_WINDOWS_1900 \
and excel_date == 60:
msg = 'Error: Excel believes 1900 was a leap year'
raise ValueError(msg)
excel_time = ((hours * 3600) + (minutes * 60) + seconds) / 86400
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 == self.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)
else:
raise NotImplementedError('Mac dates are not yet supported.')
#excel_base_date = 24107
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)
+59
View File
@@ -0,0 +1,59 @@
# file openpyxl/shared/exc.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""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"""
+60
View File
@@ -0,0 +1,60 @@
# file openpyxl/shared/ooxml.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""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'
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'
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'
}
@@ -0,0 +1,47 @@
# file openpyxl/shared/password_hasher.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""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 <xnoguer@rezebra.com>.
"""
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:]
+67
View File
@@ -0,0 +1,67 @@
# file openpyxl/shared/units.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
import math
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
@@ -0,0 +1,96 @@
# file openpyxl/shared/xmltools.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""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 xml.sax.xmlreader import AttributesNSImpl
from xml.sax.saxutils import XMLGenerator
try:
from xml.etree.ElementTree import ElementTree, Element, SubElement, \
QName, fromstring, tostring
except ImportError:
from cElementTree import ElementTree, Element, SubElement, \
QName, fromstring, tostring
# package imports
from .. import __name__ as prefix
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 = {}
attr_vals = {}
attr_keys = {}
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)
+392
View File
@@ -0,0 +1,392 @@
# file openpyxl/style.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Style and formatting option tracking."""
# Python stdlib imports
import re
try:
from hashlib import md5
except ImportError:
from md5 import md5
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_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'
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 list(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
return any([x in format for x in self.DATE_INDICATORS])
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):
super(Style, self).__init__()
self.font = Font()
self.fill = Fill()
self.borders = Borders()
self.alignment = Alignment()
self.number_format = NumberFormat()
self.protection = Protection()
DEFAULTS = Style()
+186
View File
@@ -0,0 +1,186 @@
# file openpyxl/workbook.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Workbook is the top-level container for all document information."""
__docformat__ = "restructuredtext en"
# Python stdlib imports
import datetime
import os
# package imports
from .worksheet import Worksheet
from .writer.dump_worksheet import DumpWorksheet, save_dump
from .writer.strings import StringTableBuilder
from .namedrange import NamedRange
from .style import Style
from .writer.excel import save_workbook
from .shared.exc import ReadOnlyWorkbookException
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'
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):
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.strings_table_builder = StringTableBuilder()
if not optimized_write:
self.worksheets.append(Worksheet(self))
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):
"""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 = DumpWorksheet(parent_workbook = self)
else:
new_ws = Worksheet(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)."""
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):
"""Create a new named_range on a worksheet"""
assert isinstance(worksheet, Worksheet)
named_range = NamedRange(name, [(worksheet, range)])
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):
""" shortcut """
if self.__optimized_write:
save_dump(self, filename)
else:
save_workbook(self, filename)
+534
View File
@@ -0,0 +1,534 @@
# file openpyxl/worksheet.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Worksheet is the 2nd-level container in Excel."""
# Python stdlib imports
import re
# package imports
from . import cell
from .cell import coordinate_from_string, \
column_index_from_string, get_column_letter
from .shared.exc import SheetTitleException, \
InsufficientCoordinatesException, CellCoordinatesException, \
NamedRangeException
from .shared.password_hasher import hash_password
from .style import Style, DEFAULTS as DEFAULTS_STYLE
from .drawing import Drawing
_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',
#'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"""
pass
class HeaderFooter(object):
"""Information about the header/footer for this sheet."""
pass
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."""
def __init__(self):
self.left = self.right = 0.7
self.top = self.bottom = 0.75
self.header = self.footer = 0.3
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:`.workbook.Workbook.create_sheet` instead
"""
BREAK_NONE = 0
BREAK_ROW = 1
BREAK_COLUMN = 2
SHEETSTATE_VISIBLE = 'visible'
SHEETSTATE_HIDDEN = 'hidden'
SHEETSTATE_VERYHIDDEN = 'veryHidden'
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._cells = {}
self._styles = {}
self._charts = []
self.relationships = []
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
def __repr__(self):
return '<Worksheet "%s">' % self.title
def garbage_collect(self):
"""Delete cells that are not storing a value."""
delete_list = [coordinate for coordinate, cell in \
self._cells.items() if (cell.value in ('', None) and \
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 list(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 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:`.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 = 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:`.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 range(min_col, max_col + 1):
cache_cols[col] = get_column_letter(col)
rows = range(min_row + row, max_row + row + 1)
cols = range(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)
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()
return self._styles[coordinate]
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_chart(self, chart):
""" Add a chart to the sheet """
chart._sheet = self
self._charts.append(chart)
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 list_or_dict.items():
if isinstance(col_idx, str):
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)
@@ -0,0 +1,34 @@
# file openpyxl/writer/__init__.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Imports for the openpyxl.writer namespace."""
# package imports
from . import excel
from . import strings
from . import styles
from . import theme
from . import workbook
from . import worksheet
+262
View File
@@ -0,0 +1,262 @@
# coding=UTF-8
'''
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.
@license: http://www.opensource.org/licenses/mit-license.php
@author: Eric Gazoni
'''
from ..shared.xmltools import Element, SubElement, get_document_content
from ..chart import Chart, ErrorBar
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(axis.max)})
SubElement(scaling, 'c:min', {'val':str(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'})
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(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.xvalues)
yval = SubElement(ser, 'c:yVal')
self._write_serial(yval, serie.values)
else:
val = SubElement(ser, 'c:val')
self._write_serial(val, serie.values)
def _write_serial(self, node, serie, literal=False):
cache = serie._get_cache()
if isinstance(cache[0], str):
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 = serie._get_ref()
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)})
SubElement(point, 'c:v').text = str(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):
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')
margins = dict([(k, str(v)) for (k,v) in self.chart.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)
@@ -0,0 +1,192 @@
# coding=UTF-8
'''
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.
@license: http://www.opensource.org/licenses/mit-license.php
@author: Eric Gazoni
'''
from ..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')
return get_document_content(root)
def write_rels(self, chart_id):
root = Element('Relationships',
{'xmlns' : 'http://schemas.openxmlformats.org/package/2006/relationships'})
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)
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'})
@@ -0,0 +1,256 @@
# file openpyxl/writer/straight_worksheet.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Write worksheets to xml representations in an optimized way"""
import datetime
import os
from ..cell import column_index_from_string, get_column_letter, Cell
from ..worksheet import Worksheet
from ..shared.xmltools import XMLGenerator, get_document_content, \
start_tag, end_tag, tag
from ..shared.date_time import SharedDate
from ..shared.ooxml import MAX_COLUMN, MAX_ROW
from tempfile import NamedTemporaryFile
from ..writer.excel import ExcelWriter
from ..writer.strings import write_string_table
from ..writer.styles import StyleWriter
from ..style import Style, NumberFormat
from ..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, \
PACKAGE_WORKSHEETS, PACKAGE_DRAWINGS, PACKAGE_CHARTS
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'},
}
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)
class DumpWorksheet(Worksheet):
"""
.. warning::
You shouldn't initialize this yourself, use :class:`..workbook.Workbook` constructor instead,
with `optimized_write = True`.
"""
def __init__(self, parent_workbook):
Worksheet.__init__(self, parent_workbook)
self._max_col = 0
self._max_row = 0
self._parent = parent_workbook
self._fileobj_header = NamedTemporaryFile(mode='r+', prefix='..', suffix='.header', delete=False)
self._fileobj_content = NamedTemporaryFile(mode='r+', prefix='..', suffix='.content', delete=False)
self._fileobj = NamedTemporaryFile(mode='w', prefix='..', delete=False)
self.doc = XMLGenerator(self._fileobj_content, 'utf-8')
self.header = XMLGenerator(self._fileobj_header, 'utf-8')
self.title = 'Sheet'
self._shared_date = SharedDate()
self._string_builder = self._parent.strings_table_builder
@property
def filename(self):
return self._fileobj.name
def write_header(self):
doc = self.header
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._close_header()
self._write_fileobj(self._fileobj_header)
self._write_fileobj(self._fileobj_content)
self._fileobj.close()
def _write_fileobj(self, fobj):
fobj.flush()
fobj.seek(0)
while True:
chunk = fobj.read(4096)
if not chunk:
break
self._fileobj.write(chunk)
fobj.close()
os.remove(fobj.name)
self._fileobj.flush()
def _close_header(self):
doc = self.header
#doc.endDocument()
def _close_content(self):
doc = self.doc
end_tag(doc, 'sheetData')
end_tag(doc, 'worksheet')
#doc.endDocument()
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 append(self, row):
"""
:param row: iterable containing values to append
:type row: iterable
"""
doc = self.doc
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, (int, float)):
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)
attributes['t'] = STYLES[dtype]['type']
start_tag(doc, 'c', attributes)
if dtype == 'formula':
tag(doc, 'f', body = '%s' % cell[1:])
tag(doc, 'v')
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))
os.remove(sheet.filename)
class StyleDumpWriter(StyleWriter):
def _get_style_list(self, workbook):
return []
+156
View File
@@ -0,0 +1,156 @@
# file openpyxl/writer/excel.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Write a .xlsx file."""
# Python stdlib imports
from zipfile import ZipFile, ZIP_DEFLATED
from io import StringIO
# package imports
from ..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, \
PACKAGE_WORKSHEETS, PACKAGE_DRAWINGS, PACKAGE_CHARTS
from .strings import create_string_table, write_string_table
from .workbook import write_content_types, write_root_rels, \
write_workbook_rels, write_properties_app, write_properties_core, \
write_workbook
from .theme import write_theme
from .styles import StyleWriter
from .drawings import DrawingWriter, ShapeWriter
from .charts import ChartWriter
from .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))
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))
archive.writestr(ARC_THEME, write_theme())
archive.writestr(ARC_STYLE, self.style_writer.write_table())
archive.writestr(ARC_WORKBOOK, write_workbook(self.workbook))
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
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.relationships:
archive.writestr(PACKAGE_WORKSHEETS +
'/_rels/sheet%d.xml.rels' % (i + 1),
write_worksheet_rels(sheet, drawing_id))
if sheet._charts:
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))
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
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 = StringIO()
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
@@ -0,0 +1,86 @@
# file openpyxl/writer/strings.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Write the shared string table."""
# Python stdlib imports
from io import StringIO
# package imports
from ..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(strings))
def write_string_table(string_table):
"""Write the string table xml."""
temp_buffer = StringIO()
doc = XMLGenerator(temp_buffer, 'utf-8')
start_tag(doc, 'sst', {'xmlns':
'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
'uniqueCount': '%d' % len(string_table)})
strings_to_write = sorted(iter(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
+256
View File
@@ -0,0 +1,256 @@
# file openpyxl/writer/styles.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Write the shared style table."""
# package imports
from ..shared.xmltools import Element, SubElement
from ..shared.xmltools import get_document_content
from .. 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:
for style in list(worksheet._styles.values()):
crc[hash(style)] = style
self.style_table = dict([(style, i+1) \
for i, style in enumerate(list(crc.values()))])
sorted_styles = sorted(iter(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)})
SubElement(font_node, 'color', {'rgb':str(st.font.color.index)})
SubElement(font_node, 'name', {'val':st.font.name})
SubElement(font_node, 'family', {'val':'2'})
SubElement(font_node, 'scheme', {'val':'minor'})
if st.font.bold:
SubElement(font_node, 'b')
if st.font.italic:
SubElement(font_node, 'i')
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):
SubElement(node, 'fgColor', {'rgb':str(st.fill.start_color.index)})
if hash(st.fill.end_color) != hash(style.DEFAULTS.fill.end_color):
SubElement(node, 'bgColor', {'rgb':str(st.fill.start_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)
node = SubElement(border, side, {'style':obj.border_style})
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['applyFillId'] = '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)
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
+202
View File
@@ -0,0 +1,202 @@
# -*- coding: utf-8 -*-
# file openpyxl/writer/theme.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Write the theme xml based on a fixed string."""
# package imports
from ..shared.xmltools import fromstring, get_document_content
def write_theme():
"""Write the theme xml."""
xml_node = fromstring(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
'<a:theme xmlns:a="http://schemas.openxmlformats.org/'
'drawingml/2006/main" name="Office Theme">'
'<a:themeElements>'
'<a:clrScheme name="Office">'
'<a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>'
'<a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>'
'<a:dk2><a:srgbClr val="1F497D"/></a:dk2>'
'<a:lt2><a:srgbClr val="EEECE1"/></a:lt2>'
'<a:accent1><a:srgbClr val="4F81BD"/></a:accent1>'
'<a:accent2><a:srgbClr val="C0504D"/></a:accent2>'
'<a:accent3><a:srgbClr val="9BBB59"/></a:accent3>'
'<a:accent4><a:srgbClr val="8064A2"/></a:accent4>'
'<a:accent5><a:srgbClr val="4BACC6"/></a:accent5>'
'<a:accent6><a:srgbClr val="F79646"/></a:accent6>'
'<a:hlink><a:srgbClr val="0000FF"/></a:hlink>'
'<a:folHlink><a:srgbClr val="800080"/></a:folHlink>'
'</a:clrScheme>'
'<a:fontScheme name="Office">'
'<a:majorFont>'
'<a:latin typeface="Cambria"/>'
'<a:ea typeface=""/>'
'<a:cs typeface=""/>'
'<a:font script="Jpan" typeface="MS Pゴシック"/>'
'<a:font script="Hang" typeface="맑은 고딕"/>'
'<a:font script="Hans" typeface="宋体"/>'
'<a:font script="Hant" typeface="新細明體"/>'
'<a:font script="Arab" typeface="Times New Roman"/>'
'<a:font script="Hebr" typeface="Times New Roman"/>'
'<a:font script="Thai" typeface="Tahoma"/>'
'<a:font script="Ethi" typeface="Nyala"/>'
'<a:font script="Beng" typeface="Vrinda"/>'
'<a:font script="Gujr" typeface="Shruti"/>'
'<a:font script="Khmr" typeface="MoolBoran"/>'
'<a:font script="Knda" typeface="Tunga"/>'
'<a:font script="Guru" typeface="Raavi"/>'
'<a:font script="Cans" typeface="Euphemia"/>'
'<a:font script="Cher" typeface="Plantagenet Cherokee"/>'
'<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>'
'<a:font script="Tibt" typeface="Microsoft Himalaya"/>'
'<a:font script="Thaa" typeface="MV Boli"/>'
'<a:font script="Deva" typeface="Mangal"/>'
'<a:font script="Telu" typeface="Gautami"/>'
'<a:font script="Taml" typeface="Latha"/>'
'<a:font script="Syrc" typeface="Estrangelo Edessa"/>'
'<a:font script="Orya" typeface="Kalinga"/>'
'<a:font script="Mlym" typeface="Kartika"/>'
'<a:font script="Laoo" typeface="DokChampa"/>'
'<a:font script="Sinh" typeface="Iskoola Pota"/>'
'<a:font script="Mong" typeface="Mongolian Baiti"/>'
'<a:font script="Viet" typeface="Times New Roman"/>'
'<a:font script="Uigh" typeface="Microsoft Uighur"/>'
'</a:majorFont>'
'<a:minorFont>'
'<a:latin typeface="Calibri"/>'
'<a:ea typeface=""/>'
'<a:cs typeface=""/>'
'<a:font script="Jpan" typeface="MS Pゴシック"/>'
'<a:font script="Hang" typeface="맑은 고딕"/>'
'<a:font script="Hans" typeface="宋体"/>'
'<a:font script="Hant" typeface="新細明體"/>'
'<a:font script="Arab" typeface="Arial"/>'
'<a:font script="Hebr" typeface="Arial"/>'
'<a:font script="Thai" typeface="Tahoma"/>'
'<a:font script="Ethi" typeface="Nyala"/>'
'<a:font script="Beng" typeface="Vrinda"/>'
'<a:font script="Gujr" typeface="Shruti"/>'
'<a:font script="Khmr" typeface="DaunPenh"/>'
'<a:font script="Knda" typeface="Tunga"/>'
'<a:font script="Guru" typeface="Raavi"/>'
'<a:font script="Cans" typeface="Euphemia"/>'
'<a:font script="Cher" typeface="Plantagenet Cherokee"/>'
'<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>'
'<a:font script="Tibt" typeface="Microsoft Himalaya"/>'
'<a:font script="Thaa" typeface="MV Boli"/>'
'<a:font script="Deva" typeface="Mangal"/>'
'<a:font script="Telu" typeface="Gautami"/>'
'<a:font script="Taml" typeface="Latha"/>'
'<a:font script="Syrc" typeface="Estrangelo Edessa"/>'
'<a:font script="Orya" typeface="Kalinga"/>'
'<a:font script="Mlym" typeface="Kartika"/>'
'<a:font script="Laoo" typeface="DokChampa"/>'
'<a:font script="Sinh" typeface="Iskoola Pota"/>'
'<a:font script="Mong" typeface="Mongolian Baiti"/>'
'<a:font script="Viet" typeface="Arial"/>'
'<a:font script="Uigh" typeface="Microsoft Uighur"/>'
'</a:minorFont>'
'</a:fontScheme>'
'<a:fmtScheme name="Office">'
'<a:fillStyleLst>'
'<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>'
'<a:gradFill rotWithShape="1"><a:gsLst>'
'<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="50000"/>'
'<a:satMod val="300000"/></a:schemeClr></a:gs>'
'<a:gs pos="35000"><a:schemeClr val="phClr"><a:tint val="37000"/>'
'<a:satMod val="300000"/></a:schemeClr></a:gs>'
'<a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="15000"/>'
'<a:satMod val="350000"/></a:schemeClr></a:gs></a:gsLst>'
'<a:lin ang="16200000" scaled="1"/></a:gradFill>'
'<a:gradFill rotWithShape="1"><a:gsLst>'
'<a:gs pos="0"><a:schemeClr val="phClr"><a:shade val="51000"/>'
'<a:satMod val="130000"/></a:schemeClr></a:gs>'
'<a:gs pos="80000"><a:schemeClr val="phClr"><a:shade val="93000"/>'
'<a:satMod val="130000"/></a:schemeClr></a:gs>'
'<a:gs pos="100000"><a:schemeClr val="phClr">'
'<a:shade val="94000"/>'
'<a:satMod val="135000"/></a:schemeClr></a:gs></a:gsLst>'
'<a:lin ang="16200000" scaled="0"/></a:gradFill></a:fillStyleLst>'
'<a:lnStyleLst>'
'<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr">'
'<a:solidFill><a:schemeClr val="phClr"><a:shade val="95000"/>'
'<a:satMod val="105000"/></a:schemeClr></a:solidFill>'
'<a:prstDash val="solid"/></a:ln>'
'<a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill>'
'<a:schemeClr val="phClr"/></a:solidFill>'
'<a:prstDash val="solid"/></a:ln>'
'<a:ln w="38100" cap="flat" cmpd="sng" algn="ctr"><a:solidFill>'
'<a:schemeClr val="phClr"/></a:solidFill>'
'<a:prstDash val="solid"/></a:ln></a:lnStyleLst>'
'<a:effectStyleLst><a:effectStyle><a:effectLst>'
'<a:outerShdw blurRad="40000" dist="20000" dir="5400000" '
'rotWithShape="0"><a:srgbClr val="000000">'
'<a:alpha val="38000"/></a:srgbClr></a:outerShdw></a:effectLst>'
'</a:effectStyle><a:effectStyle><a:effectLst>'
'<a:outerShdw blurRad="40000" dist="23000" dir="5400000" '
'rotWithShape="0"><a:srgbClr val="000000">'
'<a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst>'
'</a:effectStyle><a:effectStyle><a:effectLst>'
'<a:outerShdw blurRad="40000" dist="23000" dir="5400000" '
'rotWithShape="0"><a:srgbClr val="000000">'
'<a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst>'
'<a:scene3d><a:camera prst="orthographicFront">'
'<a:rot lat="0" lon="0" rev="0"/></a:camera>'
'<a:lightRig rig="threePt" dir="t">'
'<a:rot lat="0" lon="0" rev="1200000"/></a:lightRig>'
'</a:scene3d><a:sp3d><a:bevelT w="63500" h="25400"/>'
'</a:sp3d></a:effectStyle></a:effectStyleLst>'
'<a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/>'
'</a:solidFill><a:gradFill rotWithShape="1"><a:gsLst>'
'<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="40000"/>'
'<a:satMod val="350000"/></a:schemeClr></a:gs>'
'<a:gs pos="40000"><a:schemeClr val="phClr"><a:tint val="45000"/>'
'<a:shade val="99000"/><a:satMod val="350000"/>'
'</a:schemeClr></a:gs>'
'<a:gs pos="100000"><a:schemeClr val="phClr">'
'<a:shade val="20000"/><a:satMod val="255000"/>'
'</a:schemeClr></a:gs></a:gsLst>'
'<a:path path="circle">'
'<a:fillToRect l="50000" t="-80000" r="50000" b="180000"/>'
'</a:path>'
'</a:gradFill><a:gradFill rotWithShape="1"><a:gsLst>'
'<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="80000"/>'
'<a:satMod val="300000"/></a:schemeClr></a:gs>'
'<a:gs pos="100000"><a:schemeClr val="phClr">'
'<a:shade val="30000"/><a:satMod val="200000"/>'
'</a:schemeClr></a:gs></a:gsLst>'
'<a:path path="circle">'
'<a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path>'
'</a:gradFill></a:bgFillStyleLst></a:fmtScheme>'
'</a:themeElements>'
'<a:objectDefaults/><a:extraClrSchemeLst/>'
'</a:theme>')
return get_document_content(xml_node)
@@ -0,0 +1,204 @@
# file openpyxl/writer/workbook.py
# 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.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: Eric Gazoni
"""Write the workbook global settings to the archive."""
# package imports
from ..shared.xmltools import Element, SubElement
from ..cell import absolute_coordinate
from ..shared.xmltools import get_document_content
from ..shared.ooxml import NAMESPACES, ARC_CORE, ARC_WORKBOOK, \
ARC_APP, ARC_THEME, ARC_STYLE, ARC_SHARED_STRINGS
from ..shared.date_time import datetime_to_W3CDTF
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)
return get_document_content(root)
def write_content_types(workbook):
"""Write the content-types xml."""
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, '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):
SubElement(root, 'Override',
{'PartName': '/xl/worksheets/sheet%d.xml' % (sheet_id + 1),
'ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml'})
if sheet._charts:
SubElement(root, 'Override',
{'PartName' : '/xl/drawings/drawing%d.xml' % (sheet_id + 1),
'ContentType' : 'application/vnd.openxmlformats-officedocument.drawing+xml'})
drawing_id += 1
for chart in sheet._charts:
SubElement(root, 'Override',
{'PartName' : '/xl/charts/chart%d.xml' % chart_id,
'ContentType' : 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml'})
chart_id += 1
if chart._shapes:
SubElement(root, 'Override',
{'PartName' : '/xl/drawings/drawing%d.xml' % drawing_id,
'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)
# named ranges
defined_names = SubElement(root, 'definedNames')
for named_range in workbook.get_named_ranges():
name = SubElement(defined_names, 'definedName',
{'name': named_range.name})
# as there can be many cells in one range, generate the list of ranges
dest_cells = []
cell_ids = []
for worksheet, range_name in named_range.destinations:
cell_ids.append(workbook.get_index(worksheet))
dest_cells.append("'%s'!%s" % (worksheet.title.replace("'", "''"),
absolute_coordinate(range_name)))
# for local ranges, we must check all the cells belong to the same sheet
base_id = cell_ids[0]
if named_range.local_only and all([x == base_id for x in cell_ids]):
name.set('localSheetId', '%s' % base_id)
# finally write the cells list
name.text = ','.join(dest_cells)
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'})
return get_document_content(root)

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