mirror of
https://github.com/kennethreitz/tablib.git
synced 2026-06-05 15:00:19 +00:00
Compare commits
443 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e42d215833 | |||
| 1a5e2ecb33 | |||
| e1bf189847 | |||
| 0785328e21 | |||
| 6ba0cc9af3 | |||
| 36876205e7 | |||
| 1b97b7191e | |||
| 8b575df419 | |||
| 6a3928759a | |||
| 63348d883b | |||
| 5dce600969 | |||
| 0913b54f47 | |||
| c5bbc74b96 | |||
| 7f5342a1b8 | |||
| d42f9bc10f | |||
| c6565c9e29 | |||
| 1a9343750e | |||
| 8a393214c8 | |||
| b8ed741a36 | |||
| cddbd78a61 | |||
| b113f49ce6 | |||
| 1429b9f8c4 | |||
| 42700f98a5 | |||
| 0e56db632a | |||
| b07512071e | |||
| e4881809d6 | |||
| 54ab300d2d | |||
| 4368d64317 | |||
| 117344de14 | |||
| 58bc1c7dcf | |||
| 4c8b5e72e3 | |||
| b900236157 | |||
| dc14a16e04 | |||
| 2d2ac9b708 | |||
| 1efcb7a63d | |||
| 65c73dfc42 | |||
| 3803a7a21b | |||
| 8b5b29fc90 | |||
| e8ba765426 | |||
| 57001a5465 | |||
| c8493ff047 | |||
| 03914323c2 | |||
| 2f331cee8e | |||
| e1734f2315 | |||
| 22cddbcd63 | |||
| 76f09cd3b3 | |||
| d11c09febe | |||
| 9ab277a468 | |||
| 23c1831144 | |||
| 1cf9bd14b4 | |||
| c2331f7a23 | |||
| bccf0d1ba1 | |||
| c219972ccd | |||
| 52e9d44739 | |||
| e94ecd8472 | |||
| 96067e6380 | |||
| 1cc0f7d1f4 | |||
| f685bf548e | |||
| ca336926da | |||
| 1aa3d3b06a | |||
| be576135b2 | |||
| 0c05d0497e | |||
| 52e307ea35 | |||
| 5cac9bd97e | |||
| a285e993f1 | |||
| 0ed367a31c | |||
| c4815c24cc | |||
| 20fe1e0153 | |||
| 5db8d1c3a6 | |||
| 828017f9a7 | |||
| cff8a6ac9a | |||
| aa8590e8b8 | |||
| d2de647c47 | |||
| 7afef680f5 | |||
| 35763f8c24 | |||
| cc3d020914 | |||
| b8b5405f1c | |||
| b7aebbc74f | |||
| d776d78df5 | |||
| 6f9365d376 | |||
| 621b1bd45c | |||
| be21b6fadd | |||
| 832bfbbb1b | |||
| 288b15fb54 | |||
| 73df22303b | |||
| 4c125bd206 | |||
| ff0de1377a | |||
| ccb29c68fa | |||
| e077a7f2bc | |||
| dcc52bdc18 | |||
| 9cac54eefc | |||
| f69a96f07e | |||
| ca77ed6f64 | |||
| 806aba9ef3 | |||
| 23cbc0c333 | |||
| 34ab54de77 | |||
| 0843a15879 | |||
| 08ed309382 | |||
| 26b6faa88d | |||
| 140736ff33 | |||
| 5379c5683d | |||
| e8b44b5777 | |||
| a0822bc9b0 | |||
| 89b431213b | |||
| 695e8c5af7 | |||
| 0797ec67d4 | |||
| 1852624a7e | |||
| f81dc41a57 | |||
| 34415b89b8 | |||
| d25655588b | |||
| 22c4d185e1 | |||
| e3b3659ea4 | |||
| 22d337790a | |||
| 0784d4b32c | |||
| 332c5bccd9 | |||
| 7055d18a2e | |||
| 6a7c685111 | |||
| 0e5b8f7058 | |||
| e3e6b656e3 | |||
| 99896a5f28 | |||
| 25da44f569 | |||
| 7727171379 | |||
| 91bd4eb9c7 | |||
| 9b74b139fd | |||
| 823a543f41 | |||
| 1aa275bf99 | |||
| 17bb0d3b2c | |||
| 1a9aee9289 | |||
| 196edb82cc | |||
| a2990d5852 | |||
| d992ece86a | |||
| 46f302255d | |||
| 9e3ab4c13f | |||
| eaed0e48c2 | |||
| 501187b357 | |||
| ea4aef88b6 | |||
| 24d800fac3 | |||
| d8136ab613 | |||
| 36bbe2726b | |||
| 1427be2901 | |||
| 10ce000d31 | |||
| a91254117c | |||
| b67762604f | |||
| 83a8346e8f | |||
| 657ab98d04 | |||
| 9ddb4de942 | |||
| 5fad80a540 | |||
| cabab73045 | |||
| 2bb0525990 | |||
| f364bb576e | |||
| 09d057094e | |||
| 8082c4ad43 | |||
| 00e9ae0120 | |||
| f01c22213e | |||
| a58bf269d9 | |||
| 437a135dd3 | |||
| 0409ff50af | |||
| dd24edcc24 | |||
| cf28f4baa8 | |||
| 52dcf79c41 | |||
| 49f098ee22 | |||
| 642b1d8def | |||
| f6964bba8f | |||
| 8d6e75ad20 | |||
| 30487999ba | |||
| b74308e81e | |||
| 577289cbc3 | |||
| cf10703e31 | |||
| 778ad0265e | |||
| e3dedb8887 | |||
| c6e240fa52 | |||
| 5c747c9c2e | |||
| 0bbd990ed8 | |||
| fcada243a2 | |||
| fca8ad6182 | |||
| 35d9e390fd | |||
| 8ca180c461 | |||
| ff63558a67 | |||
| f621b56178 | |||
| 2b529bcb1c | |||
| 90c3435600 | |||
| 1fa28ee2ca | |||
| a5cae7c249 | |||
| 666991ca1e | |||
| 5f4162918f | |||
| b554ce36bb | |||
| e5e22d3ca2 | |||
| 8626351618 | |||
| cdfacb6d6e | |||
| 108c9de130 | |||
| 271aeebf56 | |||
| e75a00541d | |||
| 3b0e0c7991 | |||
| 23440fb7a3 | |||
| 459f310857 | |||
| f9021f53c2 | |||
| 7fda829d27 | |||
| ca08ac8a7b | |||
| 08b51113d3 | |||
| 3e391fc8e3 | |||
| a230844914 | |||
| bc82be09c5 | |||
| ed9fe01604 | |||
| e69546a0ff | |||
| d4b659ece9 | |||
| 55eb3f93e3 | |||
| be7182aea9 | |||
| 48def2cba6 | |||
| df8c0335d1 | |||
| d0b09f0fce | |||
| 9efd982bfa | |||
| a3c82804cd | |||
| 2e75e93f57 | |||
| a26d782e88 | |||
| f5c0c5c34d | |||
| d9aee8e605 | |||
| 315a082b70 | |||
| 120ce9fcd6 | |||
| 914a82eac9 | |||
| 3931bcb4e6 | |||
| 471e56c387 | |||
| 8553dbc040 | |||
| 96c93871cf | |||
| a54949bc08 | |||
| ed686c2391 | |||
| 143677be77 | |||
| b2c35c2543 | |||
| 9dfd9d0c8e | |||
| 140e23c980 | |||
| ac797f1eda | |||
| 7c90595364 | |||
| 38ac98fdb2 | |||
| 07c7d172d9 | |||
| 9c7707be60 | |||
| 14bee65208 | |||
| 4fc5e0655d | |||
| da2e670d0d | |||
| 5912bf4870 | |||
| 28e9d7e23e | |||
| 930d38cf5a | |||
| 5e433c263d | |||
| 19ac9b9716 | |||
| 6feb59504a | |||
| 817eedd6f5 | |||
| 4d1c5a9996 | |||
| 520a1986d7 | |||
| 1ea793112c | |||
| 41a7a5d329 | |||
| c4edaa2ca8 | |||
| c612bb3dae | |||
| c223dfbdf1 | |||
| 49bd48b016 | |||
| c6d90bc825 | |||
| bcd0e37a65 | |||
| 8c92e878a3 | |||
| da2b011358 | |||
| a8b0bf4b5f | |||
| 6574d3e58b | |||
| 1020799828 | |||
| 333e73f892 | |||
| bfe70066b8 | |||
| fbfbe01b70 | |||
| 06a394ea5c | |||
| 9427decdb0 | |||
| fb59035f8d | |||
| 187d12cffc | |||
| eaa4de7793 | |||
| d479c5735a | |||
| 96668bb393 | |||
| b369baba40 | |||
| 25f846a78a | |||
| 22fe18239f | |||
| 149bafa97b | |||
| 9f7fec2379 | |||
| 762ac39e27 | |||
| 2a7aa959b3 | |||
| d85523b6a6 | |||
| 6407afba3e | |||
| 25a5bcea0c | |||
| 7aada68952 | |||
| 5ba92b0f6b | |||
| f58d4b67dc | |||
| a310ab7a09 | |||
| 7f2f925ddb | |||
| 3fc898e222 | |||
| de46f45e2e | |||
| 392eaac299 | |||
| 3a9c3944cf | |||
| 8c402da729 | |||
| 8feb6e8ddf | |||
| 9072b6ddae | |||
| 9f26c23eb5 | |||
| 8136f4b09e | |||
| 7e7ad73ddd | |||
| f889910629 | |||
| 969d9d957d | |||
| 86d84b555d | |||
| 66867527d2 | |||
| 7505d8d985 | |||
| d5515c17b8 | |||
| 07ac723971 | |||
| 5d7843ea59 | |||
| b5f0cf9d37 | |||
| a73bbe1645 | |||
| f1bdf43aab | |||
| 7623bfe7b0 | |||
| 59ccc0b422 | |||
| 99154aa6d6 | |||
| 65836d5ace | |||
| 4117503ed5 | |||
| dfa26a7d53 | |||
| 4f035caf1b | |||
| a9c7a5067d | |||
| 80cb42e8dd | |||
| 8d7e5732cd | |||
| 942dd3dadf | |||
| b1d282744c | |||
| 4c0c879d65 | |||
| cab63e02c8 | |||
| 63d025888a | |||
| 5a993ac281 | |||
| 666dd1d2c7 | |||
| ac1666e3ae | |||
| 5b7e817db2 | |||
| f9c168e4bc | |||
| 82f3d84c7d | |||
| 121cf46aec | |||
| 4bb4a05bcb | |||
| e52b8dd329 | |||
| 93fb89b8b6 | |||
| c01b66a16a | |||
| c3fa29a166 | |||
| 8d6a52aaf5 | |||
| 703b1da04c | |||
| 0e6bd079cc | |||
| 579dbf0cc0 | |||
| fbabb430ca | |||
| b8f923f8c5 | |||
| fbe6fe1612 | |||
| 17e90e71e5 | |||
| dc21825f34 | |||
| 7364995eaa | |||
| 3407170b99 | |||
| dd13744c92 | |||
| 31e4c39762 | |||
| 4fc70957ac | |||
| 7f17ccf445 | |||
| fbcc3b60af | |||
| 9b3268f0ad | |||
| f386ef8ac8 | |||
| e8f5e023c4 | |||
| 81445aeec8 | |||
| f94a236122 | |||
| bfbb7c626f | |||
| be0f77f9ee | |||
| 3b44349090 | |||
| 04a16afa58 | |||
| a8632125dc | |||
| ccf2ebcde2 | |||
| 2c60ce9233 | |||
| 649c7e8bb7 | |||
| 2d3dc5ef71 | |||
| efc516f366 | |||
| b2a51fd941 | |||
| d54d70bc22 | |||
| 391ad61bef | |||
| 99a45814d1 | |||
| fad3546614 | |||
| 7ba2849829 | |||
| 7ec0f2ef07 | |||
| bd470684a4 | |||
| dbcea81c17 | |||
| 49dc4a249e | |||
| 7cd82f956f | |||
| 13c3e537fd | |||
| f913853cae | |||
| ea1de420a3 | |||
| d0c8df95a3 | |||
| bb4e97f8aa | |||
| ffaeb64639 | |||
| f31ec562b4 | |||
| 68d7204b2d | |||
| 52db1ddc3e | |||
| 4755020dd7 | |||
| 5468dd7e67 | |||
| 8673710ddb | |||
| f01cf184d4 | |||
| 1482ca4a19 | |||
| 93c6c39581 | |||
| a0cb44cc43 | |||
| b2cd061773 | |||
| 876b849950 | |||
| 40c9e09578 | |||
| 9f5379fcc7 | |||
| 9ecc57dbf7 | |||
| a7471f7302 | |||
| 70211b71e0 | |||
| ddf4b441b0 | |||
| a0509126e0 | |||
| ec5b1cf3e0 | |||
| 3fb729aac6 | |||
| 647f69044f | |||
| 3da34af76f | |||
| 5b42824871 | |||
| 89209b6bd3 | |||
| 9362d3283b | |||
| 123851a737 | |||
| 54973c276c | |||
| 275ac9d194 | |||
| 4ecd5888af | |||
| 359f12c83c | |||
| de8d76fdae | |||
| f188e3dd87 | |||
| 3dff8f5b79 | |||
| c24d2dd45d | |||
| 4e98563483 | |||
| ca17c9f965 | |||
| 41c4fcc59f | |||
| e9166b14fd | |||
| a5528d731e | |||
| 1a122f2a4d | |||
| e9d9350e43 | |||
| ac4b568cba | |||
| 8be372b8cc | |||
| f8d8d3058a | |||
| d03ba7e532 | |||
| 35102ab951 | |||
| b8587e5cb0 | |||
| da4f2013f1 | |||
| ff069b1604 | |||
| 2994a9fc0d | |||
| 310400af5b | |||
| d52537b75b | |||
| 40490d1ba5 | |||
| 9f025dc111 | |||
| 335e3b1134 | |||
| 6b4afc38d1 | |||
| bd3099897c | |||
| 4b6fbe9225 | |||
| f22357f1bc | |||
| 37ffbc71c0 | |||
| 6f7c64eb03 | |||
| 89be8f402f |
+12
-2
@@ -1,10 +1,11 @@
|
||||
# application builds
|
||||
build/*
|
||||
dist/*
|
||||
MANIFEST
|
||||
|
||||
# python skin
|
||||
.pyc
|
||||
.pyo
|
||||
*.pyc
|
||||
*.pyo
|
||||
|
||||
# osx noise
|
||||
.DS_Store
|
||||
@@ -13,3 +14,12 @@ profile
|
||||
# pycharm noise
|
||||
.idea
|
||||
.idea/*
|
||||
|
||||
# vi noise
|
||||
*.swp
|
||||
docs/_build/*
|
||||
coverage.xml
|
||||
nosetests.xml
|
||||
junit-py25.xml
|
||||
junit-py26.xml
|
||||
junit-py27.xml
|
||||
@@ -1,4 +1,4 @@
|
||||
GistAPI.py is written and maintained by Kenneth Reitz and
|
||||
Tablib is written and maintained by Kenneth Reitz and
|
||||
various contributors:
|
||||
|
||||
Development Lead
|
||||
@@ -10,4 +10,7 @@ Development Lead
|
||||
Patches and Suggestions
|
||||
```````````````````````
|
||||
|
||||
- A Lucky Someone
|
||||
- Luke Lee
|
||||
- Josh Ourisman
|
||||
- Luca Beltrame
|
||||
- Benjamin Wohlwend
|
||||
@@ -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 :)
|
||||
+142
-4
@@ -1,7 +1,145 @@
|
||||
History
|
||||
=======
|
||||
-------
|
||||
|
||||
0.1.0 (2010-09-??)
|
||||
------------------
|
||||
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.
|
||||
|
||||
|
||||
0.9.0 (2010-11-04)
|
||||
++++++++++++++++++
|
||||
|
||||
* Massive documentation update!
|
||||
* Tablib.org!
|
||||
* Row tagging and Dataset filtering!
|
||||
* Column insert/delete support
|
||||
* Column append API change (header required)
|
||||
* Internal Changes (Row object and use thereof)
|
||||
|
||||
|
||||
0.8.5 (2010-10-06)
|
||||
++++++++++++++++++
|
||||
|
||||
* New import system. All dependencies attempt to load from site-packages,
|
||||
then fallback on tenderized modules.
|
||||
|
||||
|
||||
0.8.4 (2010-10-04)
|
||||
++++++++++++++++++
|
||||
|
||||
* Updated XLS output: Only wrap if '\\n' in cell.
|
||||
|
||||
|
||||
0.8.3 (2010-10-04)
|
||||
++++++++++++++++++
|
||||
|
||||
* Ability to append new column passing a callable
|
||||
as the value that will be applied to every row.
|
||||
|
||||
|
||||
0.8.2 (2010-10-04)
|
||||
++++++++++++++++++
|
||||
|
||||
* Added alignment wrapping to written cells.
|
||||
* Added separator support to XLS.
|
||||
|
||||
|
||||
0.8.1 (2010-09-28)
|
||||
++++++++++++++++++
|
||||
|
||||
* Packaging Fix
|
||||
|
||||
|
||||
0.8.0 (2010-09-25)
|
||||
++++++++++++++++++
|
||||
|
||||
* New format plugin system!
|
||||
* Imports! ELEGANT Imports!
|
||||
* Tests. Lots of tests.
|
||||
|
||||
|
||||
0.7.1 (2010-09-20)
|
||||
++++++++++++++++++
|
||||
|
||||
* Reverting methods back to properties.
|
||||
* Windows bug compensated in documentation.
|
||||
|
||||
|
||||
0.7.0 (2010-09-20)
|
||||
++++++++++++++++++
|
||||
|
||||
* 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.
|
||||
|
||||
|
||||
0.6.4 (2010-09-19)
|
||||
++++++++++++++++++
|
||||
|
||||
* Updated unicode export for XLS.
|
||||
* More exhaustive unit tests.
|
||||
|
||||
|
||||
0.6.3 (2010-09-14)
|
||||
++++++++++++++++++
|
||||
* Added Dataset.append() support for columns.
|
||||
|
||||
|
||||
0.6.2 (2010-09-13)
|
||||
++++++++++++++++++
|
||||
* Fixed Dataset.append() error on empty dataset.
|
||||
* Updated Dataset.headers property w/ validation.
|
||||
* Added Testing Fixtures.
|
||||
|
||||
0.6.1 (2010-09-12)
|
||||
++++++++++++++++++
|
||||
|
||||
* Packaging hotfixes.
|
||||
|
||||
|
||||
0.6.0 (2010-09-11)
|
||||
++++++++++++++++++
|
||||
|
||||
* Public Release.
|
||||
* Export Support for XLS, JSON, YAML, and CSV.
|
||||
* DataBook Export for XLS, JSON, and YAML.
|
||||
* Python Dict Property Support.
|
||||
|
||||
* Initial Release
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
include HISTORY.rst README.rst tabbed
|
||||
include HISTORY.rst README.rst LICENSE AUTHORS
|
||||
@@ -0,0 +1,178 @@
|
||||
Tablib includes some vendorized python libraries: ordereddict, pyyaml,
|
||||
simplejson, 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.
|
||||
|
||||
|
||||
|
||||
PyYAML License
|
||||
==============
|
||||
|
||||
Copyright (c) 2006 Kirill Simonov
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
AnyJSON License
|
||||
==================
|
||||
|
||||
This software is licensed under the ``New BSD License``:
|
||||
|
||||
Copyright (c) 2009, by the authors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* 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.
|
||||
|
||||
|
||||
|
||||
XLWT License
|
||||
============
|
||||
|
||||
Portions copyright © 2007, Stephen John Machin, Lingfo Pty Ltd
|
||||
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. 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.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
"""
|
||||
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
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Roman V. Kiseliov OR
|
||||
ITS 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.
|
||||
|
||||
Roman V. Kiseliov
|
||||
Russia
|
||||
Kursk
|
||||
Libknecht St., 4
|
||||
|
||||
+7(0712)56-09-83
|
||||
|
||||
<roman@kiseliov.ru>
|
||||
Subject: pyExcelerator
|
||||
+113
-63
@@ -1,98 +1,148 @@
|
||||
Tabbed: format-agnostic tabular dataset library
|
||||
Tablib: format-agnostic tabular dataset library
|
||||
===============================================
|
||||
|
||||
::
|
||||
|
||||
_____ ______ ______ _________
|
||||
__ /_______ ____ /_ ___ /_ _____ ______ /
|
||||
_ __/_ __ `/__ __ \__ __ \_ _ \_ __ /
|
||||
/ /_ / /_/ / _ /_/ /_ /_/ // __// /_/ /
|
||||
\__/ \__,_/ /_.___/ /_.___/ \___/ \__,_/
|
||||
|
||||
.. *Tabbed is under active documentation-driven development.*
|
||||
_____ ______ ___________ ______
|
||||
__ /_______ ____ /_ ___ /___(_)___ /_
|
||||
_ __/_ __ `/__ __ \__ / __ / __ __ \
|
||||
/ /_ / /_/ / _ /_/ /_ / _ / _ /_/ /
|
||||
\__/ \__,_/ /_.___/ /_/ /_/ /_.___/
|
||||
|
||||
|
||||
Tabbed is a format-agnostic tabular dataset library, written in Python.
|
||||
It is a full python module which doubles as a CLI application for quick
|
||||
dataset conversions.
|
||||
|
||||
Formats supported:
|
||||
Tablib is a format-agnostic tabular dataset library, written in Python.
|
||||
|
||||
- JSON
|
||||
- YAML
|
||||
- Excel
|
||||
- CSV
|
||||
.. - HTML
|
||||
Output formats supported:
|
||||
|
||||
At this time, Tabbed supports the **export** of it's powerful Dataset object instances into any of the above formats. Import is underway.
|
||||
- Excel (Sets + Books)
|
||||
- JSON (Sets + Books)
|
||||
- YAML (Sets + Books)
|
||||
- HTML (Sets)
|
||||
- TSV (Sets)
|
||||
- CSV (Sets)
|
||||
|
||||
Please note that tabbed *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.)
|
||||
|
||||
|
||||
Features
|
||||
Overview
|
||||
--------
|
||||
|
||||
.. Convert datafile formats via API: ::
|
||||
..
|
||||
.. tablib.source(filename='data.csv').export('data.json')
|
||||
`tablib.Dataset()`
|
||||
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
|
||||
-----
|
||||
|
||||
.. Convert datafile formats via CLI: ::
|
||||
..
|
||||
.. $ tabbed data.csv data.json
|
||||
|
||||
.. Convert data formats via CLI pipe interface: ::
|
||||
..
|
||||
.. $ curl http://domain.dev/dataset.json | tabbed --to excel | gist -p
|
||||
|
||||
|
||||
Populate fresh data files: ::
|
||||
|
||||
headers = ('first_name', 'last_name', 'gpa')
|
||||
headers = ('first_name', 'last_name')
|
||||
|
||||
data = [
|
||||
('John', 'Adams', 4.0),
|
||||
('George', 'Washington', 2.6),
|
||||
('Henry', 'Ford', 2.3)
|
||||
('John', 'Adams'),
|
||||
('George', 'Washington')
|
||||
]
|
||||
|
||||
data = tablib.Dataset(*data, headers=headers)
|
||||
|
||||
# Establish file location and save
|
||||
data.save('test.xls')
|
||||
|
||||
|
||||
Intelligently add new rows: ::
|
||||
|
||||
data.adppend('Bob', 'Dylan', 3.2)
|
||||
|
||||
print data.headers
|
||||
# >>> ('first_name', 'last_name', 'gpa')
|
||||
|
||||
>>> data.append(('Henry', 'Ford'))
|
||||
|
||||
Intelligently add new columns: ::
|
||||
|
||||
>>> data.append(col=(90, 67, 83), header='age')
|
||||
|
||||
Slice rows: ::
|
||||
|
||||
print data[0:1]
|
||||
# >>> [('John', 'Adams', 4.0), ('George', 'Washington', 2.6)]
|
||||
>>> print data[:2]
|
||||
[('John', 'Adams', 90), ('George', 'Washington', 67)]
|
||||
|
||||
|
||||
.. Slice columns by header: ::
|
||||
..
|
||||
.. print data['first_name']
|
||||
.. # >>> ['John', 'George', 'Henry']
|
||||
..
|
||||
Slice columns by header: ::
|
||||
|
||||
Manipulate rows by index: ::
|
||||
>>> print data['first_name']
|
||||
['John', 'George', 'Henry']
|
||||
|
||||
del data[0]
|
||||
print data[0:1]
|
||||
# >>> [('George', 'Washington', 2.6), ('Henry', 'Ford', 2.3)]
|
||||
|
||||
.. # Update saved file
|
||||
.. data.save()
|
||||
|
||||
Easily delete rows: ::
|
||||
|
||||
.. Export to various formats: ::
|
||||
..
|
||||
.. # Save copy as CSV
|
||||
.. data.export('backup.csv')
|
||||
>>> del data[1]
|
||||
|
||||
Exports
|
||||
-------
|
||||
|
||||
Drumroll please...........
|
||||
|
||||
JSON!
|
||||
+++++
|
||||
::
|
||||
|
||||
>>> print data.json
|
||||
[
|
||||
{
|
||||
"last_name": "Adams",
|
||||
"age": 90,
|
||||
"first_name": "John"
|
||||
},
|
||||
{
|
||||
"last_name": "Ford",
|
||||
"age": 83,
|
||||
"first_name": "Henry"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
YAML!
|
||||
+++++
|
||||
::
|
||||
|
||||
>>> print data.yaml
|
||||
- {age: 90, first_name: John, last_name: Adams}
|
||||
- {age: 83, first_name: Henry, last_name: Ford}
|
||||
|
||||
CSV...
|
||||
++++++
|
||||
::
|
||||
|
||||
>>> print data.csv
|
||||
first_name,last_name,age
|
||||
John,Adams,90
|
||||
Henry,Ford,83
|
||||
|
||||
EXCEL!
|
||||
++++++
|
||||
::
|
||||
|
||||
>>> open('people.xls', 'wb').write(data.xls)
|
||||
|
||||
It's that easy.
|
||||
|
||||
|
||||
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_.
|
||||
|
||||
|
||||
Roadmap
|
||||
-------
|
||||
- Python 2.4, 3.0, 3.1, 3.2 Support
|
||||
- Tablib.ext namespace
|
||||
|
||||
.. _`the repository`: http://github.com/kennethreitz/tablib
|
||||
.. _AUTHORS: http://github.com/kennethreitz/tablib/blob/master/AUTHORS
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
* Add seperator support to HTML out
|
||||
* Hooks System
|
||||
- pre/post-append
|
||||
- pre/post-import
|
||||
- pre/post-export
|
||||
* Big Data
|
||||
* Backwards-compatible OrderedDict support
|
||||
* Write more exhausive unit-tests.
|
||||
* Write stress tests.
|
||||
* Make CSV write customizable.
|
||||
* Integrate django-tablib
|
||||
* Mention django-tablib in Documention
|
||||
* Dataset title usage in documentation (#17)
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Tablib.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Tablib.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Tablib"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Tablib"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
make -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
@@ -0,0 +1,3 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
.DS_Store
|
||||
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
Modifications:
|
||||
|
||||
Copyright (c) 2011 Kenneth Reitz.
|
||||
|
||||
|
||||
Original Project:
|
||||
|
||||
Copyright (c) 2010 by Armin Ronacher.
|
||||
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms of the theme, with or
|
||||
without modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* 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.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
We kindly ask you to only use these themes in an unmodified manner just
|
||||
for Flask and Flask-related products, not for unrelated projects. If you
|
||||
like the visual style and want to use it for your own projects, please
|
||||
consider making some larger changes to the themes (such as changing
|
||||
font faces, sizes, colors or margins).
|
||||
|
||||
THIS THEME 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 THEME, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
krTheme Sphinx Style
|
||||
====================
|
||||
|
||||
This repository contains sphinx styles Kenneth Reitz uses in most of
|
||||
his projects. It is a drivative of Mitsuhiko's themes for Flask and Flask related
|
||||
projects. To use this style in your Sphinx documentation, follow
|
||||
this guide:
|
||||
|
||||
1. put this folder as _themes into your docs folder. Alternatively
|
||||
you can also use git submodules to check out the contents there.
|
||||
|
||||
2. add this to your conf.py: ::
|
||||
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
html_theme_path = ['_themes']
|
||||
html_theme = 'flask'
|
||||
|
||||
The following themes exist:
|
||||
|
||||
**kr**
|
||||
the standard flask documentation theme for large projects
|
||||
|
||||
**kr_small**
|
||||
small one-page theme. Intended to be used by very small addon libraries.
|
||||
|
||||
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
# flasky extensions. flasky pygments style based on tango style
|
||||
from pygments.style import Style
|
||||
from pygments.token import Keyword, Name, Comment, String, Error, \
|
||||
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
|
||||
|
||||
|
||||
class FlaskyStyle(Style):
|
||||
background_color = "#f8f8f8"
|
||||
default_style = ""
|
||||
|
||||
styles = {
|
||||
# No corresponding class for the following:
|
||||
#Text: "", # class: ''
|
||||
Whitespace: "underline #f8f8f8", # class: 'w'
|
||||
Error: "#a40000 border:#ef2929", # class: 'err'
|
||||
Other: "#000000", # class 'x'
|
||||
|
||||
Comment: "italic #8f5902", # class: 'c'
|
||||
Comment.Preproc: "noitalic", # class: 'cp'
|
||||
|
||||
Keyword: "bold #004461", # class: 'k'
|
||||
Keyword.Constant: "bold #004461", # class: 'kc'
|
||||
Keyword.Declaration: "bold #004461", # class: 'kd'
|
||||
Keyword.Namespace: "bold #004461", # class: 'kn'
|
||||
Keyword.Pseudo: "bold #004461", # class: 'kp'
|
||||
Keyword.Reserved: "bold #004461", # class: 'kr'
|
||||
Keyword.Type: "bold #004461", # class: 'kt'
|
||||
|
||||
Operator: "#582800", # class: 'o'
|
||||
Operator.Word: "bold #004461", # class: 'ow' - like keywords
|
||||
|
||||
Punctuation: "bold #000000", # class: 'p'
|
||||
|
||||
# because special names such as Name.Class, Name.Function, etc.
|
||||
# are not recognized as such later in the parsing, we choose them
|
||||
# to look the same as ordinary variables.
|
||||
Name: "#000000", # class: 'n'
|
||||
Name.Attribute: "#c4a000", # class: 'na' - to be revised
|
||||
Name.Builtin: "#004461", # class: 'nb'
|
||||
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
|
||||
Name.Class: "#000000", # class: 'nc' - to be revised
|
||||
Name.Constant: "#000000", # class: 'no' - to be revised
|
||||
Name.Decorator: "#888", # class: 'nd' - to be revised
|
||||
Name.Entity: "#ce5c00", # class: 'ni'
|
||||
Name.Exception: "bold #cc0000", # class: 'ne'
|
||||
Name.Function: "#000000", # class: 'nf'
|
||||
Name.Property: "#000000", # class: 'py'
|
||||
Name.Label: "#f57900", # class: 'nl'
|
||||
Name.Namespace: "#000000", # class: 'nn' - to be revised
|
||||
Name.Other: "#000000", # class: 'nx'
|
||||
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
|
||||
Name.Variable: "#000000", # class: 'nv' - to be revised
|
||||
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
|
||||
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
|
||||
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
|
||||
|
||||
Number: "#990000", # class: 'm'
|
||||
|
||||
Literal: "#000000", # class: 'l'
|
||||
Literal.Date: "#000000", # class: 'ld'
|
||||
|
||||
String: "#4e9a06", # class: 's'
|
||||
String.Backtick: "#4e9a06", # class: 'sb'
|
||||
String.Char: "#4e9a06", # class: 'sc'
|
||||
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
|
||||
String.Double: "#4e9a06", # class: 's2'
|
||||
String.Escape: "#4e9a06", # class: 'se'
|
||||
String.Heredoc: "#4e9a06", # class: 'sh'
|
||||
String.Interpol: "#4e9a06", # class: 'si'
|
||||
String.Other: "#4e9a06", # class: 'sx'
|
||||
String.Regex: "#4e9a06", # class: 'sr'
|
||||
String.Single: "#4e9a06", # class: 's1'
|
||||
String.Symbol: "#4e9a06", # class: 'ss'
|
||||
|
||||
Generic: "#000000", # class: 'g'
|
||||
Generic.Deleted: "#a40000", # class: 'gd'
|
||||
Generic.Emph: "italic #000000", # class: 'ge'
|
||||
Generic.Error: "#ef2929", # class: 'gr'
|
||||
Generic.Heading: "bold #000080", # class: 'gh'
|
||||
Generic.Inserted: "#00A000", # class: 'gi'
|
||||
Generic.Output: "#888", # class: 'go'
|
||||
Generic.Prompt: "#745334", # class: 'gp'
|
||||
Generic.Strong: "bold #000000", # class: 'gs'
|
||||
Generic.Subheading: "bold #800080", # class: 'gu'
|
||||
Generic.Traceback: "bold #a40000", # class: 'gt'
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
{%- extends "basic/layout.html" %}
|
||||
{%- block extrahead %}
|
||||
{{ super() }}
|
||||
{% if theme_touch_icon %}
|
||||
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
|
||||
{% endif %}
|
||||
<link media="only screen and (max-device-width: 480px)" href="{{
|
||||
pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
|
||||
{% endblock %}
|
||||
{%- block relbar2 %}{% endblock %}
|
||||
{%- block footer %}
|
||||
<div class="footer">
|
||||
© Copyright {{ copyright }}.
|
||||
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
|
||||
</div>
|
||||
{%- endblock %}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
<h3>Related Topics</h3>
|
||||
<ul>
|
||||
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
|
||||
{%- for parent in parents %}
|
||||
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
|
||||
{%- endfor %}
|
||||
{%- if prev %}
|
||||
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
|
||||
}}">{{ prev.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- if next %}
|
||||
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
|
||||
}}">{{ next.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- for parent in parents %}
|
||||
</ul></li>
|
||||
{%- endfor %}
|
||||
</ul></li>
|
||||
</ul>
|
||||
Vendored
+387
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
* flasky.css_t
|
||||
* ~~~~~~~~~~~~
|
||||
*
|
||||
* :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz.
|
||||
* :license: Flask Design License, see LICENSE for details.
|
||||
*/
|
||||
|
||||
{% set page_width = '940px' %}
|
||||
{% set sidebar_width = '220px' %}
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro';
|
||||
font-size: 17px;
|
||||
background-color: white;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: {{ page_width }};
|
||||
margin: 30px auto 0 auto;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 {{ sidebar_width }};
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
width: {{ sidebar_width }};
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 0 30px;
|
||||
}
|
||||
|
||||
img.floatingflask {
|
||||
padding: 0 0 10px 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
width: {{ page_width }};
|
||||
margin: 20px auto 30px auto;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a:hover {
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 18px 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper p.logo {
|
||||
padding: 0 0 20px 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
color: #444;
|
||||
font-size: 24px;
|
||||
font-weight: normal;
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo a,
|
||||
div.sphinxsidebar h3 a,
|
||||
div.sphinxsidebar p.logo a:hover,
|
||||
div.sphinxsidebar h3 a:hover {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #555;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #ccc;
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #004B6B;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #6D4100;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
|
||||
div.body h2 { font-size: 180%; }
|
||||
div.body h3 { font-size: 150%; }
|
||||
div.body h4 { font-size: 130%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: #ddd;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #444;
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
background: #fafafa;
|
||||
margin: 20px -30px;
|
||||
padding: 10px 30px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.admonition tt.xref, div.admonition a tt {
|
||||
border-bottom: 1px solid #fafafa;
|
||||
}
|
||||
|
||||
dd div.admonition {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.highlight {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dt:target, .highlight {
|
||||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre, tt {
|
||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
padding-right: 0.08em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
border: 1px solid #888;
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
border: 1px solid #888;
|
||||
padding: 0.25em 0.7em;
|
||||
}
|
||||
|
||||
table.field-list, table.footnote {
|
||||
border: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
margin: 15px 0;
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
background: #fdfdfd;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
table.footnote + table.footnote {
|
||||
margin-top: -15px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
table.field-list th {
|
||||
padding: 0 0.8em 0 0;
|
||||
}
|
||||
|
||||
table.field-list td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.footnote td.label {
|
||||
width: 0px;
|
||||
padding: 0.3em 0 0.3em 0.5em;
|
||||
}
|
||||
|
||||
table.footnote td {
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 0 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 10px 0 10px 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #eee;
|
||||
padding: 7px 30px;
|
||||
margin: 15px -30px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
dl pre, blockquote pre, li pre {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
dl dl pre {
|
||||
margin-left: -90px;
|
||||
padding-left: 90px;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: #FBFBFB;
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
|
||||
a.reference {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
a.footnote-reference {
|
||||
text-decoration: none;
|
||||
font-size: 0.7em;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.footnote-reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
a:hover tt {
|
||||
background: #EEE;
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* small_flask.css_t
|
||||
* ~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* :copyright: Copyright 2010 by Armin Ronacher.
|
||||
* :license: Flask Design License, see LICENSE for details.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: none;
|
||||
background: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
display: block;
|
||||
float: none;
|
||||
width: 102.5%;
|
||||
margin: 50px -30px -20px -30px;
|
||||
padding: 10px 20px;
|
||||
background: #333;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
|
||||
div.sphinxsidebar h3 a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 10px 0 20px 0;
|
||||
}
|
||||
|
||||
div.related ul,
|
||||
div.related ul li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.body {
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = flasky.css
|
||||
pygments_style = flask_theme_support.FlaskyStyle
|
||||
|
||||
[options]
|
||||
touch_icon =
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
{% extends "basic/layout.html" %}
|
||||
{% block header %}
|
||||
{{ super() }}
|
||||
{% if pagename == 'index' %}
|
||||
<div class=indexwrapper>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
{% if pagename == 'index' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{# do not display relbars #}
|
||||
{% block relbar1 %}{% endblock %}
|
||||
{% block relbar2 %}
|
||||
{% if theme_github_fork %}
|
||||
<a href="http://github.com/{{ theme_github_fork }}"><img style="position: fixed; top: 0; right: 0; border: 0;"
|
||||
src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block sidebar1 %}{% endblock %}
|
||||
{% block sidebar2 %}{% endblock %}
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* flasky.css_t
|
||||
* ~~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- flasky theme based on nature theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 17px;
|
||||
color: #000;
|
||||
background: white;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 40px auto 0 auto;
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 30px 30px;
|
||||
}
|
||||
|
||||
img.floatingflask {
|
||||
padding: 0 0 10px 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
text-align: right;
|
||||
color: #888;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
width: 650px;
|
||||
margin: 0 auto 40px auto;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #888;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.related {
|
||||
line-height: 32px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.related ul {
|
||||
padding: 0 0 0 10px;
|
||||
}
|
||||
|
||||
div.related a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #004B6B;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #6D4100;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body {
|
||||
padding-bottom: 40px; /* saved for footer */
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
{% if theme_index_logo %}
|
||||
div.indexwrapper h1 {
|
||||
text-indent: -999999px;
|
||||
background: url({{ theme_index_logo }}) no-repeat center center;
|
||||
height: {{ theme_index_logo_height }};
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
div.body h2 { font-size: 180%; }
|
||||
div.body h3 { font-size: 150%; }
|
||||
div.body h4 { font-size: 130%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: white;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #444;
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
background: #fafafa;
|
||||
margin: 20px -30px;
|
||||
padding: 10px 30px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.highlight{
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dt:target, .highlight {
|
||||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
div.warning {
|
||||
background-color: #ffe4e4;
|
||||
border: 1px solid #f66;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre, tt {
|
||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
padding-right: 0.08em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
border: 1px solid #888;
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
border: 1px solid #888;
|
||||
padding: 0.25em 0.7em;
|
||||
}
|
||||
|
||||
table.field-list, table.footnote {
|
||||
border: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
margin: 15px 0;
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
table.field-list th {
|
||||
padding: 0 0.8em 0 0;
|
||||
}
|
||||
|
||||
table.field-list td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.footnote td {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 0;
|
||||
margin: 15px -30px;
|
||||
padding: 8px;
|
||||
line-height: 1.3em;
|
||||
padding: 7px 30px;
|
||||
background: #eee;
|
||||
border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
}
|
||||
|
||||
dl pre {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: #FBFBFB;
|
||||
}
|
||||
|
||||
a:hover tt {
|
||||
background: #EEE;
|
||||
}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = flasky.css
|
||||
nosidebar = true
|
||||
pygments_style = flask_theme_support.FlaskyStyle
|
||||
|
||||
[options]
|
||||
index_logo = ''
|
||||
index_logo_height = 120px
|
||||
github_fork = ''
|
||||
@@ -0,0 +1,64 @@
|
||||
.. _api:
|
||||
|
||||
===
|
||||
API
|
||||
===
|
||||
|
||||
|
||||
.. module:: tablib
|
||||
|
||||
This part of the documentation covers all the interfaces of Tablib. For
|
||||
parts where Tablib depends on external libraries, we document the most
|
||||
important right here and provide links to the canonical documentation.
|
||||
|
||||
|
||||
--------------
|
||||
Dataset Object
|
||||
--------------
|
||||
|
||||
|
||||
.. autoclass:: Dataset
|
||||
:inherited-members:
|
||||
|
||||
|
||||
---------------
|
||||
Databook Object
|
||||
---------------
|
||||
|
||||
|
||||
.. autoclass:: Databook
|
||||
:inherited-members:
|
||||
|
||||
|
||||
|
||||
---------
|
||||
Functions
|
||||
---------
|
||||
|
||||
|
||||
.. autofunction:: detect
|
||||
|
||||
.. autofunction:: import_set
|
||||
|
||||
|
||||
----------
|
||||
Exceptions
|
||||
----------
|
||||
|
||||
|
||||
.. class:: InvalidDatasetType
|
||||
|
||||
You're trying to add something that doesn't quite look right.
|
||||
|
||||
|
||||
.. class:: InvalidDimensions
|
||||
|
||||
You're trying to add something that doesn't quite fit right.
|
||||
|
||||
|
||||
.. class:: UnsupportedFormat
|
||||
|
||||
You're trying to add something that doesn't quite taste right.
|
||||
|
||||
|
||||
Now, go start some :ref:`Tablib Development <development>`.
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Tablib documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue Oct 5 15:25:21 2010.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# 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.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Tablib'
|
||||
copyright = u'2011, Kenneth Reitz. Styles (modified) © 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 = '0.9.5'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'flask_theme_support.FlaskyStyle'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
html_show_sphinx = False
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Tablibdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Tablib.tex', u'Tablib Documentation',
|
||||
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
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'tablib', u'Tablib Documentation',
|
||||
[u'Kenneth Reitz'], 1)
|
||||
]
|
||||
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
html_theme_path = ['_themes']
|
||||
html_theme = 'kr'
|
||||
@@ -0,0 +1,246 @@
|
||||
.. _development:
|
||||
|
||||
Development
|
||||
===========
|
||||
|
||||
Tablib is under active development, and contributors are welcome.
|
||||
|
||||
If you have a feature request, suggestion, or bug report, please open a new issue on GitHub_. To submit patches, please send a pull request on GitHub_.
|
||||
|
||||
If you'd like to contribute, there's plenty to do. Here's a short todo list.
|
||||
|
||||
.. include:: ../TODO.rst
|
||||
|
||||
|
||||
.. _GitHub: http://github.com/kennethreitz/tablib/
|
||||
|
||||
|
||||
|
||||
.. _design:
|
||||
|
||||
---------------------
|
||||
Design Considerations
|
||||
---------------------
|
||||
|
||||
Tablib was developed with a few :pep:`20` idioms in mind.
|
||||
|
||||
#. Beautiful is better than ugly.
|
||||
#. Explicit is better than implicit.
|
||||
#. Simple is better than complex.
|
||||
#. Complex is better than complicated.
|
||||
#. Readability counts.
|
||||
|
||||
A few other things to keep in mind:
|
||||
|
||||
#. Keep your code DRY.
|
||||
#. Strive to be as simple (to use) as possible.
|
||||
|
||||
.. _scm:
|
||||
|
||||
--------------
|
||||
Source Control
|
||||
--------------
|
||||
|
||||
|
||||
Tablib source is controlled with Git_, the lean, mean, distributed source control machine.
|
||||
|
||||
The repository is publicly accessable.
|
||||
|
||||
``git clone git://github.com/kennethreitz/tablib.git``
|
||||
|
||||
The project is hosted both on **GitHub** and **git.kennethreitz.com**.
|
||||
|
||||
|
||||
GitHub:
|
||||
http://github.com/kennethreitz/tablib
|
||||
"Mirror":
|
||||
http://git.kennethreitz.com/projects/tablib
|
||||
|
||||
|
||||
Git Branch Structure
|
||||
++++++++++++++++++++
|
||||
|
||||
Feature / Hotfix / Release branches follow a `Successful Git Branching Model`_ . Git-flow_ is a great tool for managing the repository. I highly recommend it.
|
||||
|
||||
``develop``
|
||||
The "next release" branch. Likely unstable.
|
||||
``master``
|
||||
Current production release (|version|) on PyPi.
|
||||
``gh-pages``
|
||||
Current release of http://tablib.org.
|
||||
|
||||
Each release is tagged.
|
||||
|
||||
When submitting patches, please place your feature/change in its own branch prior to opening a pull reqeust on GitHub_.
|
||||
|
||||
|
||||
.. _Git: http://git-scm.org
|
||||
.. _`Successful Git Branching Model`: http://nvie.com/posts/a-successful-git-branching-model/
|
||||
.. _git-flow: http://github.com/nvie/gitflow
|
||||
|
||||
|
||||
.. _newformats:
|
||||
|
||||
------------------
|
||||
Adding New Formats
|
||||
------------------
|
||||
|
||||
Tablib welcomes new format additions! Format suggestions include:
|
||||
|
||||
* Tab Separated Values
|
||||
* MySQL Dump
|
||||
* HTML Table
|
||||
|
||||
|
||||
Coding by Convention
|
||||
++++++++++++++++++++
|
||||
|
||||
Tablib features a micro-framework for adding format support. The easiest way to understand it is to use it. So, let's define our own format, named *xxx*.
|
||||
|
||||
1. Write a new format interface.
|
||||
|
||||
:class:`tablib.core` follows a simple pattern for automatically utilizing your format throughout Tablib. Function names are crucial.
|
||||
|
||||
Example **tablib/formats/_xxx.py**: ::
|
||||
|
||||
title = 'xxx'
|
||||
|
||||
def export_set(dset):
|
||||
....
|
||||
# returns string representation of given dataset
|
||||
|
||||
def export_book(dbook):
|
||||
....
|
||||
# returns string representation of given databook
|
||||
|
||||
def import_set(dset, in_stream):
|
||||
...
|
||||
# populates given Dataset with given datastream
|
||||
|
||||
def import_book(dbook, in_stream):
|
||||
...
|
||||
# returns Databook instance
|
||||
|
||||
def detect(stream):
|
||||
...
|
||||
# returns True if given stream is parsable as xxx
|
||||
|
||||
.. 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 respective functions. Appropriate errors will be raised.
|
||||
|
||||
2.
|
||||
|
||||
Add your new format module to the :class:`tablib.formats.avalable` tuple.
|
||||
|
||||
3.
|
||||
Add a mock property to the :class:`Dataset <tablib.Dataset>` class with verbose `reStructured Text`_ docstring. This alleviates IDE confusion, and allows for pretty auto-generated Sphinx_ documentation.
|
||||
|
||||
4. Write respective :ref:`tests <testing>`.
|
||||
|
||||
.. _testing:
|
||||
|
||||
--------------
|
||||
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 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. ::
|
||||
|
||||
$ ./test_tablib.py
|
||||
|
||||
|
||||
`Hudson CI`_, amongst other tools, supports Java's xUnit testing report format. Nose_ allows us to generate our own xUnit reports.
|
||||
|
||||
Installing nose is simple. ::
|
||||
|
||||
$ pip install nose
|
||||
|
||||
Once installed, we can generate our xUnit report with a single command. ::
|
||||
|
||||
$ nosetests test_tablib.py --with-xunit
|
||||
|
||||
This will generate a **nosetests.xml** file, which can then be analyzed.
|
||||
|
||||
.. _Nose: http://somethingaboutorange.com/mrl/projects/nose/
|
||||
|
||||
|
||||
|
||||
.. _hudson:
|
||||
|
||||
----------------------
|
||||
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 repository and broke the build, you will receive an email accordingly.
|
||||
|
||||
Anyone may view the build status and history at any time.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Additional reports will also be included here in the future, including :pep:`8` checks and stress reports for extremely large datasets.
|
||||
|
||||
.. _`Hudson CI`: http://hudson.dev.java.net
|
||||
.. _`Kenneth Reitz`: http://kennethreitz.com/contact-me/
|
||||
|
||||
|
||||
.. _docs:
|
||||
|
||||
-----------------
|
||||
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 throughout the module.
|
||||
|
||||
The Docs live in ``tablib/docs``. In order to build them, you will first need to install Sphinx. ::
|
||||
|
||||
$ pip install sphinx
|
||||
|
||||
|
||||
Then, to build an HTML version of the docs, simply run the following from the **docs** directory: ::
|
||||
|
||||
$ make html
|
||||
|
||||
Your ``docs/_build/html`` directory will then contain an HTML representation of the documentation, ready for publication on most web servers.
|
||||
|
||||
You can also generate the documentation in **ebpub**, **latex**, **json**, *&c* similarly.
|
||||
|
||||
.. admonition:: GitHub Pages
|
||||
|
||||
To push the documentation up to `GitHub Pages`_, you will first need to run `sphinx-to-github`_ against your ``docs/_build/html`` directory.
|
||||
|
||||
GitHub Pages are powered by an HTML generation system called Jeckyl_, which is configured to ignore files and folders that begin with "``_``" (*ie.* **_static**).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
and `sphinx-to-github`_. ::
|
||||
|
||||
Installing sphinx-to-github is simple. ::
|
||||
|
||||
$ pip install sphinx-to-github
|
||||
|
||||
Running it against the docs is even simpler. ::
|
||||
|
||||
$ sphinx-to-github _build/html
|
||||
|
||||
Move the resulting files to the **gh-pages** branch of your repository, and push it up to GitHub.
|
||||
|
||||
.. _`reStructured Text`: http://docutils.sourceforge.net/rst.html
|
||||
.. _Sphinx: http://sphinx.pocoo.org
|
||||
.. _`GitHub Pages`: http://pages.github.com
|
||||
.. _Jeckyl: http://github.com/mojombo/jekyll
|
||||
.. _`sphinx-to-github`: http://github.com/michaeljones/sphinx-to-github
|
||||
|
||||
----------
|
||||
|
||||
Make sure to check out the :ref:`API Documentation <api>`.
|
||||
@@ -0,0 +1,64 @@
|
||||
.. Tablib documentation master file, created by
|
||||
sphinx-quickstart on Tue Oct 5 15:25:21 2010.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Tablib: Pythonic Tabular Datasets
|
||||
=================================
|
||||
|
||||
Release |version|.
|
||||
|
||||
.. Contents:
|
||||
..
|
||||
.. .. toctree::
|
||||
.. :maxdepth: 2
|
||||
..
|
||||
|
||||
.. Indices and tables
|
||||
.. ==================
|
||||
..
|
||||
.. * :ref:`genindex`
|
||||
.. * :ref:`modindex`
|
||||
.. * :ref:`search`
|
||||
|
||||
|
||||
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>`.
|
||||
|
||||
User's Guide
|
||||
------------
|
||||
|
||||
This part of the documentation, which is mostly prose, begins with some background information about Tablib, then focuses on step-by-step instructions for getting the most out of your datasets.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
intro
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
install
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
tutorial
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
development
|
||||
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
If you are looking for information on a specific function, class or
|
||||
method, this part of the documentation is for you.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api
|
||||
@@ -0,0 +1,78 @@
|
||||
.. _install:
|
||||
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 enhancements <peed-extentions>`.
|
||||
|
||||
|
||||
.. _installing:
|
||||
|
||||
-----------------
|
||||
Installing Tablib
|
||||
-----------------
|
||||
|
||||
To install Tablib, it only takes one simple command. ::
|
||||
|
||||
$ pip install tablib
|
||||
|
||||
Or, if you must: ::
|
||||
|
||||
$ easy_install tablib
|
||||
|
||||
But, you really shouldn't do that.
|
||||
|
||||
|
||||
-------------------
|
||||
Download the Source
|
||||
-------------------
|
||||
|
||||
You can also install tablib from source. The latest release (|version|) is available from GitHub.
|
||||
|
||||
* tarball_
|
||||
* zipball_
|
||||
|
||||
.. _
|
||||
Once you have a copy of the source, you can embed it in your Python package, or install it into your site-packages easily. ::
|
||||
|
||||
$ python setup.py install
|
||||
|
||||
|
||||
To download the full source history from Git, see :ref:`Source Control <scm>`.
|
||||
|
||||
.. _tarball: http://github.com/kennethreitz/tablib/tarball/master
|
||||
.. _zipball: http://github.com/kennethreitz/tablib/zipball/master
|
||||
|
||||
|
||||
.. _speed-extentions:
|
||||
Speed Extentions
|
||||
----------------
|
||||
|
||||
.. versionadded:: 0.8.5
|
||||
|
||||
Tablib is partially dependent on the **pyyaml**, **simplejson**, and **xlwt** modules. To reduce installation issues, fully integrated versions of all required libraries are included in Tablib.
|
||||
|
||||
However, if performance is important to you (and it should be), you can install **pyyaml** with C extentions from PyPi. ::
|
||||
|
||||
$ pip install PyYAML
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
.. _updates:
|
||||
Staying Updated
|
||||
---------------
|
||||
|
||||
The latest version of Tablib will always be available here:
|
||||
|
||||
* PyPi: http://pypi.python.org/pypi/tablib/
|
||||
* GitHub: http://github.com/kennethreitz/tablib/
|
||||
|
||||
When a new version is available, upgrading is simple. ::
|
||||
|
||||
$ pip install tablib --upgrade
|
||||
|
||||
|
||||
Now, go get a :ref:`Quick Start <quickstart>`.
|
||||
@@ -0,0 +1,84 @@
|
||||
.. _intro:
|
||||
|
||||
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/export.
|
||||
|
||||
|
||||
Philosphy
|
||||
---------
|
||||
|
||||
Tablib was developed with a few :pep:`20` idioms in mind.
|
||||
|
||||
|
||||
#. Beautiful is better than ugly.
|
||||
#. Explicit is better than implicit.
|
||||
#. Simple is better than complex.
|
||||
#. Complex is better than complicated.
|
||||
#. Readability counts.
|
||||
|
||||
All contributions to Tablib should keep these important rules in mind.
|
||||
|
||||
.. _mit:
|
||||
|
||||
MIT License
|
||||
-----------
|
||||
|
||||
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 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`_.
|
||||
|
||||
.. _`GPL Licensed`: http://www.opensource.org/licenses/gpl-license.php
|
||||
.. _`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:
|
||||
|
||||
Pythons Supported
|
||||
-----------------
|
||||
|
||||
At this time, the following Python platforms are officially supported:
|
||||
|
||||
* 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.
|
||||
|
||||
|
||||
|
||||
|
||||
Now, go :ref:`Install Tablib <install>`.
|
||||
@@ -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
|
||||
@@ -0,0 +1,353 @@
|
||||
.. _quickstart:
|
||||
|
||||
==========
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
|
||||
.. module:: tablib
|
||||
|
||||
|
||||
Eager to get started? This page gives a good introduction in how to get started with Tablib. This assumes you already have Tablib installed. If you do not, head over to the :ref:`Installation <install>` section.
|
||||
|
||||
First, make sure that:
|
||||
|
||||
* Tablib is :ref:`installed <install>`
|
||||
* Tablib is :ref:`up-to-date <updates>`
|
||||
|
||||
|
||||
Lets gets started with some simple use cases and examples.
|
||||
|
||||
|
||||
|
||||
------------------
|
||||
Creating a Dataset
|
||||
------------------
|
||||
|
||||
|
||||
A :class:`Dataset <tablib.Dataset>` is nothing more than what its name implies—a set of data.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
-----------
|
||||
Adding Rows
|
||||
-----------
|
||||
|
||||
|
||||
Let's say you want to collect a simple list of names. ::
|
||||
|
||||
# collection of names
|
||||
names = ['Kenneth Reitz', 'Bessie Monke']
|
||||
|
||||
for name in names:
|
||||
# split name appropriately
|
||||
fname, lname = name.split()
|
||||
|
||||
# add names to Dataset
|
||||
data.append([fname, lname])
|
||||
|
||||
You can get a nice, Pythonic view of the dataset at any time with :class:`Dataset.dict`.
|
||||
|
||||
>>> data.dict
|
||||
[('Kenneth', 'Reitz'), ('Bessie', 'Monke')]
|
||||
|
||||
|
||||
|
||||
--------------
|
||||
Adding Headers
|
||||
--------------
|
||||
|
||||
|
||||
It's time enhance our :class:`Dataset` by giving our columns some titles. To do so, set :class:`Dataset.headers`. ::
|
||||
|
||||
data.headers = ['First Name', 'Last Name']
|
||||
|
||||
Now our data looks a little different. ::
|
||||
|
||||
>>> data.dict
|
||||
[{'Last Name': 'Reitz', 'First Name': 'Kenneth'}, {'Last Name': 'Monke', 'First Name': 'Bessie'}]
|
||||
|
||||
|
||||
|
||||
|
||||
--------------
|
||||
Adding Columns
|
||||
--------------
|
||||
|
||||
|
||||
Now that we have a basic :class:`Dataset` in place, let's add a column of **ages** to it. ::
|
||||
|
||||
data.append(col=[22, 20], header='Age')
|
||||
|
||||
Let's view the data now. ::
|
||||
|
||||
>>> data.dict
|
||||
[{'Last Name': 'Reitz', 'First Name': 'Kenneth', 'Age': 22}, {'Last Name': 'Monke', 'First Name': 'Bessie', 'Age': 20}]
|
||||
|
||||
It's that easy.
|
||||
|
||||
|
||||
--------------
|
||||
Exporting Data
|
||||
--------------
|
||||
|
||||
Tablib's killer feature is the ability to export your :class:`Dataset` objects into a number of formats.
|
||||
|
||||
**Comma-Separated Values** ::
|
||||
|
||||
>>> data.csv
|
||||
Last Name,First Name,Age
|
||||
Reitz,Kenneth,22
|
||||
Monke,Bessie,20
|
||||
|
||||
**JavaScript Object Notation** ::
|
||||
|
||||
>>> data.json
|
||||
[{"Last Name": "Reitz", "First Name": "Kenneth", "Age": 22}, {"Last Name": "Monke", "First Name": "Bessie", "Age": 20}]
|
||||
|
||||
|
||||
**YAML Ain't Markup Language** ::
|
||||
|
||||
>>> data.yaml
|
||||
- {Age: 22, First Name: Kenneth, Last Name: Reitz}
|
||||
- {Age: 20, First Name: Bessie, Last Name: Monke}
|
||||
|
||||
|
||||
**Microsoft Excel** ::
|
||||
|
||||
>>> data.xls
|
||||
<censored binary data>
|
||||
|
||||
|
||||
------------------------
|
||||
Selecting Rows & Columns
|
||||
------------------------
|
||||
|
||||
|
||||
You can slice and dice your data, just like a standard Python list. ::
|
||||
|
||||
>>> data[0]
|
||||
('Kenneth', 'Reitz', 22)
|
||||
|
||||
|
||||
If we had a set of data consisting of thousands of rows, it could be useful to get a list of values in a column.
|
||||
To do so, we access the :class:`Dataset` as if it were a standard Python dictionary. ::
|
||||
|
||||
>>> data['First Name']
|
||||
['Kenneth', 'Bessie']
|
||||
|
||||
Let's find the average age. ::
|
||||
|
||||
>>> ages = data['Age']
|
||||
>>> float(sum(ages)) / len(ages)
|
||||
21.0
|
||||
|
||||
|
||||
|
||||
-----------------------
|
||||
Removing Rows & Columns
|
||||
-----------------------
|
||||
|
||||
It's easier than you could imagine. ::
|
||||
|
||||
>>> del data['Col Name']
|
||||
|
||||
::
|
||||
|
||||
>>> del data[0:12]
|
||||
|
||||
|
||||
==============
|
||||
Advanced Usage
|
||||
==============
|
||||
|
||||
|
||||
This part of the documentation services to give you an idea that are otherwise hard to extract from the :ref:`API Documentation <api>`
|
||||
|
||||
And now for something completely different.
|
||||
|
||||
|
||||
.. _dyncols:
|
||||
|
||||
---------------
|
||||
Dynamic Columns
|
||||
---------------
|
||||
|
||||
.. versionadded:: 0.8.3
|
||||
|
||||
Thanks to Josh Ourisman, Tablib now supports adding dynamic columns. A dynamic column is a single callable object (*ie.* a function).
|
||||
|
||||
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. ::
|
||||
|
||||
>>> data.yaml
|
||||
- {Age: 22, First Name: Kenneth, Grade: 0.6, Last Name: Reitz}
|
||||
- {Age: 20, First Name: Bessie, Grade: 0.75, Last Name: Monke}
|
||||
|
||||
|
||||
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.
|
||||
|
||||
For example, we can use the data available in the row to guess the gender of a student. ::
|
||||
|
||||
def guess_gender(row):
|
||||
"""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:
|
||||
return 'Female'
|
||||
else:
|
||||
return 'Unknown'
|
||||
|
||||
Adding this function to our dataset as a dynamic column would result in: ::
|
||||
|
||||
>>> data.yaml
|
||||
- {Age: 22, First Name: Kenneth, Gender: Male, Last Name: Reitz}
|
||||
- {Age: 20, First Name: Bessie, Gender: Female, Last Name: Monke}
|
||||
|
||||
|
||||
.. _tags:
|
||||
|
||||
----------------------------
|
||||
Filtering Datasets with Tags
|
||||
----------------------------
|
||||
|
||||
.. versionadded:: 0.9.0
|
||||
|
||||
|
||||
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. ::
|
||||
|
||||
students = tablib.Dataset()
|
||||
|
||||
students.headers = ['first', 'last']
|
||||
|
||||
students.append(['Kenneth', 'Reitz'], tags=['male', 'technical'])
|
||||
students.append(['Bessie', 'Monke'], tags=['female', 'creative'])
|
||||
|
||||
Now that we have extra meta-data on our rows, we can use easily filter our :class:`Dataset`. Let's just see Male students. ::
|
||||
|
||||
|
||||
>>> data.filter(['male']).yaml
|
||||
- {first: Kenneth, Last: Reitz}
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Let's say we have 3 different :class:`Datasets <Dataset>`. All we have to do is add then to a :class:`Databook` object... ::
|
||||
|
||||
book = tablib.Databook([data1, data2, data3])
|
||||
|
||||
... and export to Excel just like :class:`Datasets <Dataset>`. ::
|
||||
|
||||
with open('students.xls', 'wb') as f:
|
||||
f.write(book.xls)
|
||||
|
||||
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:
|
||||
|
||||
----------
|
||||
Seperators
|
||||
----------
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
When, it's often useful to create a blank row containing information on the upcoming data. So,
|
||||
|
||||
|
||||
|
||||
::
|
||||
|
||||
daniel_tests = [
|
||||
('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')
|
||||
|
||||
for test_row in daniel_tests:
|
||||
tests.append(test_row)
|
||||
|
||||
# Susie's Tests
|
||||
tests.append_seperator('Susie\'s Scores')
|
||||
|
||||
for test_row in suzie_tests:
|
||||
tests.append(test_row)
|
||||
|
||||
# Write spreadsheet to disk
|
||||
with open('grades.xls', 'wb') as f:
|
||||
f.write(tests.xls)
|
||||
|
||||
The resulting **tests.xls** will have the following layout:
|
||||
|
||||
|
||||
Daniel's Scores:
|
||||
* '11/24/09', 'Math 101 Mid-term Exam', 56.
|
||||
* '05/24/10', 'Math 101 Final Exam', 62.
|
||||
|
||||
Suzie's Scores:
|
||||
* '11/24/09', 'Math 101 Mid-term Exam', 56.
|
||||
* '05/24/10', 'Math 101 Final Exam', 62.
|
||||
|
||||
|
||||
|
||||
.. admonition:: Format Support
|
||||
|
||||
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>`.
|
||||
Vendored
+13
-23
@@ -1,27 +1,17 @@
|
||||
import os
|
||||
from fabric.api import *
|
||||
|
||||
|
||||
def scrub():
|
||||
""" Death to the bytecode! """
|
||||
local("rm -fr dist build")
|
||||
local("find . -name \"*.pyc\" -exec rm '{}' ';'")
|
||||
|
||||
def test():
|
||||
""" Test parsing! """
|
||||
local("rm output/*")
|
||||
local("./strata.py --nsanity_files 'strata/tests/samples/nsanity' -d")
|
||||
|
||||
def build():
|
||||
""" Build application"""
|
||||
pass
|
||||
|
||||
def init():
|
||||
""" Initialize Environment """
|
||||
# TODO: Possibly add Virtual Environment?
|
||||
local("sudo pip install -r REQUIREMENTS")
|
||||
|
||||
if __name__ == '__main__':
|
||||
# TODO: Remove (for testing purposes)
|
||||
# TODO: [Possibly] add doctests
|
||||
test()
|
||||
|
||||
""" Death to the bytecode! """
|
||||
local('rm -fr dist build')
|
||||
local("find . -name \"*.pyc\" -exec rm '{}' ';'")
|
||||
|
||||
def docs():
|
||||
"""Build docs."""
|
||||
os.system('make dirhtml')
|
||||
os.chdir('_build/dirhtml')
|
||||
os.system('sphinxtogithub .')
|
||||
os.system('git add -A')
|
||||
os.system('git commit -m \'documentation update\'')
|
||||
os.system('git push origin gh-pages')
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tablib
|
||||
|
||||
from distutils.core import setup
|
||||
|
||||
@@ -15,22 +15,39 @@ if sys.argv[-1] == "publish":
|
||||
publish()
|
||||
sys.exit()
|
||||
|
||||
setup(name='tablib',
|
||||
version=tablib.__version__,
|
||||
description='Python wrapper for Gist API',
|
||||
long_description=open('README.rst').read() + '\n\n' +
|
||||
open('HISTORY.rst').read(),
|
||||
author='Kenneth Reitz',
|
||||
author_email='me@kennethreitz.com',
|
||||
url='http://github.com/kennethreitz/tabbed',
|
||||
packages=['tablib'],
|
||||
license='MIT',
|
||||
classifiers=(
|
||||
"Development Status :: 4 - Beta",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 2.5",
|
||||
"Programming Language :: Python :: 2.6",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
)
|
||||
)
|
||||
required = []
|
||||
|
||||
if sys.version_info[:2] < (2,6):
|
||||
required.append('simplejson')
|
||||
|
||||
setup(
|
||||
name='tablib',
|
||||
version='0.9.5',
|
||||
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.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',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.0',
|
||||
'Programming Language :: Python :: 3.1',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Tabbed
|
||||
Copyright (c) 2010 Kenneth Reitz. MIT License.
|
||||
"""
|
||||
|
||||
import tablib.cli
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
tablib.cli.start()
|
||||
+16
-1
@@ -1 +1,16 @@
|
||||
from core import *
|
||||
""" Tablib.
|
||||
"""
|
||||
|
||||
import sys
|
||||
if sys.version_info[0:1] > (2, 5):
|
||||
from tablib.core import (
|
||||
Databook, Dataset, detect, import_set,
|
||||
InvalidDatasetType, InvalidDimensions, UnsupportedFormat
|
||||
)
|
||||
|
||||
else:
|
||||
from tablib.core25 import (
|
||||
Databook, Dataset, detect, import_set,
|
||||
InvalidDatasetType, InvalidDimensions, UnsupportedFormat
|
||||
)
|
||||
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
""" Tabbed CLI Inteface Application
|
||||
"""
|
||||
|
||||
import io
|
||||
import sys
|
||||
|
||||
from helpers import *
|
||||
import tablib.core
|
||||
|
||||
from packages import opster
|
||||
|
||||
|
||||
FORMATS = ('json', 'yaml', 'xls', 'csv', 'html')
|
||||
|
||||
opts = []
|
||||
|
||||
opts.append(('v', 'version', False, 'Report tabbed version'))
|
||||
|
||||
for format in FORMATS:
|
||||
opts.append(('', format, False, 'Output to %s' % (format.upper())))
|
||||
|
||||
|
||||
|
||||
@opster.command(options=opts, usage='[FILE] [--FORMAT | FILE]')
|
||||
def start(in_file=None, out_file=None, **opts):
|
||||
"""Covertly convert dataset formats"""
|
||||
|
||||
opts = Object(**opts)
|
||||
|
||||
if opts.version:
|
||||
print('Tabbed, Ver. %s' % tabbed.core.__version__)
|
||||
sys.sys.exit(0)
|
||||
|
||||
stdin = piped()
|
||||
|
||||
if stdin:
|
||||
print stdin
|
||||
|
||||
elif in_file:
|
||||
|
||||
try:
|
||||
in_file = io.open(in_file, 'r')
|
||||
except Exception, e:
|
||||
print(' %s cannot be read.' % in_file)
|
||||
sys.exit(65)
|
||||
|
||||
file_ext = in_file.name.split('.')[-1]
|
||||
|
||||
if file_ext.lower() in FORMATS:
|
||||
setattr(opts, file_ext, True)
|
||||
else:
|
||||
print('Import format not supported.')
|
||||
sys.exit(65)
|
||||
else:
|
||||
print('Please provide input.')
|
||||
sys.exit(65)
|
||||
|
||||
|
||||
|
||||
_formats_sum = sum(opts[f] for f in FORMATS)
|
||||
|
||||
# Multiple output formats given
|
||||
if _formats_sum > 1:
|
||||
print('Please specify a single output format.')
|
||||
sys.exit(64)
|
||||
|
||||
# No output formats given
|
||||
elif _formats_sum < 1:
|
||||
print('Please specify an output format.')
|
||||
sys.exit(64)
|
||||
|
||||
|
||||
# fetch options.formats list
|
||||
# if sum(()) > 1
|
||||
# log only one data format please
|
||||
# if sum of formats == 0, specity format
|
||||
|
||||
# look for filename
|
||||
|
||||
print opts.__dict__
|
||||
print in_file
|
||||
print out_file
|
||||
+769
-172
@@ -1,219 +1,816 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tablib.core
|
||||
~~~~~~~~~~~
|
||||
|
||||
# _____ ______ ______ _________
|
||||
# __ /_______ ____ /_ ___ /_ _____ ______ /
|
||||
# _ __/_ __ `/__ __ \__ __ \_ _ \_ __ /
|
||||
# / /_ / /_/ / _ /_/ /_ /_/ // __// /_/ /
|
||||
# \__/ \__,_/ /_.___/ /_.___/ \___/ \__,_/
|
||||
This module implements the central Tablib objects.
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:license: MIT, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import csv
|
||||
import cStringIO
|
||||
import os
|
||||
from copy import copy
|
||||
from operator import itemgetter
|
||||
|
||||
from helpers import *
|
||||
from packages import simplejson as json
|
||||
from packages import xlwt
|
||||
from tablib import formats
|
||||
import collections
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError, why:
|
||||
from packages import yaml
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from tablib.packages.ordereddict import OrderedDict
|
||||
|
||||
|
||||
|
||||
__all__ = ['Dataset', 'source']
|
||||
|
||||
__version__ = '0.0.4'
|
||||
__build__ = '0x000004'
|
||||
__title__ = 'tablib'
|
||||
__version__ = '0.9.4'
|
||||
__build__ = 0x000904
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'MIT'
|
||||
__copyright__ = 'Copyright 2010 Kenneth Reitz'
|
||||
__copyright__ = 'Copyright 2011 Kenneth Reitz'
|
||||
__docformat__ = 'restructuredtext'
|
||||
|
||||
|
||||
FILE_EXTENSIONS = ('csv', 'json', 'xls', 'yaml')
|
||||
class Row(object):
|
||||
"""Internal Row object. Mainly used for filtering."""
|
||||
|
||||
__slots__ = ['tuple', '_row', 'tags']
|
||||
|
||||
def __init__(self, row=list(), tags=list()):
|
||||
self._row = list(row)
|
||||
self.tags = list(tags)
|
||||
|
||||
def __iter__(self):
|
||||
return (col for col in self._row)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._row)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._row)
|
||||
|
||||
def __getslice__(self, i, j):
|
||||
return self._row[i,j]
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self._row[i]
|
||||
|
||||
def __setitem__(self, i, value):
|
||||
self._row[i] = value
|
||||
|
||||
def __delitem__(self, i):
|
||||
del self._row[i]
|
||||
|
||||
def __getstate__(self):
|
||||
return {slot: [getattr(self, slot) for slot in self.__slots__]}
|
||||
|
||||
def __setstate__(self, state):
|
||||
for (k, v) in list(state.items()): setattr(self, k, v)
|
||||
|
||||
def append(self, value):
|
||||
self._row.append(value)
|
||||
|
||||
def insert(self, index, value):
|
||||
self._row.insert(index, value)
|
||||
|
||||
def __contains__(self, item):
|
||||
return (item in self._row)
|
||||
|
||||
@property
|
||||
def tuple(self):
|
||||
'''Tuple representation of :class:`Row`.'''
|
||||
return tuple(self._row)
|
||||
|
||||
@property
|
||||
def list(self):
|
||||
'''List representation of :class:`Row`.'''
|
||||
return list(self._row)
|
||||
|
||||
def has_tag(self, tag):
|
||||
"""Returns true if current row contains tag."""
|
||||
|
||||
if tag == None:
|
||||
return False
|
||||
elif isinstance(tag, str):
|
||||
return (tag in self.tags)
|
||||
else:
|
||||
return bool(len(set(tag) & set(self.tags)))
|
||||
|
||||
|
||||
|
||||
|
||||
class Dataset(object):
|
||||
"""Epic Tabular-Dataset object. """
|
||||
"""The :class:`Dataset` object is the heart of Tablib. It provides all core
|
||||
functionality.
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._data = None
|
||||
self._saved_file = None
|
||||
self._saved_format = None
|
||||
Usually you create a :class:`Dataset` instance in your main module, and append
|
||||
rows and columns as you collect data. ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.headers = ('name', 'age')
|
||||
|
||||
for (name, age) in some_collector():
|
||||
data.append((name, age))
|
||||
|
||||
You can also set rows and headers upon instantiation. This is useful if dealing
|
||||
with dozens or hundres of :class:`Dataset` objects. ::
|
||||
|
||||
headers = ('first_name', 'last_name')
|
||||
data = [('John', 'Adams'), ('George', 'Washington')]
|
||||
|
||||
data = tablib.Dataset(*data, headers=headers)
|
||||
|
||||
|
||||
self._data = list(args)
|
||||
|
||||
try:
|
||||
self.headers = kwargs['headers']
|
||||
except KeyError, why:
|
||||
self.headers = None
|
||||
|
||||
try:
|
||||
self.title = kwargs['title']
|
||||
except KeyError, why:
|
||||
self.title = None
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return self.height
|
||||
|
||||
|
||||
def __getitem__(self, key):
|
||||
if is_string(key):
|
||||
if key in self.headers:
|
||||
pos = self.headers.index(key) # get 'key' index from each data
|
||||
return [row[pos] for row in self._data]
|
||||
else:
|
||||
raise KeyError
|
||||
else:
|
||||
return self._data[key]
|
||||
:param \*args: (optional) list of rows to populate Dataset
|
||||
:param headers: (optional) list strings for Dataset header row
|
||||
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._validate(value)
|
||||
self._data[key] = tuple(value)
|
||||
.. admonition:: Format Attributes Definition
|
||||
|
||||
If you look at the code, the various output/import formats are not
|
||||
defined within the :class:`Dataset` object. To add support for a new format, see
|
||||
:ref:`Adding New Formats <newformats>`.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._data = list(Row(arg) for arg in args)
|
||||
self.__headers = None
|
||||
|
||||
# ('title', index) tuples
|
||||
self._separators = []
|
||||
|
||||
# (column, callback) tuples
|
||||
self._formatters = []
|
||||
|
||||
try:
|
||||
self.headers = kwargs['headers']
|
||||
except KeyError:
|
||||
self.headers = None
|
||||
|
||||
try:
|
||||
self.title = kwargs['title']
|
||||
except KeyError:
|
||||
self.title = None
|
||||
|
||||
self._register_formats()
|
||||
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._data[key]
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
if self.title:
|
||||
return '<%s dataset>' % (self.title.lower())
|
||||
else:
|
||||
return '<dataset object>'
|
||||
def __len__(self):
|
||||
return self.height
|
||||
|
||||
|
||||
def _validate(self, row=None, safety=False):
|
||||
"""Assures size of every row in dataset is of proper proportions."""
|
||||
if row:
|
||||
is_valid = (len(row) == self.width) if self.width else True
|
||||
else:
|
||||
is_valid = all((len(x)== self.width for x in self._data))
|
||||
|
||||
if is_valid:
|
||||
return True
|
||||
else:
|
||||
if not safety:
|
||||
raise InvalidDimensions
|
||||
return False
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, str):
|
||||
if key in self.headers:
|
||||
pos = self.headers.index(key) # get 'key' index from each data
|
||||
return [row[pos] for row in self._data]
|
||||
else:
|
||||
raise KeyError
|
||||
else:
|
||||
_results = self._data[key]
|
||||
if isinstance(_results, Row):
|
||||
return _results.tuple
|
||||
else:
|
||||
return [result.tuple for result in _results]
|
||||
|
||||
|
||||
def _package(self, dicts=True):
|
||||
"""Packages Dataset into lists of dictionaries for transmission."""
|
||||
|
||||
if self.headers:
|
||||
if dicts:
|
||||
data = [dict(zip(self.headers, data_row)) for data_row in self ._data]
|
||||
else:
|
||||
data = [list(self.headers)] + list(self._data)
|
||||
else:
|
||||
data = [list(row) for row in self._data]
|
||||
|
||||
return data
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
"""Returns the height of the Dataset."""
|
||||
return len(self._data)
|
||||
def __setitem__(self, key, value):
|
||||
self._validate(value)
|
||||
self._data[key] = Row(value)
|
||||
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
"""Returns the width of the Dataset."""
|
||||
try:
|
||||
return len(self._data[0])
|
||||
except KeyError, why:
|
||||
return 0
|
||||
def __delitem__(self, key):
|
||||
if isinstance(key, str):
|
||||
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
"""Returns JSON representation of Dataset."""
|
||||
return json.dumps(self._package())
|
||||
if key in self.headers:
|
||||
|
||||
|
||||
@property
|
||||
def yaml(self):
|
||||
"""Returns YAML representation of Dataset."""
|
||||
return yaml.dump(self._package())
|
||||
pos = self.headers.index(key)
|
||||
del self.headers[pos]
|
||||
|
||||
for i, row in enumerate(self._data):
|
||||
|
||||
del row[pos]
|
||||
self._data[i] = row
|
||||
else:
|
||||
raise KeyError
|
||||
else:
|
||||
del self._data[key]
|
||||
|
||||
|
||||
@property
|
||||
def csv(self):
|
||||
"""Returns CSV representation of Dataset."""
|
||||
stream = cStringIO.StringIO()
|
||||
_csv = csv.writer(stream)
|
||||
|
||||
for row in self._package(dicts=False):
|
||||
_csv.writerow(row)
|
||||
|
||||
return stream.getvalue()
|
||||
def __repr__(self):
|
||||
try:
|
||||
return '<%s dataset>' % (self.title.lower())
|
||||
except AttributeError:
|
||||
return '<dataset object>'
|
||||
|
||||
|
||||
@property
|
||||
def xls(self):
|
||||
"""Returns XLS representation of Dataset."""
|
||||
stream = cStringIO.StringIO()
|
||||
|
||||
wb = xlwt.Workbook()
|
||||
ws = wb.add_sheet(self.title if self.title else 'Tabbed Dataset')
|
||||
# for row in self._package(dicts=False):
|
||||
for i, row in enumerate(self._package(dicts=False)):
|
||||
for j, col in enumerate(row):
|
||||
ws.write(i, j, col)
|
||||
@classmethod
|
||||
def _register_formats(cls):
|
||||
"""Adds format properties."""
|
||||
for fmt in formats.available:
|
||||
try:
|
||||
try:
|
||||
setattr(cls, fmt.title, property(fmt.export_set, fmt.import_set))
|
||||
except AttributeError:
|
||||
setattr(cls, fmt.title, property(fmt.export_set))
|
||||
|
||||
doc = xlwt.CompoundDoc.XlsDoc()
|
||||
doc.save(stream, wb.get_biff_data())
|
||||
|
||||
return stream.getvalue()
|
||||
|
||||
|
||||
def append(self, row, index=None):
|
||||
# todo: impliment index
|
||||
self._validate(row)
|
||||
self._data.append(tuple(row))
|
||||
|
||||
|
||||
def sort_by(self, key):
|
||||
"""Sorts datastet by given key"""
|
||||
# todo: accpept string if headers, or index nubmer
|
||||
pass
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
def save(self, filename=None, format=None):
|
||||
"""Saves dataset"""
|
||||
if not format:
|
||||
# set format from filename
|
||||
# format = filename
|
||||
pass
|
||||
|
||||
if format not in FILE_EXTENSIONS:
|
||||
raise UnsupportedFormat
|
||||
|
||||
def _validate(self, row=None, col=None, safety=False):
|
||||
"""Assures size of every row in dataset is of proper proportions."""
|
||||
if row:
|
||||
is_valid = (len(row) == self.width) if self.width else True
|
||||
elif col:
|
||||
if len(col) < 1:
|
||||
is_valid = True
|
||||
else:
|
||||
is_valid = (len(col) == self.height) if self.height else True
|
||||
else:
|
||||
is_valid = all((len(x) == self.width for x in self._data))
|
||||
|
||||
# note export format
|
||||
# open file, save the bitch
|
||||
if is_valid:
|
||||
return True
|
||||
else:
|
||||
if not safety:
|
||||
raise InvalidDimensions
|
||||
return False
|
||||
|
||||
|
||||
def export(self):
|
||||
"""Exports Dataset to given filename or file-object."""
|
||||
|
||||
def _package(self, dicts=True):
|
||||
"""Packages Dataset into lists of dictionaries for transmission."""
|
||||
|
||||
_data = list(self._data)
|
||||
|
||||
# Execute formatters
|
||||
if self._formatters:
|
||||
for row_i, row in enumerate(_data):
|
||||
for col, callback in self._formatters:
|
||||
try:
|
||||
if col is None:
|
||||
for j, c in enumerate(row):
|
||||
_data[row_i][j] = callback(c)
|
||||
else:
|
||||
_data[row_i][col] = callback(row[col])
|
||||
except IndexError:
|
||||
raise InvalidDatasetIndex
|
||||
|
||||
|
||||
if self.headers:
|
||||
if dicts:
|
||||
data = [OrderedDict(list(zip(self.headers, data_row))) for data_row in _data]
|
||||
else:
|
||||
data = [list(self.headers)] + list(_data)
|
||||
else:
|
||||
data = [list(row) for row in _data]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def _clean_col(self, col):
|
||||
"""Prepares the given column for insert/append."""
|
||||
|
||||
col = list(col)
|
||||
|
||||
if self.headers:
|
||||
header = [col.pop(0)]
|
||||
else:
|
||||
header = []
|
||||
|
||||
if len(col) == 1 and isinstance(col[0], collections.Callable):
|
||||
col = list(map(col[0], self._data))
|
||||
col = tuple(header + col)
|
||||
|
||||
return col
|
||||
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
"""The number of rows currently in the :class:`Dataset`.
|
||||
Cannot be directly modified.
|
||||
"""
|
||||
return len(self._data)
|
||||
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
"""The number of columns currently in the :class:`Dataset`.
|
||||
Cannot be directly modified.
|
||||
"""
|
||||
|
||||
try:
|
||||
return len(self._data[0])
|
||||
except IndexError:
|
||||
try:
|
||||
return len(self.headers)
|
||||
except TypeError:
|
||||
return 0
|
||||
|
||||
|
||||
def _get_headers(self):
|
||||
"""An *optional* list of strings to be used for header rows and attribute names.
|
||||
|
||||
This must be set manually. The given list length must equal :class:`Dataset.width`.
|
||||
|
||||
"""
|
||||
return self.__headers
|
||||
|
||||
|
||||
def _set_headers(self, collection):
|
||||
"""Validating headers setter."""
|
||||
self._validate(collection)
|
||||
if collection:
|
||||
try:
|
||||
self.__headers = list(collection)
|
||||
except TypeError:
|
||||
raise TypeError
|
||||
else:
|
||||
self.__headers = None
|
||||
|
||||
headers = property(_get_headers, _set_headers)
|
||||
|
||||
def _get_dict(self):
|
||||
"""A native Python representation of the :class:`Dataset` object. If headers have
|
||||
been set, a list of Python dictionaries will be returned. If no headers have been set,
|
||||
a list of tuples (rows) will be returned instead.
|
||||
|
||||
A dataset object can also be imported by setting the `Dataset.dict` attribute: ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.json = '[{"last_name": "Adams","age": 90,"first_name": "John"}]'
|
||||
|
||||
"""
|
||||
return self._package()
|
||||
|
||||
|
||||
def _set_dict(self, pickle):
|
||||
"""A native Python representation of the Dataset object. If headers have been
|
||||
set, a list of Python dictionaries will be returned. If no headers have been
|
||||
set, a list of tuples (rows) will be returned instead.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.dict` attribute. ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.dict = [{'age': 90, 'first_name': 'Kenneth', 'last_name': 'Reitz'}]
|
||||
|
||||
"""
|
||||
|
||||
if not len(pickle):
|
||||
return
|
||||
|
||||
# if list of rows
|
||||
if isinstance(pickle[0], list):
|
||||
self.wipe()
|
||||
for row in pickle:
|
||||
self.append(Row(row))
|
||||
|
||||
# if list of objects
|
||||
elif isinstance(pickle[0], dict):
|
||||
self.wipe()
|
||||
self.headers = list(pickle[0].keys())
|
||||
for row in pickle:
|
||||
self.append(Row(list(row.values())))
|
||||
else:
|
||||
raise UnsupportedFormat
|
||||
|
||||
dict = property(_get_dict, _set_dict)
|
||||
|
||||
|
||||
@property
|
||||
def xls():
|
||||
"""An Excel Spreadsheet representation of the :class:`Dataset` object, with :ref:`seperators`. Cannot be set.
|
||||
|
||||
.. admonition:: Binary Warning
|
||||
|
||||
:class:`Dataset.xls` contains binary data, so make sure to write in binary mode::
|
||||
|
||||
with open('output.xls', 'wb') as f:
|
||||
f.write(data.xls)'
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@property
|
||||
def csv():
|
||||
"""A CSV representation of the :class:`Dataset` object. The top row will contain
|
||||
headers, if they have been set. Otherwise, the top row will contain
|
||||
the first row of the dataset.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.csv` attribute. ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.csv = 'age, first_name, last_name\\n90, John, Adams'
|
||||
|
||||
Import assumes (for now) that headers exist.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@property
|
||||
def tsv():
|
||||
"""A TSV representation of the :class:`Dataset` object. The top row will contain
|
||||
headers, if they have been set. Otherwise, the top row will contain
|
||||
the first row of the dataset.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.tsv` attribute. ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.tsv = 'age\tfirst_name\tlast_name\\n90\tJohn\tAdams'
|
||||
|
||||
Import assumes (for now) that headers exist.
|
||||
"""
|
||||
|
||||
@property
|
||||
def yaml():
|
||||
"""A YAML representation of the :class:`Dataset` object. If headers have been
|
||||
set, a YAML list of objects will be returned. If no headers have
|
||||
been set, a YAML list of lists (rows) will be returned instead.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.json` attribute: ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.yaml = '- {age: 90, first_name: John, last_name: Adams}'
|
||||
|
||||
Import assumes (for now) that headers exist.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@property
|
||||
def json():
|
||||
"""A JSON representation of the :class:`Dataset` object. If headers have been
|
||||
set, a JSON list of objects will be returned. If no headers have
|
||||
been set, a JSON list of lists (rows) will be returned instead.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.json` attribute: ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.json = '[{age: 90, first_name: "John", liast_name: "Adams"}]'
|
||||
|
||||
Import assumes (for now) that headers exist.
|
||||
"""
|
||||
|
||||
@property
|
||||
def html():
|
||||
"""A HTML table representation of the :class:`Dataset` object. If
|
||||
headers have been set, they will be used as table headers.
|
||||
|
||||
..notice:: This method can be used for export only.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def append(self, row=None, col=None, header=None, tags=list()):
|
||||
"""Adds a row or column to the :class:`Dataset`.
|
||||
Usage is :class:`Dataset.insert` for documentation.
|
||||
"""
|
||||
|
||||
if row is not None:
|
||||
self.insert(self.height, row=row, tags=tags)
|
||||
elif col is not None:
|
||||
self.insert(self.width, col=col, header=header)
|
||||
|
||||
|
||||
def insert_separator(self, index, text='-'):
|
||||
"""Adds a separator to :class:`Dataset` at given index."""
|
||||
|
||||
sep = (index, text)
|
||||
self._separators.append(sep)
|
||||
|
||||
|
||||
def append_separator(self, text='-'):
|
||||
"""Adds a :ref:`seperator <seperators>` to the :class:`Dataset`."""
|
||||
|
||||
# change offsets if headers are or aren't defined
|
||||
if not self.headers:
|
||||
index = self.height if self.height else 0
|
||||
else:
|
||||
index = (self.height + 1) if self.height else 1
|
||||
|
||||
self.insert_separator(index, text)
|
||||
|
||||
|
||||
def add_formatter(self, col, handler):
|
||||
"""Adds a :ref:`formatter` to the :class:`Dataset`.
|
||||
|
||||
.. versionadded:: 0.9.5
|
||||
:param col: column to. Accepts index int or header str.
|
||||
:param handler: reference to callback function to execute
|
||||
against each cell value.
|
||||
"""
|
||||
|
||||
if isinstance(col, str):
|
||||
if col in self.headers:
|
||||
col = self.headers.index(col) # get 'key' index from each data
|
||||
else:
|
||||
raise KeyError
|
||||
|
||||
if not col > self.width:
|
||||
self._formatters.append((col, handler))
|
||||
else:
|
||||
raise InvalidDatasetIndex
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def insert(self, index, row=None, col=None, header=None, tags=list()):
|
||||
"""Inserts a row or column to the :class:`Dataset` at the given index.
|
||||
|
||||
Rows and columns inserted must be the correct size (height or width).
|
||||
|
||||
The default behaviour is to insert the given row to the :class:`Dataset`
|
||||
object at the given index. If the ``col`` parameter is given, however,
|
||||
a new column will be insert to the :class:`Dataset` object instead.
|
||||
|
||||
You can also insert a column of a single callable object, which will
|
||||
add a new column with the return values of the callable each as an
|
||||
item in the column. ::
|
||||
|
||||
data.append(col=random.randint)
|
||||
|
||||
See :ref:`dyncols` for an in-depth example.
|
||||
|
||||
.. versionchanged:: 0.9.0
|
||||
If inserting a column, and :class:`Dataset.headers` is set, the
|
||||
header attribute must be set, and will be considered the header for
|
||||
that row.
|
||||
|
||||
.. versionadded:: 0.9.0
|
||||
If inserting a row, you can add :ref:`tags <tags>` to the row you are inserting.
|
||||
This gives you the ability to :class:`filter <Dataset.filter>` your
|
||||
:class:`Dataset` later.
|
||||
|
||||
"""
|
||||
if row:
|
||||
self._validate(row)
|
||||
self._data.insert(index, Row(row, tags=tags))
|
||||
elif col:
|
||||
col = list(col)
|
||||
|
||||
# Callable Columns...
|
||||
if len(col) == 1 and isinstance(col[0], collections.Callable):
|
||||
col = list(map(col[0], self._data))
|
||||
|
||||
col = self._clean_col(col)
|
||||
self._validate(col=col)
|
||||
|
||||
if self.headers:
|
||||
# pop the first item off, add to headers
|
||||
if not header:
|
||||
raise HeadersNeeded()
|
||||
self.headers.insert(index, header)
|
||||
|
||||
if self.height and self.width:
|
||||
|
||||
for i, row in enumerate(self._data):
|
||||
|
||||
row.insert(index, col[i])
|
||||
self._data[i] = row
|
||||
else:
|
||||
self._data = [Row([row]) for row in col]
|
||||
|
||||
|
||||
def filter(self, tag):
|
||||
"""Returns a new instance of the :class:`Dataset`, excluding any rows
|
||||
that do not contain the given :ref:`tags <tags>`.
|
||||
"""
|
||||
_dset = copy(self)
|
||||
_dset._data = [row for row in _dset._data if row.has_tag(tag)]
|
||||
|
||||
return _dset
|
||||
|
||||
|
||||
def sort(self, col, reverse=False):
|
||||
"""Sort a :class:`Dataset` by a specific column, given string (for
|
||||
header) or integer (for column index). The order can be reversed by
|
||||
setting ``reverse`` to ``True``.
|
||||
Returns a new :class:`Dataset` instance where columns have been
|
||||
sorted."""
|
||||
|
||||
if isinstance(col, str):
|
||||
|
||||
if not self.headers:
|
||||
raise HeadersNeeded
|
||||
|
||||
_sorted = sorted(self.dict, key=itemgetter(col), reverse=reverse)
|
||||
_dset = Dataset(headers=self.headers)
|
||||
|
||||
for item in _sorted:
|
||||
row = [item[key] for key in self.headers]
|
||||
_dset.append(row=row)
|
||||
|
||||
else:
|
||||
if self.headers:
|
||||
col = self.headers[col]
|
||||
|
||||
_sorted = sorted(self.dict, key=itemgetter(col), reverse=reverse)
|
||||
_dset = Dataset(headers=self.headers)
|
||||
|
||||
for item in _sorted:
|
||||
if self.headers:
|
||||
row = [item[key] for key in self.headers]
|
||||
else:
|
||||
row = item
|
||||
_dset.append(row=row)
|
||||
|
||||
|
||||
return _dset
|
||||
|
||||
|
||||
def transpose(self):
|
||||
"""Transpose a :class:`Dataset`, turning rows into columns and vice
|
||||
versa, returning a new ``Dataset`` instance. The first row of the
|
||||
original instance becomes the new header row."""
|
||||
|
||||
# Don't transpose if there is no data
|
||||
if not self:
|
||||
return
|
||||
|
||||
_dset = Dataset()
|
||||
# The first element of the headers stays in the headers,
|
||||
# it is our "hinge" on which we rotate the data
|
||||
new_headers = [self.headers[0]] + self[self.headers[0]]
|
||||
|
||||
_dset.headers = new_headers
|
||||
for column in self.headers:
|
||||
|
||||
if column == self.headers[0]:
|
||||
# It's in the headers, so skip it
|
||||
continue
|
||||
|
||||
# Adding the column name as now they're a regular column
|
||||
row_data = [column] + self[column]
|
||||
row_data = Row(row_data)
|
||||
_dset.append(row=row_data)
|
||||
|
||||
return _dset
|
||||
|
||||
|
||||
def stack_rows(self, other):
|
||||
"""Stack two :class:`Dataset` instances together by
|
||||
joining at the row level, and return new combined
|
||||
``Dataset`` instance."""
|
||||
|
||||
if not isinstance(other, Dataset):
|
||||
return
|
||||
|
||||
if self.width != other.width:
|
||||
raise InvalidDimensions
|
||||
|
||||
# Copy the source data
|
||||
_dset = copy(self)
|
||||
|
||||
rows_to_stack = [row for row in _dset._data]
|
||||
other_rows = [row for row in other._data]
|
||||
|
||||
rows_to_stack.extend(other_rows)
|
||||
_dset._data = rows_to_stack
|
||||
|
||||
return _dset
|
||||
|
||||
|
||||
def stack_columns(self, other):
|
||||
"""Stack two :class:`Dataset` instances together by
|
||||
joining at the column level, and return a new
|
||||
combined ``Dataset`` instance. If either ``Dataset``
|
||||
has headers set, than the other must as well."""
|
||||
|
||||
if not isinstance(other, Dataset):
|
||||
return
|
||||
|
||||
if self.headers or other.headers:
|
||||
if not self.headers or not other.headers:
|
||||
raise HeadersNeeded
|
||||
|
||||
if self.height != other.height:
|
||||
raise InvalidDimensions
|
||||
|
||||
try:
|
||||
new_headers = self.headers + other.headers
|
||||
except TypeError:
|
||||
new_headers = None
|
||||
|
||||
_dset = Dataset()
|
||||
|
||||
for column in self.headers:
|
||||
_dset.append(col=self[column])
|
||||
|
||||
for column in other.headers:
|
||||
_dset.append(col=other[column])
|
||||
|
||||
_dset.headers = new_headers
|
||||
|
||||
return _dset
|
||||
|
||||
|
||||
def wipe(self):
|
||||
"""Removes all content and headers from the :class:`Dataset` object."""
|
||||
self._data = list()
|
||||
self.__headers = None
|
||||
|
||||
|
||||
|
||||
class Databook(object):
|
||||
"""A book of :class:`Dataset` objects.
|
||||
"""
|
||||
|
||||
def __init__(self, sets=None):
|
||||
|
||||
if sets is None:
|
||||
self._datasets = list()
|
||||
else:
|
||||
self._datasets = sets
|
||||
|
||||
self._register_formats()
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
return '<%s databook>' % (self.title.lower())
|
||||
except AttributeError:
|
||||
return '<databook object>'
|
||||
|
||||
|
||||
def wipe(self):
|
||||
"""Removes all :class:`Dataset` objects from the :class:`Databook`."""
|
||||
self._datasets = []
|
||||
|
||||
|
||||
@classmethod
|
||||
def _register_formats(cls):
|
||||
"""Adds format properties."""
|
||||
for fmt in formats.available:
|
||||
try:
|
||||
try:
|
||||
setattr(cls, fmt.title, property(fmt.export_book, fmt.import_book))
|
||||
except AttributeError:
|
||||
setattr(cls, fmt.title, property(fmt.export_book))
|
||||
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
def add_sheet(self, dataset):
|
||||
"""Adds given :class:`Dataset` to the :class:`Databook`."""
|
||||
if type(dataset) is Dataset:
|
||||
self._datasets.append(dataset)
|
||||
else:
|
||||
raise InvalidDatasetType
|
||||
|
||||
|
||||
def _package(self):
|
||||
"""Packages :class:`Databook` for delivery."""
|
||||
collector = []
|
||||
for dset in self._datasets:
|
||||
collector.append(OrderedDict(
|
||||
title = dset.title,
|
||||
data = dset.dict
|
||||
))
|
||||
return collector
|
||||
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
"""The number of the :class:`Dataset` objects within :class:`Databook`."""
|
||||
return len(self._datasets)
|
||||
|
||||
|
||||
def detect(stream):
|
||||
"""Return (format, stream) of given stream."""
|
||||
for fmt in formats.available:
|
||||
try:
|
||||
if fmt.detect(stream):
|
||||
return (fmt, stream)
|
||||
except AttributeError:
|
||||
pass
|
||||
return (None, stream)
|
||||
|
||||
|
||||
def import_set(stream):
|
||||
"""Return dataset of given stream."""
|
||||
(format, stream) = detect(stream)
|
||||
|
||||
try:
|
||||
data = Dataset()
|
||||
format.import_set(data, stream)
|
||||
return data
|
||||
|
||||
except AttributeError as e:
|
||||
return None
|
||||
|
||||
|
||||
class InvalidDatasetType(Exception):
|
||||
"Only Datasets can be added to a DataBook"
|
||||
|
||||
|
||||
class InvalidDimensions(Exception):
|
||||
"Invalid size"
|
||||
"Invalid size"
|
||||
|
||||
class InvalidDatasetIndex(Exception):
|
||||
"Outside of Dataset size"
|
||||
|
||||
class HeadersNeeded(Exception):
|
||||
"Header parameter must be given when appending a column in this Dataset."
|
||||
|
||||
class UnsupportedFormat(NotImplementedError):
|
||||
"Format is not supported"
|
||||
|
||||
|
||||
|
||||
def source(src=None, file=None, filename=None):
|
||||
"""docstring for import"""
|
||||
#open by filename
|
||||
pass
|
||||
"Format is not supported"
|
||||
|
||||
@@ -0,0 +1,818 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""
|
||||
tablib.core
|
||||
~~~~~~~~~~~
|
||||
|
||||
This module implements the central Tablib objects.
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:license: MIT, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from copy import copy
|
||||
from operator import itemgetter
|
||||
|
||||
from tablib import formats
|
||||
import collections
|
||||
from itertools import izip
|
||||
from itertools import imap
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from tablib.packages.ordereddict import OrderedDict
|
||||
|
||||
|
||||
__title__ = u'tablib'
|
||||
__version__ = u'0.9.4'
|
||||
__build__ = 0x000904
|
||||
__author__ = u'Kenneth Reitz'
|
||||
__license__ = u'MIT'
|
||||
__copyright__ = u'Copyright 2011 Kenneth Reitz'
|
||||
__docformat__ = u'restructuredtext'
|
||||
|
||||
|
||||
class Row(object):
|
||||
u"""Internal Row object. Mainly used for filtering."""
|
||||
|
||||
__slots__ = [u'tuple', u'_row', u'tags']
|
||||
|
||||
def __init__(self, row=list(), tags=list()):
|
||||
self._row = list(row)
|
||||
self.tags = list(tags)
|
||||
|
||||
def __iter__(self):
|
||||
return (col for col in self._row)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._row)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._row)
|
||||
|
||||
def __getslice__(self, i, j):
|
||||
return self._row[i,j]
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self._row[i]
|
||||
|
||||
def __setitem__(self, i, value):
|
||||
self._row[i] = value
|
||||
|
||||
def __delitem__(self, i):
|
||||
del self._row[i]
|
||||
|
||||
def __getstate__(self):
|
||||
return {slot: [getattr(self, slot) for slot in self.__slots__]}
|
||||
|
||||
def __setstate__(self, state):
|
||||
for (k, v) in list(state.items()): setattr(self, k, v)
|
||||
|
||||
def append(self, value):
|
||||
self._row.append(value)
|
||||
|
||||
def insert(self, index, value):
|
||||
self._row.insert(index, value)
|
||||
|
||||
def __contains__(self, item):
|
||||
return (item in self._row)
|
||||
|
||||
@property
|
||||
def tuple(self):
|
||||
u'''Tuple representation of :class:`Row`.'''
|
||||
return tuple(self._row)
|
||||
|
||||
@property
|
||||
def list(self):
|
||||
u'''List representation of :class:`Row`.'''
|
||||
return list(self._row)
|
||||
|
||||
def has_tag(self, tag):
|
||||
u"""Returns true if current row contains tag."""
|
||||
|
||||
if tag == None:
|
||||
return False
|
||||
elif isinstance(tag, basestring):
|
||||
return (tag in self.tags)
|
||||
else:
|
||||
return bool(len(set(tag) & set(self.tags)))
|
||||
|
||||
|
||||
|
||||
|
||||
class Dataset(object):
|
||||
u"""The :class:`Dataset` object is the heart of Tablib. It provides all core
|
||||
functionality.
|
||||
|
||||
Usually you create a :class:`Dataset` instance in your main module, and append
|
||||
rows and columns as you collect data. ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.headers = ('name', 'age')
|
||||
|
||||
for (name, age) in some_collector():
|
||||
data.append((name, age))
|
||||
|
||||
You can also set rows and headers upon instantiation. This is useful if dealing
|
||||
with dozens or hundres of :class:`Dataset` objects. ::
|
||||
|
||||
headers = ('first_name', 'last_name')
|
||||
data = [('John', 'Adams'), ('George', 'Washington')]
|
||||
|
||||
data = tablib.Dataset(*data, headers=headers)
|
||||
|
||||
|
||||
:param \*args: (optional) list of rows to populate Dataset
|
||||
:param headers: (optional) list strings for Dataset header row
|
||||
|
||||
|
||||
.. admonition:: Format Attributes Definition
|
||||
|
||||
If you look at the code, the various output/import formats are not
|
||||
defined within the :class:`Dataset` object. To add support for a new format, see
|
||||
:ref:`Adding New Formats <newformats>`.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._data = list(Row(arg) for arg in args)
|
||||
self.__headers = None
|
||||
|
||||
# ('title', index) tuples
|
||||
self._separators = []
|
||||
|
||||
# (column, callback) tuples
|
||||
self._formatters = []
|
||||
|
||||
try:
|
||||
self.headers = kwargs[u'headers']
|
||||
except KeyError:
|
||||
self.headers = None
|
||||
|
||||
try:
|
||||
self.title = kwargs[u'title']
|
||||
except KeyError:
|
||||
self.title = None
|
||||
|
||||
self._register_formats()
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return self.height
|
||||
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, basestring):
|
||||
if key in self.headers:
|
||||
pos = self.headers.index(key) # get 'key' index from each data
|
||||
return [row[pos] for row in self._data]
|
||||
else:
|
||||
raise KeyError
|
||||
else:
|
||||
_results = self._data[key]
|
||||
if isinstance(_results, Row):
|
||||
return _results.tuple
|
||||
else:
|
||||
return [result.tuple for result in _results]
|
||||
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._validate(value)
|
||||
self._data[key] = Row(value)
|
||||
|
||||
|
||||
def __delitem__(self, key):
|
||||
if isinstance(key, basestring):
|
||||
|
||||
if key in self.headers:
|
||||
|
||||
pos = self.headers.index(key)
|
||||
del self.headers[pos]
|
||||
|
||||
for i, row in enumerate(self._data):
|
||||
|
||||
del row[pos]
|
||||
self._data[i] = row
|
||||
else:
|
||||
raise KeyError
|
||||
else:
|
||||
del self._data[key]
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
return u'<%s dataset>' % (self.title.lower())
|
||||
except AttributeError:
|
||||
return u'<dataset object>'
|
||||
|
||||
|
||||
@classmethod
|
||||
def _register_formats(cls):
|
||||
u"""Adds format properties."""
|
||||
for fmt in formats.available:
|
||||
try:
|
||||
try:
|
||||
setattr(cls, fmt.title, property(fmt.export_set, fmt.import_set))
|
||||
except AttributeError:
|
||||
setattr(cls, fmt.title, property(fmt.export_set))
|
||||
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
def _validate(self, row=None, col=None, safety=False):
|
||||
u"""Assures size of every row in dataset is of proper proportions."""
|
||||
if row:
|
||||
is_valid = (len(row) == self.width) if self.width else True
|
||||
elif col:
|
||||
if len(col) < 1:
|
||||
is_valid = True
|
||||
else:
|
||||
is_valid = (len(col) == self.height) if self.height else True
|
||||
else:
|
||||
is_valid = all((len(x) == self.width for x in self._data))
|
||||
|
||||
if is_valid:
|
||||
return True
|
||||
else:
|
||||
if not safety:
|
||||
raise InvalidDimensions
|
||||
return False
|
||||
|
||||
|
||||
def _package(self, dicts=True):
|
||||
u"""Packages Dataset into lists of dictionaries for transmission."""
|
||||
|
||||
_data = list(self._data)
|
||||
|
||||
# Execute formatters
|
||||
if self._formatters:
|
||||
for row_i, row in enumerate(_data):
|
||||
for col, callback in self._formatters:
|
||||
try:
|
||||
if col is None:
|
||||
for j, c in enumerate(row):
|
||||
_data[row_i][j] = callback(c)
|
||||
else:
|
||||
_data[row_i][col] = callback(row[col])
|
||||
except IndexError:
|
||||
raise InvalidDatasetIndex
|
||||
|
||||
|
||||
if self.headers:
|
||||
if dicts:
|
||||
data = [OrderedDict(list(izip(self.headers, data_row))) for data_row in _data]
|
||||
else:
|
||||
data = [list(self.headers)] + list(_data)
|
||||
else:
|
||||
data = [list(row) for row in _data]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def _clean_col(self, col):
|
||||
u"""Prepares the given column for insert/append."""
|
||||
|
||||
col = list(col)
|
||||
|
||||
if self.headers:
|
||||
header = [col.pop(0)]
|
||||
else:
|
||||
header = []
|
||||
|
||||
if len(col) == 1 and hasattr(col[0], '__call__'):
|
||||
col = list(imap(col[0], self._data))
|
||||
col = tuple(header + col)
|
||||
|
||||
return col
|
||||
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
u"""The number of rows currently in the :class:`Dataset`.
|
||||
Cannot be directly modified.
|
||||
"""
|
||||
return len(self._data)
|
||||
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
u"""The number of columns currently in the :class:`Dataset`.
|
||||
Cannot be directly modified.
|
||||
"""
|
||||
|
||||
try:
|
||||
return len(self._data[0])
|
||||
except IndexError:
|
||||
try:
|
||||
return len(self.headers)
|
||||
except TypeError:
|
||||
return 0
|
||||
|
||||
|
||||
def _get_headers(self):
|
||||
u"""An *optional* list of strings to be used for header rows and attribute names.
|
||||
|
||||
This must be set manually. The given list length must equal :class:`Dataset.width`.
|
||||
|
||||
"""
|
||||
return self.__headers
|
||||
|
||||
|
||||
def _set_headers(self, collection):
|
||||
u"""Validating headers setter."""
|
||||
self._validate(collection)
|
||||
if collection:
|
||||
try:
|
||||
self.__headers = list(collection)
|
||||
except TypeError:
|
||||
raise TypeError
|
||||
else:
|
||||
self.__headers = None
|
||||
|
||||
headers = property(_get_headers, _set_headers)
|
||||
|
||||
def _get_dict(self):
|
||||
u"""A native Python representation of the :class:`Dataset` object. If headers have
|
||||
been set, a list of Python dictionaries will be returned. If no headers have been set,
|
||||
a list of tuples (rows) will be returned instead.
|
||||
|
||||
A dataset object can also be imported by setting the `Dataset.dict` attribute: ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.json = '[{"last_name": "Adams","age": 90,"first_name": "John"}]'
|
||||
|
||||
"""
|
||||
return self._package()
|
||||
|
||||
|
||||
def _set_dict(self, pickle):
|
||||
u"""A native Python representation of the Dataset object. If headers have been
|
||||
set, a list of Python dictionaries will be returned. If no headers have been
|
||||
set, a list of tuples (rows) will be returned instead.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.dict` attribute. ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.dict = [{'age': 90, 'first_name': 'Kenneth', 'last_name': 'Reitz'}]
|
||||
|
||||
"""
|
||||
|
||||
if not len(pickle):
|
||||
return
|
||||
|
||||
# if list of rows
|
||||
if isinstance(pickle[0], list):
|
||||
self.wipe()
|
||||
for row in pickle:
|
||||
self.append(Row(row))
|
||||
|
||||
# if list of objects
|
||||
elif isinstance(pickle[0], dict):
|
||||
self.wipe()
|
||||
self.headers = list(pickle[0].keys())
|
||||
for row in pickle:
|
||||
self.append(Row(list(row.values())))
|
||||
else:
|
||||
raise UnsupportedFormat
|
||||
|
||||
dict = property(_get_dict, _set_dict)
|
||||
|
||||
|
||||
@property
|
||||
def xls():
|
||||
u"""An Excel Spreadsheet representation of the :class:`Dataset` object, with :ref:`seperators`. Cannot be set.
|
||||
|
||||
.. admonition:: Binary Warning
|
||||
|
||||
:class:`Dataset.xls` contains binary data, so make sure to write in binary mode::
|
||||
|
||||
with open('output.xls', 'wb') as f:
|
||||
f.write(data.xls)'
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@property
|
||||
def csv():
|
||||
u"""A CSV representation of the :class:`Dataset` object. The top row will contain
|
||||
headers, if they have been set. Otherwise, the top row will contain
|
||||
the first row of the dataset.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.csv` attribute. ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.csv = 'age, first_name, last_name\\n90, John, Adams'
|
||||
|
||||
Import assumes (for now) that headers exist.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@property
|
||||
def tsv():
|
||||
u"""A TSV representation of the :class:`Dataset` object. The top row will contain
|
||||
headers, if they have been set. Otherwise, the top row will contain
|
||||
the first row of the dataset.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.tsv` attribute. ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.tsv = 'age\tfirst_name\tlast_name\\n90\tJohn\tAdams'
|
||||
|
||||
Import assumes (for now) that headers exist.
|
||||
"""
|
||||
|
||||
@property
|
||||
def yaml():
|
||||
u"""A YAML representation of the :class:`Dataset` object. If headers have been
|
||||
set, a YAML list of objects will be returned. If no headers have
|
||||
been set, a YAML list of lists (rows) will be returned instead.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.json` attribute: ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.yaml = '- {age: 90, first_name: John, last_name: Adams}'
|
||||
|
||||
Import assumes (for now) that headers exist.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@property
|
||||
def json():
|
||||
u"""A JSON representation of the :class:`Dataset` object. If headers have been
|
||||
set, a JSON list of objects will be returned. If no headers have
|
||||
been set, a JSON list of lists (rows) will be returned instead.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.json` attribute: ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.json = '[{age: 90, first_name: "John", liast_name: "Adams"}]'
|
||||
|
||||
Import assumes (for now) that headers exist.
|
||||
"""
|
||||
|
||||
@property
|
||||
def html():
|
||||
u"""A HTML table representation of the :class:`Dataset` object. If
|
||||
headers have been set, they will be used as table headers.
|
||||
|
||||
..notice:: This method can be used for export only.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def append(self, row=None, col=None, header=None, tags=list()):
|
||||
u"""Adds a row or column to the :class:`Dataset`.
|
||||
Usage is :class:`Dataset.insert` for documentation.
|
||||
"""
|
||||
|
||||
if row is not None:
|
||||
self.insert(self.height, row=row, tags=tags)
|
||||
elif col is not None:
|
||||
self.insert(self.width, col=col, header=header)
|
||||
|
||||
|
||||
def insert_separator(self, index, text=u'-'):
|
||||
u"""Adds a separator to :class:`Dataset` at given index."""
|
||||
|
||||
sep = (index, text)
|
||||
self._separators.append(sep)
|
||||
|
||||
|
||||
def append_separator(self, text=u'-'):
|
||||
u"""Adds a :ref:`seperator <seperators>` to the :class:`Dataset`."""
|
||||
|
||||
# change offsets if headers are or aren't defined
|
||||
if not self.headers:
|
||||
index = self.height if self.height else 0
|
||||
else:
|
||||
index = (self.height + 1) if self.height else 1
|
||||
|
||||
self.insert_separator(index, text)
|
||||
|
||||
|
||||
def add_formatter(self, col, handler):
|
||||
u"""Adds a :ref:`formatter` to the :class:`Dataset`.
|
||||
|
||||
.. versionadded:: 0.9.5
|
||||
:param col: column to. Accepts index int or header str.
|
||||
:param handler: reference to callback function to execute
|
||||
against each cell value.
|
||||
"""
|
||||
|
||||
if isinstance(col, basestring):
|
||||
if col in self.headers:
|
||||
col = self.headers.index(col) # get 'key' index from each data
|
||||
else:
|
||||
raise KeyError
|
||||
|
||||
if not col > self.width:
|
||||
self._formatters.append((col, handler))
|
||||
else:
|
||||
raise InvalidDatasetIndex
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def insert(self, index, row=None, col=None, header=None, tags=list()):
|
||||
u"""Inserts a row or column to the :class:`Dataset` at the given index.
|
||||
|
||||
Rows and columns inserted must be the correct size (height or width).
|
||||
|
||||
The default behaviour is to insert the given row to the :class:`Dataset`
|
||||
object at the given index. If the ``col`` parameter is given, however,
|
||||
a new column will be insert to the :class:`Dataset` object instead.
|
||||
|
||||
You can also insert a column of a single callable object, which will
|
||||
add a new column with the return values of the callable each as an
|
||||
item in the column. ::
|
||||
|
||||
data.append(col=random.randint)
|
||||
|
||||
See :ref:`dyncols` for an in-depth example.
|
||||
|
||||
.. versionchanged:: 0.9.0
|
||||
If inserting a column, and :class:`Dataset.headers` is set, the
|
||||
header attribute must be set, and will be considered the header for
|
||||
that row.
|
||||
|
||||
.. versionadded:: 0.9.0
|
||||
If inserting a row, you can add :ref:`tags <tags>` to the row you are inserting.
|
||||
This gives you the ability to :class:`filter <Dataset.filter>` your
|
||||
:class:`Dataset` later.
|
||||
|
||||
"""
|
||||
if row:
|
||||
self._validate(row)
|
||||
self._data.insert(index, Row(row, tags=tags))
|
||||
elif col:
|
||||
col = list(col)
|
||||
|
||||
# Callable Columns...
|
||||
if len(col) == 1 and hasattr(col[0], '__call__'):
|
||||
col = list(imap(col[0], self._data))
|
||||
|
||||
col = self._clean_col(col)
|
||||
self._validate(col=col)
|
||||
|
||||
if self.headers:
|
||||
# pop the first item off, add to headers
|
||||
if not header:
|
||||
raise HeadersNeeded()
|
||||
self.headers.insert(index, header)
|
||||
|
||||
if self.height and self.width:
|
||||
|
||||
for i, row in enumerate(self._data):
|
||||
|
||||
row.insert(index, col[i])
|
||||
self._data[i] = row
|
||||
else:
|
||||
self._data = [Row([row]) for row in col]
|
||||
|
||||
|
||||
def filter(self, tag):
|
||||
u"""Returns a new instance of the :class:`Dataset`, excluding any rows
|
||||
that do not contain the given :ref:`tags <tags>`.
|
||||
"""
|
||||
_dset = copy(self)
|
||||
_dset._data = [row for row in _dset._data if row.has_tag(tag)]
|
||||
|
||||
return _dset
|
||||
|
||||
|
||||
def sort(self, col, reverse=False):
|
||||
u"""Sort a :class:`Dataset` by a specific column, given string (for
|
||||
header) or integer (for column index). The order can be reversed by
|
||||
setting ``reverse`` to ``True``.
|
||||
Returns a new :class:`Dataset` instance where columns have been
|
||||
sorted."""
|
||||
|
||||
if isinstance(col, basestring):
|
||||
|
||||
if not self.headers:
|
||||
raise HeadersNeeded
|
||||
|
||||
_sorted = sorted(self.dict, key=itemgetter(col), reverse=reverse)
|
||||
_dset = Dataset(headers=self.headers)
|
||||
|
||||
for item in _sorted:
|
||||
row = [item[key] for key in self.headers]
|
||||
_dset.append(row=row)
|
||||
|
||||
else:
|
||||
if self.headers:
|
||||
col = self.headers[col]
|
||||
|
||||
_sorted = sorted(self.dict, key=itemgetter(col), reverse=reverse)
|
||||
_dset = Dataset(headers=self.headers)
|
||||
|
||||
for item in _sorted:
|
||||
if self.headers:
|
||||
row = [item[key] for key in self.headers]
|
||||
else:
|
||||
row = item
|
||||
_dset.append(row=row)
|
||||
|
||||
|
||||
return _dset
|
||||
|
||||
|
||||
def transpose(self):
|
||||
u"""Transpose a :class:`Dataset`, turning rows into columns and vice
|
||||
versa, returning a new ``Dataset`` instance. The first row of the
|
||||
original instance becomes the new header row."""
|
||||
|
||||
# Don't transpose if there is no data
|
||||
if not self:
|
||||
return
|
||||
|
||||
_dset = Dataset()
|
||||
# The first element of the headers stays in the headers,
|
||||
# it is our "hinge" on which we rotate the data
|
||||
new_headers = [self.headers[0]] + self[self.headers[0]]
|
||||
|
||||
_dset.headers = new_headers
|
||||
for column in self.headers:
|
||||
|
||||
if column == self.headers[0]:
|
||||
# It's in the headers, so skip it
|
||||
continue
|
||||
|
||||
# Adding the column name as now they're a regular column
|
||||
row_data = [column] + self[column]
|
||||
row_data = Row(row_data)
|
||||
_dset.append(row=row_data)
|
||||
|
||||
return _dset
|
||||
|
||||
|
||||
def stack_rows(self, other):
|
||||
u"""Stack two :class:`Dataset` instances together by
|
||||
joining at the row level, and return new combined
|
||||
``Dataset`` instance."""
|
||||
|
||||
if not isinstance(other, Dataset):
|
||||
return
|
||||
|
||||
if self.width != other.width:
|
||||
raise InvalidDimensions
|
||||
|
||||
# Copy the source data
|
||||
_dset = copy(self)
|
||||
|
||||
rows_to_stack = [row for row in _dset._data]
|
||||
other_rows = [row for row in other._data]
|
||||
|
||||
rows_to_stack.extend(other_rows)
|
||||
_dset._data = rows_to_stack
|
||||
|
||||
return _dset
|
||||
|
||||
|
||||
def stack_columns(self, other):
|
||||
u"""Stack two :class:`Dataset` instances together by
|
||||
joining at the column level, and return a new
|
||||
combined ``Dataset`` instance. If either ``Dataset``
|
||||
has headers set, than the other must as well."""
|
||||
|
||||
if not isinstance(other, Dataset):
|
||||
return
|
||||
|
||||
if self.headers or other.headers:
|
||||
if not self.headers or not other.headers:
|
||||
raise HeadersNeeded
|
||||
|
||||
if self.height != other.height:
|
||||
raise InvalidDimensions
|
||||
|
||||
try:
|
||||
new_headers = self.headers + other.headers
|
||||
except TypeError:
|
||||
new_headers = None
|
||||
|
||||
_dset = Dataset()
|
||||
|
||||
for column in self.headers:
|
||||
_dset.append(col=self[column])
|
||||
|
||||
for column in other.headers:
|
||||
_dset.append(col=other[column])
|
||||
|
||||
_dset.headers = new_headers
|
||||
|
||||
return _dset
|
||||
|
||||
|
||||
def wipe(self):
|
||||
u"""Removes all content and headers from the :class:`Dataset` object."""
|
||||
self._data = list()
|
||||
self.__headers = None
|
||||
|
||||
|
||||
|
||||
class Databook(object):
|
||||
u"""A book of :class:`Dataset` objects.
|
||||
"""
|
||||
|
||||
def __init__(self, sets=None):
|
||||
|
||||
if sets is None:
|
||||
self._datasets = list()
|
||||
else:
|
||||
self._datasets = sets
|
||||
|
||||
self._register_formats()
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
return u'<%s databook>' % (self.title.lower())
|
||||
except AttributeError:
|
||||
return u'<databook object>'
|
||||
|
||||
|
||||
def wipe(self):
|
||||
u"""Removes all :class:`Dataset` objects from the :class:`Databook`."""
|
||||
self._datasets = []
|
||||
|
||||
|
||||
@classmethod
|
||||
def _register_formats(cls):
|
||||
u"""Adds format properties."""
|
||||
for fmt in formats.available:
|
||||
try:
|
||||
try:
|
||||
setattr(cls, fmt.title, property(fmt.export_book, fmt.import_book))
|
||||
except AttributeError:
|
||||
setattr(cls, fmt.title, property(fmt.export_book))
|
||||
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
def add_sheet(self, dataset):
|
||||
u"""Adds given :class:`Dataset` to the :class:`Databook`."""
|
||||
if type(dataset) is Dataset:
|
||||
self._datasets.append(dataset)
|
||||
else:
|
||||
raise InvalidDatasetType
|
||||
|
||||
|
||||
def _package(self):
|
||||
u"""Packages :class:`Databook` for delivery."""
|
||||
collector = []
|
||||
for dset in self._datasets:
|
||||
collector.append(OrderedDict(
|
||||
title = dset.title,
|
||||
data = dset.dict
|
||||
))
|
||||
return collector
|
||||
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
u"""The number of the :class:`Dataset` objects within :class:`Databook`."""
|
||||
return len(self._datasets)
|
||||
|
||||
|
||||
def detect(stream):
|
||||
u"""Return (format, stream) of given stream."""
|
||||
for fmt in formats.available:
|
||||
try:
|
||||
if fmt.detect(stream):
|
||||
return (fmt, stream)
|
||||
except AttributeError:
|
||||
pass
|
||||
return (None, stream)
|
||||
|
||||
|
||||
def import_set(stream):
|
||||
u"""Return dataset of given stream."""
|
||||
(format, stream) = detect(stream)
|
||||
|
||||
try:
|
||||
data = Dataset()
|
||||
format.import_set(data, stream)
|
||||
return data
|
||||
|
||||
except AttributeError, e:
|
||||
return None
|
||||
|
||||
|
||||
class InvalidDatasetType(Exception):
|
||||
u"Only Datasets can be added to a DataBook"
|
||||
|
||||
|
||||
class InvalidDimensions(Exception):
|
||||
u"Invalid size"
|
||||
|
||||
class InvalidDatasetIndex(Exception):
|
||||
u"Outside of Dataset size"
|
||||
|
||||
class HeadersNeeded(Exception):
|
||||
u"Header parameter must be given when appending a column in this Dataset."
|
||||
|
||||
class UnsupportedFormat(NotImplementedError):
|
||||
u"Format is not supported"
|
||||
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - formats
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
available = (json, xls, yaml, csv, tsv, html)
|
||||
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - CSV Support.
|
||||
"""
|
||||
|
||||
import sys
|
||||
if sys.version_info[0] > 2:
|
||||
from io import StringIO
|
||||
else:
|
||||
from cStringIO import StringIO
|
||||
|
||||
|
||||
import csv
|
||||
import os
|
||||
|
||||
import tablib
|
||||
|
||||
|
||||
title = 'csv'
|
||||
extentions = ('csv',)
|
||||
|
||||
|
||||
|
||||
def export_set(dataset):
|
||||
"""Returns CSV representation of Dataset."""
|
||||
stream = StringIO()
|
||||
_csv = csv.writer(stream)
|
||||
|
||||
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."""
|
||||
|
||||
dset.wipe()
|
||||
|
||||
rows = csv.reader(in_stream.splitlines())
|
||||
for i, row in enumerate(rows):
|
||||
|
||||
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
|
||||
@@ -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()
|
||||
@@ -0,0 +1,54 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - JSON Support
|
||||
"""
|
||||
|
||||
import tablib
|
||||
|
||||
import sys
|
||||
if sys.version_info[:2] > (2, 5):
|
||||
from tablib.packages import anyjson
|
||||
else:
|
||||
from tablib.packages import anyjson25 as anyjson
|
||||
|
||||
|
||||
|
||||
title = 'json'
|
||||
extentions = ('json', 'jsn')
|
||||
|
||||
|
||||
def export_set(dataset):
|
||||
"""Returns JSON representation of Dataset."""
|
||||
return anyjson.serialize(dataset.dict)
|
||||
|
||||
|
||||
def export_book(databook):
|
||||
"""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 = anyjson.deserialize(in_stream)
|
||||
|
||||
|
||||
def import_book(dbook, in_stream):
|
||||
"""Returns databook from JSON stream."""
|
||||
|
||||
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:
|
||||
anyjson.deserialize(stream)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
@@ -0,0 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - TSV (Tab Separated Values) Support.
|
||||
"""
|
||||
|
||||
import sys
|
||||
if sys.version_info[0] > 2:
|
||||
from io import StringIO
|
||||
else:
|
||||
from cStringIO import StringIO
|
||||
|
||||
import csv
|
||||
import os
|
||||
|
||||
import tablib
|
||||
|
||||
|
||||
title = 'tsv'
|
||||
extentions = ('tsv',)
|
||||
|
||||
|
||||
|
||||
def export_set(dataset):
|
||||
"""Returns a TSV representation of Dataset."""
|
||||
stream = StringIO()
|
||||
_tsv = csv.writer(stream, delimiter='\t')
|
||||
|
||||
for row in dataset._package(dicts=False):
|
||||
_tsv.writerow(row)
|
||||
|
||||
return stream.getvalue()
|
||||
|
||||
|
||||
def import_set(dset, in_stream, headers=True):
|
||||
"""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
|
||||
|
||||
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
|
||||
@@ -0,0 +1,90 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - XLS Support.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
if sys.version_info[0] > 2:
|
||||
from io import BytesIO
|
||||
import tablib.packages.xlwt3 as xlwt
|
||||
|
||||
else:
|
||||
from cStringIO import StringIO as BytesIO
|
||||
import tablib.packages.xlwt as xlwt
|
||||
|
||||
|
||||
|
||||
title = 'xls'
|
||||
extentions = ('xls',)
|
||||
|
||||
# special styles
|
||||
wrap = xlwt.easyxf("alignment: wrap on")
|
||||
bold = xlwt.easyxf("font: bold on")
|
||||
|
||||
|
||||
def export_set(dataset):
|
||||
"""Returns XLS representation of Dataset."""
|
||||
|
||||
wb = xlwt.Workbook(encoding='utf8')
|
||||
ws = wb.add_sheet(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 XLS representation of DataBook."""
|
||||
|
||||
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))
|
||||
|
||||
dset_sheet(dset, ws)
|
||||
|
||||
|
||||
stream = BytesIO()
|
||||
wb.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):
|
||||
for j, col in enumerate(row):
|
||||
|
||||
# bold headers
|
||||
if (i == 0) and dataset.headers:
|
||||
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)
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - YAML Support.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
if sys.version_info[0] > 2:
|
||||
import tablib.packages.yaml3 as yaml
|
||||
else:
|
||||
import tablib.packages.yaml as yaml
|
||||
|
||||
|
||||
import tablib
|
||||
|
||||
|
||||
|
||||
title = 'yaml'
|
||||
extentions = ('yaml', 'yml')
|
||||
|
||||
|
||||
|
||||
def export_set(dataset):
|
||||
"""Returns YAML representation of Dataset."""
|
||||
return yaml.dump(dataset.dict)
|
||||
|
||||
|
||||
def export_book(databook):
|
||||
"""Returns YAML representation of Databook."""
|
||||
return yaml.dump(databook._package())
|
||||
|
||||
|
||||
def import_set(dset, in_stream):
|
||||
"""Returns dataset from YAML stream."""
|
||||
|
||||
dset.wipe()
|
||||
dset.dict = yaml.load(in_stream)
|
||||
|
||||
|
||||
def import_book(dbook, in_stream):
|
||||
"""Returns databook from YAML stream."""
|
||||
|
||||
dbook.wipe()
|
||||
|
||||
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
|
||||
@@ -1,25 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
class Object(object):
|
||||
"""Your attributes are belong to us."""
|
||||
|
||||
def __init__(self, **entries):
|
||||
self.__dict__.update(entries)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
|
||||
def piped():
|
||||
"""Returns piped input via stdin, else False"""
|
||||
with sys.stdin as stdin:
|
||||
return stdin.read() if not stdin.isatty() else None
|
||||
|
||||
|
||||
def is_string(obj):
|
||||
"""Tests if an object is a string"""
|
||||
|
||||
return True if type(obj).__name__ == 'str' else False
|
||||
@@ -1 +0,0 @@
|
||||
all = ['simplejson', 'typecheck', 'xlwt', 'opster']
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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 < and >
|
||||
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( '&', '&' )
|
||||
if '>' in text:
|
||||
text = text.replace( '>', '>' )
|
||||
if '<' in text:
|
||||
text = text.replace( '<', '<' )
|
||||
if '\"' in text:
|
||||
text = text.replace( '\"', '"' )
|
||||
if '\'' in text:
|
||||
text = text.replace( '\'', '"' )
|
||||
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 '&' in text:
|
||||
text = text.replace( '&', '&' )
|
||||
if '>' in text:
|
||||
text = text.replace( '>', '>' )
|
||||
if '<' in text:
|
||||
text = text.replace( '<', '<' )
|
||||
if '"' in text:
|
||||
text = text.replace( '"', '\"' )
|
||||
|
||||
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__
|
||||
@@ -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 < and >
|
||||
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( '&', '&' )
|
||||
if '>' in text:
|
||||
text = text.replace( '>', '>' )
|
||||
if '<' in text:
|
||||
text = text.replace( '<', '<' )
|
||||
if '\"' in text:
|
||||
text = text.replace( '\"', '"' )
|
||||
if '\'' in text:
|
||||
text = text.replace( '\'', '"' )
|
||||
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 '&' in text:
|
||||
text = text.replace( '&', '&' )
|
||||
if '>' in text:
|
||||
text = text.replace( '>', '>' )
|
||||
if '<' in text:
|
||||
text = text.replace( '<', '<' )
|
||||
if '"' in text:
|
||||
text = text.replace( '"', '\"' )
|
||||
|
||||
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__)
|
||||
@@ -1,612 +0,0 @@
|
||||
# (c) Alexander Solovyov, 2009, under terms of the new BSD License
|
||||
'''Command line arguments parser
|
||||
'''
|
||||
|
||||
import sys, traceback, getopt, types, textwrap, inspect, os
|
||||
from itertools import imap
|
||||
|
||||
__all__ = ['command', 'dispatch']
|
||||
__version__ = '0.9.10'
|
||||
__author__ = 'Alexander Solovyov'
|
||||
__email__ = 'piranha@piranha.org.ua'
|
||||
|
||||
write = sys.stdout.write
|
||||
err = sys.stderr.write
|
||||
|
||||
CMDTABLE = {}
|
||||
|
||||
# --------
|
||||
# Public interface
|
||||
# --------
|
||||
|
||||
def command(options=None, usage=None, name=None, shortlist=False, hide=False):
|
||||
'''Decorator to mark function to be used for command line processing.
|
||||
|
||||
All arguments are optional:
|
||||
|
||||
- ``options``: options in format described in docs. If not supplied,
|
||||
will be determined from function.
|
||||
- ``usage``: usage string for function, replaces ``%name`` with name
|
||||
of program or subcommand. In case if it's subcommand and ``%name``
|
||||
is not present, usage is prepended by ``name``
|
||||
- ``name``: used for multiple subcommands. Defaults to wrapped
|
||||
function name
|
||||
- ``shortlist``: if command should be included in shortlist. Used
|
||||
only with multiple subcommands
|
||||
- ``hide``: if command should be hidden from help listing. Used only
|
||||
with multiple subcommands, overrides ``shortlist``
|
||||
'''
|
||||
def wrapper(func):
|
||||
try:
|
||||
options_ = list(guess_options(func))
|
||||
except TypeError:
|
||||
options_ = []
|
||||
try:
|
||||
options_ = options_ + list(options)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
name_ = name or func.__name__.replace('_', '-')
|
||||
if usage is None:
|
||||
usage_ = guess_usage(func, options_)
|
||||
else:
|
||||
usage_ = usage
|
||||
prefix = hide and '~' or (shortlist and '^' or '')
|
||||
CMDTABLE[prefix + name_] = (func, options_, usage_)
|
||||
|
||||
def help_func(name=None):
|
||||
return help_cmd(func, replace_name(usage_, sysname()), options_)
|
||||
|
||||
@wraps(func)
|
||||
def inner(*args, **opts):
|
||||
# look if we need to add 'help' option
|
||||
try:
|
||||
(True for option in reversed(options_)
|
||||
if option[1] == 'help').next()
|
||||
except StopIteration:
|
||||
options_.append(('h', 'help', False, 'show help'))
|
||||
|
||||
argv = opts.pop('argv', sys.argv[1:])
|
||||
if opts.pop('help', False):
|
||||
return help_func()
|
||||
|
||||
if args or opts:
|
||||
# no catcher here because this is call from Python
|
||||
return call_cmd_regular(func, options_)(*args, **opts)
|
||||
|
||||
try:
|
||||
opts, args = catcher(lambda: parse(argv, options_), help_func)
|
||||
except Abort:
|
||||
return -1
|
||||
|
||||
try:
|
||||
if opts.pop('help', False):
|
||||
return help_func()
|
||||
return catcher(lambda: call_cmd(name_, func)(*args, **opts),
|
||||
help_func)
|
||||
except Abort:
|
||||
return -1
|
||||
|
||||
return inner
|
||||
return wrapper
|
||||
|
||||
|
||||
def dispatch(args=None, cmdtable=None, globaloptions=None,
|
||||
middleware=lambda x: x):
|
||||
'''Dispatch command arguments based on subcommands.
|
||||
|
||||
- ``args``: list of arguments, default: ``sys.argv[1:]``
|
||||
- ``cmdtable``: dict of commands in format described below.
|
||||
If not supplied, will use functions decorated with ``@command``.
|
||||
- ``globaloptions``: list of options which are applied to all
|
||||
commands, will contain ``--help`` option at least.
|
||||
- ``middleware``: global decorator for all commands.
|
||||
|
||||
cmdtable format description::
|
||||
|
||||
{'name': (function, options, usage)}
|
||||
|
||||
- ``name`` is the name used on command-line. Can contain aliases
|
||||
(separate them with ``|``), pointer to a fact that this command
|
||||
should be displayed in short help (start name with ``^``), or to
|
||||
a fact that this command should be hidden (start name with ``~``)
|
||||
- ``function`` is the actual callable
|
||||
- ``options`` is options list in format described in docs
|
||||
- ``usage`` is the short string of usage
|
||||
'''
|
||||
args = args or sys.argv[1:]
|
||||
cmdtable = cmdtable or CMDTABLE
|
||||
|
||||
globaloptions = globaloptions or []
|
||||
globaloptions.append(('h', 'help', False, 'display help'))
|
||||
|
||||
cmdtable['help'] = (help_(cmdtable, globaloptions), [], '[TOPIC]')
|
||||
help_func = cmdtable['help'][0]
|
||||
|
||||
autocomplete(cmdtable, args)
|
||||
|
||||
try:
|
||||
name, func, args, kwargs = catcher(
|
||||
lambda: _dispatch(args, cmdtable, globaloptions),
|
||||
help_func)
|
||||
return catcher(
|
||||
lambda: call_cmd(name, middleware(func))(*args, **kwargs),
|
||||
help_func)
|
||||
except Abort:
|
||||
return -1
|
||||
|
||||
# --------
|
||||
# Help
|
||||
# --------
|
||||
|
||||
def help_(cmdtable, globalopts):
|
||||
def help_inner(name=None):
|
||||
'''Show help for a given help topic or a help overview
|
||||
|
||||
With no arguments, print a list of commands with short help messages.
|
||||
|
||||
Given a command name, print help for that command.
|
||||
'''
|
||||
def helplist():
|
||||
hlp = {}
|
||||
# determine if any command is marked for shortlist
|
||||
shortlist = (name == 'shortlist' and
|
||||
any(imap(lambda x: x.startswith('^'), cmdtable)))
|
||||
|
||||
for cmd, info in cmdtable.items():
|
||||
if cmd.startswith('~'):
|
||||
continue # do not display hidden commands
|
||||
if shortlist and not cmd.startswith('^'):
|
||||
continue # short help contains only marked commands
|
||||
cmd = cmd.lstrip('^~')
|
||||
doc = info[0].__doc__ or '(no help text available)'
|
||||
hlp[cmd] = doc.splitlines()[0].rstrip()
|
||||
|
||||
hlplist = sorted(hlp)
|
||||
maxlen = max(map(len, hlplist))
|
||||
|
||||
write('usage: %s <command> [options]\n' % sysname())
|
||||
write('\ncommands:\n\n')
|
||||
for cmd in hlplist:
|
||||
doc = hlp[cmd]
|
||||
if False: # verbose?
|
||||
write(' %s:\n %s\n' % (cmd.replace('|', ', '), doc))
|
||||
else:
|
||||
write(' %-*s %s\n' % (maxlen, cmd.split('|', 1)[0],
|
||||
doc))
|
||||
|
||||
if not cmdtable:
|
||||
return err('No commands specified!\n')
|
||||
|
||||
if not name or name == 'shortlist':
|
||||
return helplist()
|
||||
|
||||
aliases, (cmd, options, usage) = findcmd(name, cmdtable)
|
||||
return help_cmd(cmd,
|
||||
replace_name(usage, sysname() + ' ' + aliases[0]),
|
||||
options + globalopts)
|
||||
return help_inner
|
||||
|
||||
def help_cmd(func, usage, options):
|
||||
'''show help for given command
|
||||
|
||||
- ``func``: function to generate help for (``func.__doc__`` is taken)
|
||||
- ``usage``: usage string
|
||||
- ``options``: options in usual format
|
||||
|
||||
>>> def test(*args, **opts):
|
||||
... """that's a test command
|
||||
...
|
||||
... you can do nothing with this command"""
|
||||
... pass
|
||||
>>> opts = [('l', 'listen', 'localhost',
|
||||
... 'ip to listen on'),
|
||||
... ('p', 'port', 8000,
|
||||
... 'port to listen on'),
|
||||
... ('d', 'daemonize', False,
|
||||
... 'daemonize process'),
|
||||
... ('', 'pid-file', '',
|
||||
... 'name of file to write process ID to')]
|
||||
>>> help_cmd(test, 'test [-l HOST] [NAME]', opts)
|
||||
test [-l HOST] [NAME]
|
||||
<BLANKLINE>
|
||||
that's a test command
|
||||
<BLANKLINE>
|
||||
you can do nothing with this command
|
||||
<BLANKLINE>
|
||||
options:
|
||||
<BLANKLINE>
|
||||
-l --listen ip to listen on (default: localhost)
|
||||
-p --port port to listen on (default: 8000)
|
||||
-d --daemonize daemonize process
|
||||
--pid-file name of file to write process ID to
|
||||
<BLANKLINE>
|
||||
'''
|
||||
write(usage + '\n')
|
||||
doc = func.__doc__
|
||||
if not doc:
|
||||
doc = '(no help text available)'
|
||||
write('\n' + doc.strip() + '\n\n')
|
||||
if options:
|
||||
write(''.join(help_options(options)))
|
||||
|
||||
def help_options(options):
|
||||
yield 'options:\n\n'
|
||||
output = []
|
||||
for short, name, default, desc in options:
|
||||
if hasattr(default, '__call__'):
|
||||
default = default(None)
|
||||
default = default and ' (default: %s)' % default or ''
|
||||
output.append(('%2s%s' % (short and '-%s' % short,
|
||||
name and ' --%s' % name),
|
||||
'%s%s' % (desc, default)))
|
||||
|
||||
opts_len = max([len(first) for first, second in output if second] or [0])
|
||||
for first, second in output:
|
||||
if second:
|
||||
# wrap description at 78 chars
|
||||
second = textwrap.wrap(second, width=(78 - opts_len - 3))
|
||||
pad = '\n' + ' ' * (opts_len + 3)
|
||||
yield ' %-*s %s\n' % (opts_len, first, pad.join(second))
|
||||
else:
|
||||
yield '%s\n' % first
|
||||
|
||||
|
||||
# --------
|
||||
# Options parsing
|
||||
# --------
|
||||
|
||||
def parse(args, options):
|
||||
'''
|
||||
>>> opts = [('l', 'listen', 'localhost',
|
||||
... 'ip to listen on'),
|
||||
... ('p', 'port', 8000,
|
||||
... 'port to listen on'),
|
||||
... ('d', 'daemonize', False,
|
||||
... 'daemonize process'),
|
||||
... ('', 'pid-file', '',
|
||||
... 'name of file to write process ID to')]
|
||||
>>> print parse(['-l', '0.0.0.0', '--pi', 'test', 'all'], opts)
|
||||
({'pid_file': 'test', 'daemonize': False, 'port': 8000, 'listen': '0.0.0.0'}, ['all'])
|
||||
|
||||
'''
|
||||
argmap, defmap, state = {}, {}, {}
|
||||
shortlist, namelist, funlist = '', [], []
|
||||
|
||||
for short, name, default, comment in options:
|
||||
if short and len(short) != 1:
|
||||
raise FOError('Short option should be only a single'
|
||||
' character: %s' % short)
|
||||
if not name:
|
||||
raise FOError(
|
||||
'Long name should be defined for every option')
|
||||
# change name to match Python styling
|
||||
pyname = name.replace('-', '_')
|
||||
argmap['-' + short] = argmap['--' + name] = pyname
|
||||
defmap[pyname] = default
|
||||
|
||||
# copy defaults to state
|
||||
if isinstance(default, list):
|
||||
state[pyname] = default[:]
|
||||
elif hasattr(default, '__call__'):
|
||||
funlist.append(pyname)
|
||||
state[pyname] = None
|
||||
else:
|
||||
state[pyname] = default
|
||||
|
||||
# getopt wants indication that it takes a parameter
|
||||
if not (default is None or default is True or default is False):
|
||||
if short: short += ':'
|
||||
if name: name += '='
|
||||
if short:
|
||||
shortlist += short
|
||||
if name:
|
||||
namelist.append(name)
|
||||
|
||||
opts, args = getopt.gnu_getopt(args, shortlist, namelist)
|
||||
|
||||
# transfer result to state
|
||||
for opt, val in opts:
|
||||
name = argmap[opt]
|
||||
t = type(defmap[name])
|
||||
if t is types.FunctionType:
|
||||
del funlist[funlist.index(name)]
|
||||
state[name] = defmap[name](val)
|
||||
elif t is types.ListType:
|
||||
state[name].append(val)
|
||||
elif t in (types.NoneType, types.BooleanType):
|
||||
state[name] = not defmap[name]
|
||||
else:
|
||||
state[name] = t(val)
|
||||
|
||||
for name in funlist:
|
||||
state[name] = defmap[name](None)
|
||||
|
||||
return state, args
|
||||
# --------
|
||||
# Subcommand system
|
||||
# --------
|
||||
|
||||
def _dispatch(args, cmdtable, globalopts):
|
||||
cmd, func, args, options = cmdparse(args, cmdtable, globalopts)
|
||||
|
||||
if options.pop('help', False):
|
||||
return 'help', cmdtable['help'][0], [cmd], {}
|
||||
elif not cmd:
|
||||
return 'help', cmdtable['help'][0], ['shortlist'], {}
|
||||
|
||||
return cmd, func, args, options
|
||||
|
||||
def cmdparse(args, cmdtable, globalopts):
|
||||
# command is the first non-option
|
||||
cmd = None
|
||||
for arg in args:
|
||||
if not arg.startswith('-'):
|
||||
cmd = arg
|
||||
break
|
||||
|
||||
if cmd:
|
||||
args.pop(args.index(cmd))
|
||||
|
||||
aliases, info = findcmd(cmd, cmdtable)
|
||||
cmd = aliases[0]
|
||||
possibleopts = list(info[1])
|
||||
else:
|
||||
possibleopts = []
|
||||
|
||||
possibleopts.extend(globalopts)
|
||||
|
||||
try:
|
||||
options, args = parse(args, possibleopts)
|
||||
except getopt.GetoptError, e:
|
||||
raise ParseError(cmd, e)
|
||||
|
||||
return (cmd, cmd and info[0] or None, args, options)
|
||||
|
||||
def aliases_(cmdtable_key):
|
||||
return cmdtable_key.lstrip("^~").split("|")
|
||||
|
||||
def findpossible(cmd, table):
|
||||
"""
|
||||
Return cmd -> (aliases, command table entry)
|
||||
for each matching command.
|
||||
"""
|
||||
choice = {}
|
||||
for e in table.keys():
|
||||
aliases = aliases_(e)
|
||||
found = None
|
||||
if cmd in aliases:
|
||||
found = cmd
|
||||
else:
|
||||
for a in aliases:
|
||||
if a.startswith(cmd):
|
||||
found = a
|
||||
break
|
||||
if found is not None:
|
||||
choice[found] = (aliases, table[e])
|
||||
|
||||
return choice
|
||||
|
||||
def findcmd(cmd, table):
|
||||
"""Return (aliases, command table entry) for command string."""
|
||||
choice = findpossible(cmd, table)
|
||||
|
||||
if cmd in choice:
|
||||
return choice[cmd]
|
||||
|
||||
if len(choice) > 1:
|
||||
clist = choice.keys()
|
||||
clist.sort()
|
||||
raise AmbiguousCommand(cmd, clist)
|
||||
|
||||
if choice:
|
||||
return choice.values()[0]
|
||||
|
||||
raise UnknownCommand(cmd)
|
||||
|
||||
# --------
|
||||
# Helpers
|
||||
# --------
|
||||
|
||||
def guess_options(func):
|
||||
args, varargs, varkw, defaults = inspect.getargspec(func)
|
||||
for name, option in zip(args[-len(defaults):], defaults):
|
||||
try:
|
||||
sname, default, hlp = option
|
||||
yield (sname, name.replace('_', '-'), default, hlp)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
def guess_usage(func, options):
|
||||
usage = '%name '
|
||||
if options:
|
||||
usage += '[OPTIONS] '
|
||||
args, varargs = inspect.getargspec(func)[:2]
|
||||
argnum = len(args) - len(options)
|
||||
if argnum > 0:
|
||||
usage += args[0].upper()
|
||||
if argnum > 1:
|
||||
usage += 'S'
|
||||
elif varargs:
|
||||
usage += '[%s]' % varargs.upper()
|
||||
return usage
|
||||
|
||||
def catcher(target, help_func):
|
||||
'''Catches all exceptions and prints human-readable information on them
|
||||
'''
|
||||
try:
|
||||
return target()
|
||||
except UnknownCommand, e:
|
||||
err("unknown command: '%s'\n" % e)
|
||||
except AmbiguousCommand, e:
|
||||
err("command '%s' is ambiguous:\n %s\n" %
|
||||
(e.args[0], ' '.join(e.args[1])))
|
||||
except ParseError, e:
|
||||
err('%s: %s\n' % (e.args[0], e.args[1]))
|
||||
help_func(e.args[0])
|
||||
except getopt.GetoptError, e:
|
||||
err('error: %s\n' % e)
|
||||
help_func()
|
||||
except FOError, e:
|
||||
err('%s\n' % e)
|
||||
except KeyboardInterrupt:
|
||||
err('interrupted!\n')
|
||||
except SystemExit:
|
||||
raise
|
||||
except:
|
||||
err('unknown exception encountered')
|
||||
raise
|
||||
|
||||
raise Abort
|
||||
|
||||
def call_cmd(name, func):
|
||||
def inner(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except TypeError:
|
||||
if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
|
||||
raise ParseError(name, "invalid arguments")
|
||||
raise
|
||||
return inner
|
||||
|
||||
def call_cmd_regular(func, opts):
|
||||
def inner(*args, **kwargs):
|
||||
funcargs, _, varkw, defaults = inspect.getargspec(func)
|
||||
if len(args) > len(funcargs):
|
||||
raise TypeError('You have supplied more positional arguments'
|
||||
' than applicable')
|
||||
|
||||
funckwargs = dict((lname.replace('-', '_'), default)
|
||||
for _, lname, default, _ in opts)
|
||||
if 'help' not in (defaults or ()) and not varkw:
|
||||
funckwargs.pop('help', None)
|
||||
funckwargs.update(kwargs)
|
||||
return func(*args, **funckwargs)
|
||||
return inner
|
||||
|
||||
def replace_name(usage, name):
|
||||
if '%name' in usage:
|
||||
return usage.replace('%name', name, 1)
|
||||
return name + ' ' + usage
|
||||
|
||||
def sysname():
|
||||
name = sys.argv[0]
|
||||
if name.startswith('./'):
|
||||
return name[2:]
|
||||
return name
|
||||
|
||||
try:
|
||||
from functools import wraps
|
||||
except ImportError:
|
||||
def wraps(wrapped, assigned=('__module__', '__name__', '__doc__'),
|
||||
updated=('__dict__',)):
|
||||
def inner(wrapper):
|
||||
for attr in assigned:
|
||||
setattr(wrapper, attr, getattr(wrapped, attr))
|
||||
for attr in updated:
|
||||
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
|
||||
return wrapper
|
||||
return inner
|
||||
|
||||
# --------
|
||||
# Autocomplete system
|
||||
# --------
|
||||
|
||||
# Borrowed from PIP
|
||||
def autocomplete(cmdtable, args):
|
||||
"""Command and option completion.
|
||||
|
||||
Enable by sourcing one of the completion shell scripts (bash or zsh).
|
||||
"""
|
||||
|
||||
# Don't complete if user hasn't sourced bash_completion file.
|
||||
if not os.environ.has_key('OPSTER_AUTO_COMPLETE'):
|
||||
return
|
||||
cwords = os.environ['COMP_WORDS'].split()[1:]
|
||||
cword = int(os.environ['COMP_CWORD'])
|
||||
|
||||
try:
|
||||
current = cwords[cword-1]
|
||||
except IndexError:
|
||||
current = ''
|
||||
|
||||
commands = []
|
||||
for k in cmdtable.keys():
|
||||
commands += aliases_(k)
|
||||
|
||||
# command
|
||||
if cword == 1:
|
||||
print ' '.join(filter(lambda x: x.startswith(current), commands))
|
||||
|
||||
# command options
|
||||
elif cwords[0] in commands:
|
||||
options = []
|
||||
aliases, (cmd, opts, usage) = findcmd(cwords[0], cmdtable)
|
||||
for (short, long, default, help) in opts:
|
||||
options.append('-%s' % short)
|
||||
options.append('--%s' % long)
|
||||
|
||||
options = [o for o in options if o.startswith(current)]
|
||||
print ' '.join(filter(lambda x: x.startswith(current), options))
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
COMPLETIONS = {
|
||||
'bash':
|
||||
"""
|
||||
# opster bash completion start
|
||||
_opster_completion()
|
||||
{
|
||||
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
|
||||
COMP_CWORD=$COMP_CWORD \\
|
||||
OPSTER_AUTO_COMPLETE=1 $1 ) )
|
||||
}
|
||||
complete -o default -F _opster_completion %s
|
||||
# opster bash completion end
|
||||
""",
|
||||
'zsh':
|
||||
"""
|
||||
# opster zsh completion start
|
||||
function _opster_completion {
|
||||
local words cword
|
||||
read -Ac words
|
||||
read -cn cword
|
||||
reply=( $( COMP_WORDS="$words[*]" \\
|
||||
COMP_CWORD=$(( cword-1 )) \\
|
||||
OPSTER_AUTO_COMPLETE=1 $words[1] ) )
|
||||
}
|
||||
compctl -K _opster_completion %s
|
||||
# opster zsh completion end
|
||||
"""
|
||||
}
|
||||
|
||||
@command(name='_completion', hide=True)
|
||||
def completion(type=('t', 'bash', 'Completion type (bash or zsh)')):
|
||||
"""Outputs completion script for bash or zsh."""
|
||||
|
||||
prog_name = os.path.split(sys.argv[0])[1]
|
||||
print COMPLETIONS[type] % prog_name
|
||||
|
||||
# --------
|
||||
# Exceptions
|
||||
# --------
|
||||
|
||||
# Command exceptions
|
||||
class CommandException(Exception):
|
||||
'Base class for command exceptions'
|
||||
|
||||
class AmbiguousCommand(CommandException):
|
||||
'Raised if command is ambiguous'
|
||||
|
||||
class UnknownCommand(CommandException):
|
||||
'Raised if command is unknown'
|
||||
|
||||
class ParseError(CommandException):
|
||||
'Raised on error in command line parsing'
|
||||
|
||||
class Abort(CommandException):
|
||||
'Abort execution'
|
||||
|
||||
class FOError(CommandException):
|
||||
'Raised on trouble with opster configuration'
|
||||
@@ -1,21 +1,27 @@
|
||||
"""Drop-in replacement for collections.OrderedDict by Raymond Hettinger
|
||||
# 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.
|
||||
|
||||
http://code.activestate.com/recipes/576693/
|
||||
|
||||
"""
|
||||
from UserDict import DictMixin
|
||||
|
||||
# Modified from original to support Python 2.4, see
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=53
|
||||
try:
|
||||
all
|
||||
except NameError:
|
||||
def all(seq):
|
||||
for elem in seq:
|
||||
if not elem:
|
||||
return False
|
||||
return True
|
||||
|
||||
class OrderedDict(dict, DictMixin):
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
@@ -63,12 +69,10 @@ class OrderedDict(dict, DictMixin):
|
||||
def popitem(self, last=True):
|
||||
if not self:
|
||||
raise KeyError('dictionary is empty')
|
||||
# Modified from original to support Python 2.4, see
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=53
|
||||
if last:
|
||||
key = reversed(self).next()
|
||||
key = next(reversed(self))
|
||||
else:
|
||||
key = iter(self).next()
|
||||
key = next(iter(self))
|
||||
value = self.pop(key)
|
||||
return key, value
|
||||
|
||||
@@ -97,7 +101,7 @@ class OrderedDict(dict, DictMixin):
|
||||
def __repr__(self):
|
||||
if not self:
|
||||
return '%s()' % (self.__class__.__name__,)
|
||||
return '%s(%r)' % (self.__class__.__name__, self.items())
|
||||
return '%s(%r)' % (self.__class__.__name__, list(self.items()))
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self)
|
||||
@@ -111,8 +115,12 @@ class OrderedDict(dict, DictMixin):
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, OrderedDict):
|
||||
return len(self)==len(other) and \
|
||||
all(p==q for p, q in zip(self.items(), other.items()))
|
||||
if len(self) != len(other):
|
||||
return False
|
||||
for p, q in zip(list(self.items()), list(other.items())):
|
||||
if p != q:
|
||||
return False
|
||||
return True
|
||||
return dict.__eq__(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
@@ -1,437 +0,0 @@
|
||||
r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
|
||||
JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
|
||||
interchange format.
|
||||
|
||||
:mod:`simplejson` exposes an API familiar to users of the standard library
|
||||
:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
|
||||
version of the :mod:`json` library contained in Python 2.6, but maintains
|
||||
compatibility with Python 2.4 and Python 2.5 and (currently) has
|
||||
significant performance advantages, even without using the optional C
|
||||
extension for speedups.
|
||||
|
||||
Encoding basic Python object hierarchies::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
|
||||
'["foo", {"bar": ["baz", null, 1.0, 2]}]'
|
||||
>>> print json.dumps("\"foo\bar")
|
||||
"\"foo\bar"
|
||||
>>> print json.dumps(u'\u1234')
|
||||
"\u1234"
|
||||
>>> print json.dumps('\\')
|
||||
"\\"
|
||||
>>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
|
||||
{"a": 0, "b": 0, "c": 0}
|
||||
>>> from StringIO import StringIO
|
||||
>>> io = StringIO()
|
||||
>>> json.dump(['streaming API'], io)
|
||||
>>> io.getvalue()
|
||||
'["streaming API"]'
|
||||
|
||||
Compact encoding::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
|
||||
'[1,2,3,{"4":5,"6":7}]'
|
||||
|
||||
Pretty printing::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=' ')
|
||||
>>> print '\n'.join([l.rstrip() for l in s.splitlines()])
|
||||
{
|
||||
"4": 5,
|
||||
"6": 7
|
||||
}
|
||||
|
||||
Decoding JSON::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
|
||||
>>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
|
||||
True
|
||||
>>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
|
||||
True
|
||||
>>> from StringIO import StringIO
|
||||
>>> io = StringIO('["streaming API"]')
|
||||
>>> json.load(io)[0] == 'streaming API'
|
||||
True
|
||||
|
||||
Specializing JSON object decoding::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> def as_complex(dct):
|
||||
... if '__complex__' in dct:
|
||||
... return complex(dct['real'], dct['imag'])
|
||||
... return dct
|
||||
...
|
||||
>>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
|
||||
... object_hook=as_complex)
|
||||
(1+2j)
|
||||
>>> from decimal import Decimal
|
||||
>>> json.loads('1.1', parse_float=Decimal) == Decimal('1.1')
|
||||
True
|
||||
|
||||
Specializing JSON object encoding::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> def encode_complex(obj):
|
||||
... if isinstance(obj, complex):
|
||||
... return [obj.real, obj.imag]
|
||||
... raise TypeError(repr(o) + " is not JSON serializable")
|
||||
...
|
||||
>>> json.dumps(2 + 1j, default=encode_complex)
|
||||
'[2.0, 1.0]'
|
||||
>>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
|
||||
'[2.0, 1.0]'
|
||||
>>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
|
||||
'[2.0, 1.0]'
|
||||
|
||||
|
||||
Using simplejson.tool from the shell to validate and pretty-print::
|
||||
|
||||
$ echo '{"json":"obj"}' | python -m simplejson.tool
|
||||
{
|
||||
"json": "obj"
|
||||
}
|
||||
$ echo '{ 1.2:3.4}' | python -m simplejson.tool
|
||||
Expecting property name: line 1 column 2 (char 2)
|
||||
"""
|
||||
__version__ = '2.1.1'
|
||||
__all__ = [
|
||||
'dump', 'dumps', 'load', 'loads',
|
||||
'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
|
||||
'OrderedDict',
|
||||
]
|
||||
|
||||
__author__ = 'Bob Ippolito <bob@redivi.com>'
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from decoder import JSONDecoder, JSONDecodeError
|
||||
from encoder import JSONEncoder
|
||||
def _import_OrderedDict():
|
||||
import collections
|
||||
try:
|
||||
return collections.OrderedDict
|
||||
except AttributeError:
|
||||
import ordered_dict
|
||||
return ordered_dict.OrderedDict
|
||||
OrderedDict = _import_OrderedDict()
|
||||
|
||||
def _import_c_make_encoder():
|
||||
try:
|
||||
from simplejson._speedups import make_encoder
|
||||
return make_encoder
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
_default_encoder = JSONEncoder(
|
||||
skipkeys=False,
|
||||
ensure_ascii=True,
|
||||
check_circular=True,
|
||||
allow_nan=True,
|
||||
indent=None,
|
||||
separators=None,
|
||||
encoding='utf-8',
|
||||
default=None,
|
||||
use_decimal=False,
|
||||
)
|
||||
|
||||
def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
|
||||
allow_nan=True, cls=None, indent=None, separators=None,
|
||||
encoding='utf-8', default=None, use_decimal=False, **kw):
|
||||
"""Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
|
||||
``.write()``-supporting file-like object).
|
||||
|
||||
If ``skipkeys`` is true then ``dict`` keys that are not basic types
|
||||
(``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
|
||||
will be skipped instead of raising a ``TypeError``.
|
||||
|
||||
If ``ensure_ascii`` is false, then the some chunks written to ``fp``
|
||||
may be ``unicode`` instances, subject to normal Python ``str`` to
|
||||
``unicode`` coercion rules. Unless ``fp.write()`` explicitly
|
||||
understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
|
||||
to cause an error.
|
||||
|
||||
If ``check_circular`` is false, then the circular reference check
|
||||
for container types will be skipped and a circular reference will
|
||||
result in an ``OverflowError`` (or worse).
|
||||
|
||||
If ``allow_nan`` is false, then it will be a ``ValueError`` to
|
||||
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
|
||||
in strict compliance of the JSON specification, instead of using the
|
||||
JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
|
||||
|
||||
If *indent* is a string, then JSON array elements and object members
|
||||
will be pretty-printed with a newline followed by that string repeated
|
||||
for each level of nesting. ``None`` (the default) selects the most compact
|
||||
representation without any newlines. For backwards compatibility with
|
||||
versions of simplejson earlier than 2.1.0, an integer is also accepted
|
||||
and is converted to a string with that many spaces.
|
||||
|
||||
If ``separators`` is an ``(item_separator, dict_separator)`` tuple
|
||||
then it will be used instead of the default ``(', ', ': ')`` separators.
|
||||
``(',', ':')`` is the most compact JSON representation.
|
||||
|
||||
``encoding`` is the character encoding for str instances, default is UTF-8.
|
||||
|
||||
``default(obj)`` is a function that should return a serializable version
|
||||
of obj or raise TypeError. The default simply raises TypeError.
|
||||
|
||||
If *use_decimal* is true (default: ``False``) then decimal.Decimal
|
||||
will be natively serialized to JSON with full precision.
|
||||
|
||||
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
|
||||
``.default()`` method to serialize additional types), specify it with
|
||||
the ``cls`` kwarg.
|
||||
|
||||
"""
|
||||
# cached encoder
|
||||
if (not skipkeys and ensure_ascii and
|
||||
check_circular and allow_nan and
|
||||
cls is None and indent is None and separators is None and
|
||||
encoding == 'utf-8' and default is None and not kw):
|
||||
iterable = _default_encoder.iterencode(obj)
|
||||
else:
|
||||
if cls is None:
|
||||
cls = JSONEncoder
|
||||
iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
|
||||
check_circular=check_circular, allow_nan=allow_nan, indent=indent,
|
||||
separators=separators, encoding=encoding,
|
||||
default=default, use_decimal=use_decimal, **kw).iterencode(obj)
|
||||
# could accelerate with writelines in some versions of Python, at
|
||||
# a debuggability cost
|
||||
for chunk in iterable:
|
||||
fp.write(chunk)
|
||||
|
||||
|
||||
def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
|
||||
allow_nan=True, cls=None, indent=None, separators=None,
|
||||
encoding='utf-8', default=None, use_decimal=False, **kw):
|
||||
"""Serialize ``obj`` to a JSON formatted ``str``.
|
||||
|
||||
If ``skipkeys`` is false then ``dict`` keys that are not basic types
|
||||
(``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
|
||||
will be skipped instead of raising a ``TypeError``.
|
||||
|
||||
If ``ensure_ascii`` is false, then the return value will be a
|
||||
``unicode`` instance subject to normal Python ``str`` to ``unicode``
|
||||
coercion rules instead of being escaped to an ASCII ``str``.
|
||||
|
||||
If ``check_circular`` is false, then the circular reference check
|
||||
for container types will be skipped and a circular reference will
|
||||
result in an ``OverflowError`` (or worse).
|
||||
|
||||
If ``allow_nan`` is false, then it will be a ``ValueError`` to
|
||||
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
|
||||
strict compliance of the JSON specification, instead of using the
|
||||
JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
|
||||
|
||||
If ``indent`` is a string, then JSON array elements and object members
|
||||
will be pretty-printed with a newline followed by that string repeated
|
||||
for each level of nesting. ``None`` (the default) selects the most compact
|
||||
representation without any newlines. For backwards compatibility with
|
||||
versions of simplejson earlier than 2.1.0, an integer is also accepted
|
||||
and is converted to a string with that many spaces.
|
||||
|
||||
If ``separators`` is an ``(item_separator, dict_separator)`` tuple
|
||||
then it will be used instead of the default ``(', ', ': ')`` separators.
|
||||
``(',', ':')`` is the most compact JSON representation.
|
||||
|
||||
``encoding`` is the character encoding for str instances, default is UTF-8.
|
||||
|
||||
``default(obj)`` is a function that should return a serializable version
|
||||
of obj or raise TypeError. The default simply raises TypeError.
|
||||
|
||||
If *use_decimal* is true (default: ``False``) then decimal.Decimal
|
||||
will be natively serialized to JSON with full precision.
|
||||
|
||||
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
|
||||
``.default()`` method to serialize additional types), specify it with
|
||||
the ``cls`` kwarg.
|
||||
|
||||
"""
|
||||
# cached encoder
|
||||
if (not skipkeys and ensure_ascii and
|
||||
check_circular and allow_nan and
|
||||
cls is None and indent is None and separators is None and
|
||||
encoding == 'utf-8' and default is None and not use_decimal
|
||||
and not kw):
|
||||
return _default_encoder.encode(obj)
|
||||
if cls is None:
|
||||
cls = JSONEncoder
|
||||
return cls(
|
||||
skipkeys=skipkeys, ensure_ascii=ensure_ascii,
|
||||
check_circular=check_circular, allow_nan=allow_nan, indent=indent,
|
||||
separators=separators, encoding=encoding, default=default,
|
||||
use_decimal=use_decimal, **kw).encode(obj)
|
||||
|
||||
|
||||
_default_decoder = JSONDecoder(encoding=None, object_hook=None,
|
||||
object_pairs_hook=None)
|
||||
|
||||
|
||||
def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
|
||||
parse_int=None, parse_constant=None, object_pairs_hook=None,
|
||||
use_decimal=False, **kw):
|
||||
"""Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
|
||||
a JSON document) to a Python object.
|
||||
|
||||
*encoding* determines the encoding used to interpret any
|
||||
:class:`str` objects decoded by this instance (``'utf-8'`` by
|
||||
default). It has no effect when decoding :class:`unicode` objects.
|
||||
|
||||
Note that currently only encodings that are a superset of ASCII work,
|
||||
strings of other encodings should be passed in as :class:`unicode`.
|
||||
|
||||
*object_hook*, if specified, will be called with the result of every
|
||||
JSON object decoded and its return value will be used in place of the
|
||||
given :class:`dict`. This can be used to provide custom
|
||||
deserializations (e.g. to support JSON-RPC class hinting).
|
||||
|
||||
*object_pairs_hook* is an optional function that will be called with
|
||||
the result of any object literal decode with an ordered list of pairs.
|
||||
The return value of *object_pairs_hook* will be used instead of the
|
||||
:class:`dict`. This feature can be used to implement custom decoders
|
||||
that rely on the order that the key and value pairs are decoded (for
|
||||
example, :func:`collections.OrderedDict` will remember the order of
|
||||
insertion). If *object_hook* is also defined, the *object_pairs_hook*
|
||||
takes priority.
|
||||
|
||||
*parse_float*, if specified, will be called with the string of every
|
||||
JSON float to be decoded. By default, this is equivalent to
|
||||
``float(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON floats (e.g. :class:`decimal.Decimal`).
|
||||
|
||||
*parse_int*, if specified, will be called with the string of every
|
||||
JSON int to be decoded. By default, this is equivalent to
|
||||
``int(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON integers (e.g. :class:`float`).
|
||||
|
||||
*parse_constant*, if specified, will be called with one of the
|
||||
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
|
||||
can be used to raise an exception if invalid JSON numbers are
|
||||
encountered.
|
||||
|
||||
If *use_decimal* is true (default: ``False``) then it implies
|
||||
parse_float=decimal.Decimal for parity with ``dump``.
|
||||
|
||||
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
|
||||
kwarg.
|
||||
|
||||
"""
|
||||
return loads(fp.read(),
|
||||
encoding=encoding, cls=cls, object_hook=object_hook,
|
||||
parse_float=parse_float, parse_int=parse_int,
|
||||
parse_constant=parse_constant, object_pairs_hook=object_pairs_hook,
|
||||
use_decimal=use_decimal, **kw)
|
||||
|
||||
|
||||
def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
|
||||
parse_int=None, parse_constant=None, object_pairs_hook=None,
|
||||
use_decimal=False, **kw):
|
||||
"""Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
|
||||
document) to a Python object.
|
||||
|
||||
*encoding* determines the encoding used to interpret any
|
||||
:class:`str` objects decoded by this instance (``'utf-8'`` by
|
||||
default). It has no effect when decoding :class:`unicode` objects.
|
||||
|
||||
Note that currently only encodings that are a superset of ASCII work,
|
||||
strings of other encodings should be passed in as :class:`unicode`.
|
||||
|
||||
*object_hook*, if specified, will be called with the result of every
|
||||
JSON object decoded and its return value will be used in place of the
|
||||
given :class:`dict`. This can be used to provide custom
|
||||
deserializations (e.g. to support JSON-RPC class hinting).
|
||||
|
||||
*object_pairs_hook* is an optional function that will be called with
|
||||
the result of any object literal decode with an ordered list of pairs.
|
||||
The return value of *object_pairs_hook* will be used instead of the
|
||||
:class:`dict`. This feature can be used to implement custom decoders
|
||||
that rely on the order that the key and value pairs are decoded (for
|
||||
example, :func:`collections.OrderedDict` will remember the order of
|
||||
insertion). If *object_hook* is also defined, the *object_pairs_hook*
|
||||
takes priority.
|
||||
|
||||
*parse_float*, if specified, will be called with the string of every
|
||||
JSON float to be decoded. By default, this is equivalent to
|
||||
``float(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON floats (e.g. :class:`decimal.Decimal`).
|
||||
|
||||
*parse_int*, if specified, will be called with the string of every
|
||||
JSON int to be decoded. By default, this is equivalent to
|
||||
``int(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON integers (e.g. :class:`float`).
|
||||
|
||||
*parse_constant*, if specified, will be called with one of the
|
||||
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
|
||||
can be used to raise an exception if invalid JSON numbers are
|
||||
encountered.
|
||||
|
||||
If *use_decimal* is true (default: ``False``) then it implies
|
||||
parse_float=decimal.Decimal for parity with ``dump``.
|
||||
|
||||
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
|
||||
kwarg.
|
||||
|
||||
"""
|
||||
if (cls is None and encoding is None and object_hook is None and
|
||||
parse_int is None and parse_float is None and
|
||||
parse_constant is None and object_pairs_hook is None
|
||||
and not use_decimal and not kw):
|
||||
return _default_decoder.decode(s)
|
||||
if cls is None:
|
||||
cls = JSONDecoder
|
||||
if object_hook is not None:
|
||||
kw['object_hook'] = object_hook
|
||||
if object_pairs_hook is not None:
|
||||
kw['object_pairs_hook'] = object_pairs_hook
|
||||
if parse_float is not None:
|
||||
kw['parse_float'] = parse_float
|
||||
if parse_int is not None:
|
||||
kw['parse_int'] = parse_int
|
||||
if parse_constant is not None:
|
||||
kw['parse_constant'] = parse_constant
|
||||
if use_decimal:
|
||||
if parse_float is not None:
|
||||
raise TypeError("use_decimal=True implies parse_float=Decimal")
|
||||
kw['parse_float'] = Decimal
|
||||
return cls(encoding=encoding, **kw).decode(s)
|
||||
|
||||
|
||||
def _toggle_speedups(enabled):
|
||||
import simplejson.decoder as dec
|
||||
import simplejson.encoder as enc
|
||||
import simplejson.scanner as scan
|
||||
c_make_encoder = _import_c_make_encoder()
|
||||
if enabled:
|
||||
dec.scanstring = dec.c_scanstring or dec.py_scanstring
|
||||
enc.c_make_encoder = c_make_encoder
|
||||
enc.encode_basestring_ascii = (enc.c_encode_basestring_ascii or
|
||||
enc.py_encode_basestring_ascii)
|
||||
scan.make_scanner = scan.c_make_scanner or scan.py_make_scanner
|
||||
else:
|
||||
dec.scanstring = dec.py_scanstring
|
||||
enc.c_make_encoder = None
|
||||
enc.encode_basestring_ascii = enc.py_encode_basestring_ascii
|
||||
scan.make_scanner = scan.py_make_scanner
|
||||
dec.make_scanner = scan.make_scanner
|
||||
global _default_decoder
|
||||
_default_decoder = JSONDecoder(
|
||||
encoding=None,
|
||||
object_hook=None,
|
||||
object_pairs_hook=None,
|
||||
)
|
||||
global _default_encoder
|
||||
_default_encoder = JSONEncoder(
|
||||
skipkeys=False,
|
||||
ensure_ascii=True,
|
||||
check_circular=True,
|
||||
allow_nan=True,
|
||||
indent=None,
|
||||
separators=None,
|
||||
encoding='utf-8',
|
||||
default=None,
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,421 +0,0 @@
|
||||
"""Implementation of JSONDecoder
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import struct
|
||||
|
||||
from simplejson.scanner import make_scanner
|
||||
def _import_c_scanstring():
|
||||
try:
|
||||
from simplejson._speedups import scanstring
|
||||
return scanstring
|
||||
except ImportError:
|
||||
return None
|
||||
c_scanstring = _import_c_scanstring()
|
||||
|
||||
__all__ = ['JSONDecoder']
|
||||
|
||||
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
|
||||
|
||||
def _floatconstants():
|
||||
_BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
|
||||
# The struct module in Python 2.4 would get frexp() out of range here
|
||||
# when an endian is specified in the format string. Fixed in Python 2.5+
|
||||
if sys.byteorder != 'big':
|
||||
_BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
|
||||
nan, inf = struct.unpack('dd', _BYTES)
|
||||
return nan, inf, -inf
|
||||
|
||||
NaN, PosInf, NegInf = _floatconstants()
|
||||
|
||||
|
||||
class JSONDecodeError(ValueError):
|
||||
"""Subclass of ValueError with the following additional properties:
|
||||
|
||||
msg: The unformatted error message
|
||||
doc: The JSON document being parsed
|
||||
pos: The start index of doc where parsing failed
|
||||
end: The end index of doc where parsing failed (may be None)
|
||||
lineno: The line corresponding to pos
|
||||
colno: The column corresponding to pos
|
||||
endlineno: The line corresponding to end (may be None)
|
||||
endcolno: The column corresponding to end (may be None)
|
||||
|
||||
"""
|
||||
def __init__(self, msg, doc, pos, end=None):
|
||||
ValueError.__init__(self, errmsg(msg, doc, pos, end=end))
|
||||
self.msg = msg
|
||||
self.doc = doc
|
||||
self.pos = pos
|
||||
self.end = end
|
||||
self.lineno, self.colno = linecol(doc, pos)
|
||||
if end is not None:
|
||||
self.endlineno, self.endcolno = linecol(doc, pos)
|
||||
else:
|
||||
self.endlineno, self.endcolno = None, None
|
||||
|
||||
|
||||
def linecol(doc, pos):
|
||||
lineno = doc.count('\n', 0, pos) + 1
|
||||
if lineno == 1:
|
||||
colno = pos
|
||||
else:
|
||||
colno = pos - doc.rindex('\n', 0, pos)
|
||||
return lineno, colno
|
||||
|
||||
|
||||
def errmsg(msg, doc, pos, end=None):
|
||||
# Note that this function is called from _speedups
|
||||
lineno, colno = linecol(doc, pos)
|
||||
if end is None:
|
||||
#fmt = '{0}: line {1} column {2} (char {3})'
|
||||
#return fmt.format(msg, lineno, colno, pos)
|
||||
fmt = '%s: line %d column %d (char %d)'
|
||||
return fmt % (msg, lineno, colno, pos)
|
||||
endlineno, endcolno = linecol(doc, end)
|
||||
#fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
|
||||
#return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
|
||||
fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
|
||||
return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
|
||||
|
||||
|
||||
_CONSTANTS = {
|
||||
'-Infinity': NegInf,
|
||||
'Infinity': PosInf,
|
||||
'NaN': NaN,
|
||||
}
|
||||
|
||||
STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
|
||||
BACKSLASH = {
|
||||
'"': u'"', '\\': u'\\', '/': u'/',
|
||||
'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
|
||||
}
|
||||
|
||||
DEFAULT_ENCODING = "utf-8"
|
||||
|
||||
def py_scanstring(s, end, encoding=None, strict=True,
|
||||
_b=BACKSLASH, _m=STRINGCHUNK.match):
|
||||
"""Scan the string s for a JSON string. End is the index of the
|
||||
character in s after the quote that started the JSON string.
|
||||
Unescapes all valid JSON string escape sequences and raises ValueError
|
||||
on attempt to decode an invalid string. If strict is False then literal
|
||||
control characters are allowed in the string.
|
||||
|
||||
Returns a tuple of the decoded string and the index of the character in s
|
||||
after the end quote."""
|
||||
if encoding is None:
|
||||
encoding = DEFAULT_ENCODING
|
||||
chunks = []
|
||||
_append = chunks.append
|
||||
begin = end - 1
|
||||
while 1:
|
||||
chunk = _m(s, end)
|
||||
if chunk is None:
|
||||
raise JSONDecodeError(
|
||||
"Unterminated string starting at", s, begin)
|
||||
end = chunk.end()
|
||||
content, terminator = chunk.groups()
|
||||
# Content is contains zero or more unescaped string characters
|
||||
if content:
|
||||
if not isinstance(content, unicode):
|
||||
content = unicode(content, encoding)
|
||||
_append(content)
|
||||
# Terminator is the end of string, a literal control character,
|
||||
# or a backslash denoting that an escape sequence follows
|
||||
if terminator == '"':
|
||||
break
|
||||
elif terminator != '\\':
|
||||
if strict:
|
||||
msg = "Invalid control character %r at" % (terminator,)
|
||||
#msg = "Invalid control character {0!r} at".format(terminator)
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
else:
|
||||
_append(terminator)
|
||||
continue
|
||||
try:
|
||||
esc = s[end]
|
||||
except IndexError:
|
||||
raise JSONDecodeError(
|
||||
"Unterminated string starting at", s, begin)
|
||||
# If not a unicode escape sequence, must be in the lookup table
|
||||
if esc != 'u':
|
||||
try:
|
||||
char = _b[esc]
|
||||
except KeyError:
|
||||
msg = "Invalid \\escape: " + repr(esc)
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
end += 1
|
||||
else:
|
||||
# Unicode escape sequence
|
||||
esc = s[end + 1:end + 5]
|
||||
next_end = end + 5
|
||||
if len(esc) != 4:
|
||||
msg = "Invalid \\uXXXX escape"
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
uni = int(esc, 16)
|
||||
# Check for surrogate pair on UCS-4 systems
|
||||
if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
|
||||
msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
|
||||
if not s[end + 5:end + 7] == '\\u':
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
esc2 = s[end + 7:end + 11]
|
||||
if len(esc2) != 4:
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
uni2 = int(esc2, 16)
|
||||
uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
|
||||
next_end += 6
|
||||
char = unichr(uni)
|
||||
end = next_end
|
||||
# Append the unescaped character
|
||||
_append(char)
|
||||
return u''.join(chunks), end
|
||||
|
||||
|
||||
# Use speedup if available
|
||||
scanstring = c_scanstring or py_scanstring
|
||||
|
||||
WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
|
||||
WHITESPACE_STR = ' \t\n\r'
|
||||
|
||||
def JSONObject((s, end), encoding, strict, scan_once, object_hook,
|
||||
object_pairs_hook, memo=None,
|
||||
_w=WHITESPACE.match, _ws=WHITESPACE_STR):
|
||||
# Backwards compatibility
|
||||
if memo is None:
|
||||
memo = {}
|
||||
memo_get = memo.setdefault
|
||||
pairs = []
|
||||
# Use a slice to prevent IndexError from being raised, the following
|
||||
# check will raise a more specific ValueError if the string is empty
|
||||
nextchar = s[end:end + 1]
|
||||
# Normally we expect nextchar == '"'
|
||||
if nextchar != '"':
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end).end()
|
||||
nextchar = s[end:end + 1]
|
||||
# Trivial empty object
|
||||
if nextchar == '}':
|
||||
if object_pairs_hook is not None:
|
||||
result = object_pairs_hook(pairs)
|
||||
return result, end
|
||||
pairs = {}
|
||||
if object_hook is not None:
|
||||
pairs = object_hook(pairs)
|
||||
return pairs, end + 1
|
||||
elif nextchar != '"':
|
||||
raise JSONDecodeError("Expecting property name", s, end)
|
||||
end += 1
|
||||
while True:
|
||||
key, end = scanstring(s, end, encoding, strict)
|
||||
key = memo_get(key, key)
|
||||
|
||||
# To skip some function call overhead we optimize the fast paths where
|
||||
# the JSON key separator is ": " or just ":".
|
||||
if s[end:end + 1] != ':':
|
||||
end = _w(s, end).end()
|
||||
if s[end:end + 1] != ':':
|
||||
raise JSONDecodeError("Expecting : delimiter", s, end)
|
||||
|
||||
end += 1
|
||||
|
||||
try:
|
||||
if s[end] in _ws:
|
||||
end += 1
|
||||
if s[end] in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
try:
|
||||
value, end = scan_once(s, end)
|
||||
except StopIteration:
|
||||
raise JSONDecodeError("Expecting object", s, end)
|
||||
pairs.append((key, value))
|
||||
|
||||
try:
|
||||
nextchar = s[end]
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
nextchar = s[end]
|
||||
except IndexError:
|
||||
nextchar = ''
|
||||
end += 1
|
||||
|
||||
if nextchar == '}':
|
||||
break
|
||||
elif nextchar != ',':
|
||||
raise JSONDecodeError("Expecting , delimiter", s, end - 1)
|
||||
|
||||
try:
|
||||
nextchar = s[end]
|
||||
if nextchar in _ws:
|
||||
end += 1
|
||||
nextchar = s[end]
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
nextchar = s[end]
|
||||
except IndexError:
|
||||
nextchar = ''
|
||||
|
||||
end += 1
|
||||
if nextchar != '"':
|
||||
raise JSONDecodeError("Expecting property name", s, end - 1)
|
||||
|
||||
if object_pairs_hook is not None:
|
||||
result = object_pairs_hook(pairs)
|
||||
return result, end
|
||||
pairs = dict(pairs)
|
||||
if object_hook is not None:
|
||||
pairs = object_hook(pairs)
|
||||
return pairs, end
|
||||
|
||||
def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
|
||||
values = []
|
||||
nextchar = s[end:end + 1]
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
nextchar = s[end:end + 1]
|
||||
# Look-ahead for trivial empty array
|
||||
if nextchar == ']':
|
||||
return values, end + 1
|
||||
_append = values.append
|
||||
while True:
|
||||
try:
|
||||
value, end = scan_once(s, end)
|
||||
except StopIteration:
|
||||
raise JSONDecodeError("Expecting object", s, end)
|
||||
_append(value)
|
||||
nextchar = s[end:end + 1]
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
nextchar = s[end:end + 1]
|
||||
end += 1
|
||||
if nextchar == ']':
|
||||
break
|
||||
elif nextchar != ',':
|
||||
raise JSONDecodeError("Expecting , delimiter", s, end)
|
||||
|
||||
try:
|
||||
if s[end] in _ws:
|
||||
end += 1
|
||||
if s[end] in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
return values, end
|
||||
|
||||
class JSONDecoder(object):
|
||||
"""Simple JSON <http://json.org> decoder
|
||||
|
||||
Performs the following translations in decoding by default:
|
||||
|
||||
+---------------+-------------------+
|
||||
| JSON | Python |
|
||||
+===============+===================+
|
||||
| object | dict |
|
||||
+---------------+-------------------+
|
||||
| array | list |
|
||||
+---------------+-------------------+
|
||||
| string | unicode |
|
||||
+---------------+-------------------+
|
||||
| number (int) | int, long |
|
||||
+---------------+-------------------+
|
||||
| number (real) | float |
|
||||
+---------------+-------------------+
|
||||
| true | True |
|
||||
+---------------+-------------------+
|
||||
| false | False |
|
||||
+---------------+-------------------+
|
||||
| null | None |
|
||||
+---------------+-------------------+
|
||||
|
||||
It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
|
||||
their corresponding ``float`` values, which is outside the JSON spec.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, encoding=None, object_hook=None, parse_float=None,
|
||||
parse_int=None, parse_constant=None, strict=True,
|
||||
object_pairs_hook=None):
|
||||
"""
|
||||
*encoding* determines the encoding used to interpret any
|
||||
:class:`str` objects decoded by this instance (``'utf-8'`` by
|
||||
default). It has no effect when decoding :class:`unicode` objects.
|
||||
|
||||
Note that currently only encodings that are a superset of ASCII work,
|
||||
strings of other encodings should be passed in as :class:`unicode`.
|
||||
|
||||
*object_hook*, if specified, will be called with the result of every
|
||||
JSON object decoded and its return value will be used in place of the
|
||||
given :class:`dict`. This can be used to provide custom
|
||||
deserializations (e.g. to support JSON-RPC class hinting).
|
||||
|
||||
*object_pairs_hook* is an optional function that will be called with
|
||||
the result of any object literal decode with an ordered list of pairs.
|
||||
The return value of *object_pairs_hook* will be used instead of the
|
||||
:class:`dict`. This feature can be used to implement custom decoders
|
||||
that rely on the order that the key and value pairs are decoded (for
|
||||
example, :func:`collections.OrderedDict` will remember the order of
|
||||
insertion). If *object_hook* is also defined, the *object_pairs_hook*
|
||||
takes priority.
|
||||
|
||||
*parse_float*, if specified, will be called with the string of every
|
||||
JSON float to be decoded. By default, this is equivalent to
|
||||
``float(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON floats (e.g. :class:`decimal.Decimal`).
|
||||
|
||||
*parse_int*, if specified, will be called with the string of every
|
||||
JSON int to be decoded. By default, this is equivalent to
|
||||
``int(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON integers (e.g. :class:`float`).
|
||||
|
||||
*parse_constant*, if specified, will be called with one of the
|
||||
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
|
||||
can be used to raise an exception if invalid JSON numbers are
|
||||
encountered.
|
||||
|
||||
*strict* controls the parser's behavior when it encounters an
|
||||
invalid control character in a string. The default setting of
|
||||
``True`` means that unescaped control characters are parse errors, if
|
||||
``False`` then control characters will be allowed in strings.
|
||||
|
||||
"""
|
||||
self.encoding = encoding
|
||||
self.object_hook = object_hook
|
||||
self.object_pairs_hook = object_pairs_hook
|
||||
self.parse_float = parse_float or float
|
||||
self.parse_int = parse_int or int
|
||||
self.parse_constant = parse_constant or _CONSTANTS.__getitem__
|
||||
self.strict = strict
|
||||
self.parse_object = JSONObject
|
||||
self.parse_array = JSONArray
|
||||
self.parse_string = scanstring
|
||||
self.memo = {}
|
||||
self.scan_once = make_scanner(self)
|
||||
|
||||
def decode(self, s, _w=WHITESPACE.match):
|
||||
"""Return the Python representation of ``s`` (a ``str`` or ``unicode``
|
||||
instance containing a JSON document)
|
||||
|
||||
"""
|
||||
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
|
||||
end = _w(s, end).end()
|
||||
if end != len(s):
|
||||
raise JSONDecodeError("Extra data", s, end, len(s))
|
||||
return obj
|
||||
|
||||
def raw_decode(self, s, idx=0):
|
||||
"""Decode a JSON document from ``s`` (a ``str`` or ``unicode``
|
||||
beginning with a JSON document) and return a 2-tuple of the Python
|
||||
representation and the index in ``s`` where the document ended.
|
||||
|
||||
This can be used to decode a JSON document from a string that may
|
||||
have extraneous data at the end.
|
||||
|
||||
"""
|
||||
try:
|
||||
obj, end = self.scan_once(s, idx)
|
||||
except StopIteration:
|
||||
raise JSONDecodeError("No JSON object could be decoded", s, idx)
|
||||
return obj, end
|
||||
@@ -1,501 +0,0 @@
|
||||
"""Implementation of JSONEncoder
|
||||
"""
|
||||
import re
|
||||
from decimal import Decimal
|
||||
|
||||
def _import_speedups():
|
||||
try:
|
||||
from simplejson import _speedups
|
||||
return _speedups.encode_basestring_ascii, _speedups.make_encoder
|
||||
except ImportError:
|
||||
return None, None
|
||||
c_encode_basestring_ascii, c_make_encoder = _import_speedups()
|
||||
|
||||
from simplejson.decoder import PosInf
|
||||
|
||||
ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
|
||||
ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
|
||||
HAS_UTF8 = re.compile(r'[\x80-\xff]')
|
||||
ESCAPE_DCT = {
|
||||
'\\': '\\\\',
|
||||
'"': '\\"',
|
||||
'\b': '\\b',
|
||||
'\f': '\\f',
|
||||
'\n': '\\n',
|
||||
'\r': '\\r',
|
||||
'\t': '\\t',
|
||||
}
|
||||
for i in range(0x20):
|
||||
#ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
|
||||
ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
|
||||
|
||||
FLOAT_REPR = repr
|
||||
|
||||
def encode_basestring(s):
|
||||
"""Return a JSON representation of a Python string
|
||||
|
||||
"""
|
||||
if isinstance(s, str) and HAS_UTF8.search(s) is not None:
|
||||
s = s.decode('utf-8')
|
||||
def replace(match):
|
||||
return ESCAPE_DCT[match.group(0)]
|
||||
return u'"' + ESCAPE.sub(replace, s) + u'"'
|
||||
|
||||
|
||||
def py_encode_basestring_ascii(s):
|
||||
"""Return an ASCII-only JSON representation of a Python string
|
||||
|
||||
"""
|
||||
if isinstance(s, str) and HAS_UTF8.search(s) is not None:
|
||||
s = s.decode('utf-8')
|
||||
def replace(match):
|
||||
s = match.group(0)
|
||||
try:
|
||||
return ESCAPE_DCT[s]
|
||||
except KeyError:
|
||||
n = ord(s)
|
||||
if n < 0x10000:
|
||||
#return '\\u{0:04x}'.format(n)
|
||||
return '\\u%04x' % (n,)
|
||||
else:
|
||||
# surrogate pair
|
||||
n -= 0x10000
|
||||
s1 = 0xd800 | ((n >> 10) & 0x3ff)
|
||||
s2 = 0xdc00 | (n & 0x3ff)
|
||||
#return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
|
||||
return '\\u%04x\\u%04x' % (s1, s2)
|
||||
return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
|
||||
|
||||
|
||||
encode_basestring_ascii = (
|
||||
c_encode_basestring_ascii or py_encode_basestring_ascii)
|
||||
|
||||
class JSONEncoder(object):
|
||||
"""Extensible JSON <http://json.org> encoder for Python data structures.
|
||||
|
||||
Supports the following objects and types by default:
|
||||
|
||||
+-------------------+---------------+
|
||||
| Python | JSON |
|
||||
+===================+===============+
|
||||
| dict | object |
|
||||
+-------------------+---------------+
|
||||
| list, tuple | array |
|
||||
+-------------------+---------------+
|
||||
| str, unicode | string |
|
||||
+-------------------+---------------+
|
||||
| int, long, float | number |
|
||||
+-------------------+---------------+
|
||||
| True | true |
|
||||
+-------------------+---------------+
|
||||
| False | false |
|
||||
+-------------------+---------------+
|
||||
| None | null |
|
||||
+-------------------+---------------+
|
||||
|
||||
To extend this to recognize other objects, subclass and implement a
|
||||
``.default()`` method with another method that returns a serializable
|
||||
object for ``o`` if possible, otherwise it should call the superclass
|
||||
implementation (to raise ``TypeError``).
|
||||
|
||||
"""
|
||||
item_separator = ', '
|
||||
key_separator = ': '
|
||||
def __init__(self, skipkeys=False, ensure_ascii=True,
|
||||
check_circular=True, allow_nan=True, sort_keys=False,
|
||||
indent=None, separators=None, encoding='utf-8', default=None,
|
||||
use_decimal=False):
|
||||
"""Constructor for JSONEncoder, with sensible defaults.
|
||||
|
||||
If skipkeys is false, then it is a TypeError to attempt
|
||||
encoding of keys that are not str, int, long, float or None. If
|
||||
skipkeys is True, such items are simply skipped.
|
||||
|
||||
If ensure_ascii is true, the output is guaranteed to be str
|
||||
objects with all incoming unicode characters escaped. If
|
||||
ensure_ascii is false, the output will be unicode object.
|
||||
|
||||
If check_circular is true, then lists, dicts, and custom encoded
|
||||
objects will be checked for circular references during encoding to
|
||||
prevent an infinite recursion (which would cause an OverflowError).
|
||||
Otherwise, no such check takes place.
|
||||
|
||||
If allow_nan is true, then NaN, Infinity, and -Infinity will be
|
||||
encoded as such. This behavior is not JSON specification compliant,
|
||||
but is consistent with most JavaScript based encoders and decoders.
|
||||
Otherwise, it will be a ValueError to encode such floats.
|
||||
|
||||
If sort_keys is true, then the output of dictionaries will be
|
||||
sorted by key; this is useful for regression tests to ensure
|
||||
that JSON serializations can be compared on a day-to-day basis.
|
||||
|
||||
If indent is a string, then JSON array elements and object members
|
||||
will be pretty-printed with a newline followed by that string repeated
|
||||
for each level of nesting. ``None`` (the default) selects the most compact
|
||||
representation without any newlines. For backwards compatibility with
|
||||
versions of simplejson earlier than 2.1.0, an integer is also accepted
|
||||
and is converted to a string with that many spaces.
|
||||
|
||||
If specified, separators should be a (item_separator, key_separator)
|
||||
tuple. The default is (', ', ': '). To get the most compact JSON
|
||||
representation you should specify (',', ':') to eliminate whitespace.
|
||||
|
||||
If specified, default is a function that gets called for objects
|
||||
that can't otherwise be serialized. It should return a JSON encodable
|
||||
version of the object or raise a ``TypeError``.
|
||||
|
||||
If encoding is not None, then all input strings will be
|
||||
transformed into unicode using that encoding prior to JSON-encoding.
|
||||
The default is UTF-8.
|
||||
|
||||
If use_decimal is true (not the default), ``decimal.Decimal`` will
|
||||
be supported directly by the encoder. For the inverse, decode JSON
|
||||
with ``parse_float=decimal.Decimal``.
|
||||
|
||||
"""
|
||||
|
||||
self.skipkeys = skipkeys
|
||||
self.ensure_ascii = ensure_ascii
|
||||
self.check_circular = check_circular
|
||||
self.allow_nan = allow_nan
|
||||
self.sort_keys = sort_keys
|
||||
self.use_decimal = use_decimal
|
||||
if isinstance(indent, (int, long)):
|
||||
indent = ' ' * indent
|
||||
self.indent = indent
|
||||
if separators is not None:
|
||||
self.item_separator, self.key_separator = separators
|
||||
if default is not None:
|
||||
self.default = default
|
||||
self.encoding = encoding
|
||||
|
||||
def default(self, o):
|
||||
"""Implement this method in a subclass such that it returns
|
||||
a serializable object for ``o``, or calls the base implementation
|
||||
(to raise a ``TypeError``).
|
||||
|
||||
For example, to support arbitrary iterators, you could
|
||||
implement default like this::
|
||||
|
||||
def default(self, o):
|
||||
try:
|
||||
iterable = iter(o)
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
return list(iterable)
|
||||
return JSONEncoder.default(self, o)
|
||||
|
||||
"""
|
||||
raise TypeError(repr(o) + " is not JSON serializable")
|
||||
|
||||
def encode(self, o):
|
||||
"""Return a JSON string representation of a Python data structure.
|
||||
|
||||
>>> from simplejson import JSONEncoder
|
||||
>>> JSONEncoder().encode({"foo": ["bar", "baz"]})
|
||||
'{"foo": ["bar", "baz"]}'
|
||||
|
||||
"""
|
||||
# This is for extremely simple cases and benchmarks.
|
||||
if isinstance(o, basestring):
|
||||
if isinstance(o, str):
|
||||
_encoding = self.encoding
|
||||
if (_encoding is not None
|
||||
and not (_encoding == 'utf-8')):
|
||||
o = o.decode(_encoding)
|
||||
if self.ensure_ascii:
|
||||
return encode_basestring_ascii(o)
|
||||
else:
|
||||
return encode_basestring(o)
|
||||
# This doesn't pass the iterator directly to ''.join() because the
|
||||
# exceptions aren't as detailed. The list call should be roughly
|
||||
# equivalent to the PySequence_Fast that ''.join() would do.
|
||||
chunks = self.iterencode(o, _one_shot=True)
|
||||
if not isinstance(chunks, (list, tuple)):
|
||||
chunks = list(chunks)
|
||||
if self.ensure_ascii:
|
||||
return ''.join(chunks)
|
||||
else:
|
||||
return u''.join(chunks)
|
||||
|
||||
def iterencode(self, o, _one_shot=False):
|
||||
"""Encode the given object and yield each string
|
||||
representation as available.
|
||||
|
||||
For example::
|
||||
|
||||
for chunk in JSONEncoder().iterencode(bigobject):
|
||||
mysocket.write(chunk)
|
||||
|
||||
"""
|
||||
if self.check_circular:
|
||||
markers = {}
|
||||
else:
|
||||
markers = None
|
||||
if self.ensure_ascii:
|
||||
_encoder = encode_basestring_ascii
|
||||
else:
|
||||
_encoder = encode_basestring
|
||||
if self.encoding != 'utf-8':
|
||||
def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
|
||||
if isinstance(o, str):
|
||||
o = o.decode(_encoding)
|
||||
return _orig_encoder(o)
|
||||
|
||||
def floatstr(o, allow_nan=self.allow_nan,
|
||||
_repr=FLOAT_REPR, _inf=PosInf, _neginf=-PosInf):
|
||||
# Check for specials. Note that this type of test is processor
|
||||
# and/or platform-specific, so do tests which don't depend on
|
||||
# the internals.
|
||||
|
||||
if o != o:
|
||||
text = 'NaN'
|
||||
elif o == _inf:
|
||||
text = 'Infinity'
|
||||
elif o == _neginf:
|
||||
text = '-Infinity'
|
||||
else:
|
||||
return _repr(o)
|
||||
|
||||
if not allow_nan:
|
||||
raise ValueError(
|
||||
"Out of range float values are not JSON compliant: " +
|
||||
repr(o))
|
||||
|
||||
return text
|
||||
|
||||
|
||||
key_memo = {}
|
||||
if (_one_shot and c_make_encoder is not None
|
||||
and not self.indent and not self.sort_keys):
|
||||
_iterencode = c_make_encoder(
|
||||
markers, self.default, _encoder, self.indent,
|
||||
self.key_separator, self.item_separator, self.sort_keys,
|
||||
self.skipkeys, self.allow_nan, key_memo, self.use_decimal)
|
||||
else:
|
||||
_iterencode = _make_iterencode(
|
||||
markers, self.default, _encoder, self.indent, floatstr,
|
||||
self.key_separator, self.item_separator, self.sort_keys,
|
||||
self.skipkeys, _one_shot, self.use_decimal)
|
||||
try:
|
||||
return _iterencode(o, 0)
|
||||
finally:
|
||||
key_memo.clear()
|
||||
|
||||
|
||||
class JSONEncoderForHTML(JSONEncoder):
|
||||
"""An encoder that produces JSON safe to embed in HTML.
|
||||
|
||||
To embed JSON content in, say, a script tag on a web page, the
|
||||
characters &, < and > should be escaped. They cannot be escaped
|
||||
with the usual entities (e.g. &) because they are not expanded
|
||||
within <script> tags.
|
||||
"""
|
||||
|
||||
def encode(self, o):
|
||||
# Override JSONEncoder.encode because it has hacks for
|
||||
# performance that make things more complicated.
|
||||
chunks = self.iterencode(o, True)
|
||||
if self.ensure_ascii:
|
||||
return ''.join(chunks)
|
||||
else:
|
||||
return u''.join(chunks)
|
||||
|
||||
def iterencode(self, o, _one_shot=False):
|
||||
chunks = super(JSONEncoderForHTML, self).iterencode(o, _one_shot)
|
||||
for chunk in chunks:
|
||||
chunk = chunk.replace('&', '\\u0026')
|
||||
chunk = chunk.replace('<', '\\u003c')
|
||||
chunk = chunk.replace('>', '\\u003e')
|
||||
yield chunk
|
||||
|
||||
|
||||
def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
|
||||
_key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
|
||||
_use_decimal,
|
||||
## HACK: hand-optimized bytecode; turn globals into locals
|
||||
False=False,
|
||||
True=True,
|
||||
ValueError=ValueError,
|
||||
basestring=basestring,
|
||||
Decimal=Decimal,
|
||||
dict=dict,
|
||||
float=float,
|
||||
id=id,
|
||||
int=int,
|
||||
isinstance=isinstance,
|
||||
list=list,
|
||||
long=long,
|
||||
str=str,
|
||||
tuple=tuple,
|
||||
):
|
||||
|
||||
def _iterencode_list(lst, _current_indent_level):
|
||||
if not lst:
|
||||
yield '[]'
|
||||
return
|
||||
if markers is not None:
|
||||
markerid = id(lst)
|
||||
if markerid in markers:
|
||||
raise ValueError("Circular reference detected")
|
||||
markers[markerid] = lst
|
||||
buf = '['
|
||||
if _indent is not None:
|
||||
_current_indent_level += 1
|
||||
newline_indent = '\n' + (_indent * _current_indent_level)
|
||||
separator = _item_separator + newline_indent
|
||||
buf += newline_indent
|
||||
else:
|
||||
newline_indent = None
|
||||
separator = _item_separator
|
||||
first = True
|
||||
for value in lst:
|
||||
if first:
|
||||
first = False
|
||||
else:
|
||||
buf = separator
|
||||
if isinstance(value, basestring):
|
||||
yield buf + _encoder(value)
|
||||
elif value is None:
|
||||
yield buf + 'null'
|
||||
elif value is True:
|
||||
yield buf + 'true'
|
||||
elif value is False:
|
||||
yield buf + 'false'
|
||||
elif isinstance(value, (int, long)):
|
||||
yield buf + str(value)
|
||||
elif isinstance(value, float):
|
||||
yield buf + _floatstr(value)
|
||||
elif _use_decimal and isinstance(value, Decimal):
|
||||
yield buf + str(value)
|
||||
else:
|
||||
yield buf
|
||||
if isinstance(value, (list, tuple)):
|
||||
chunks = _iterencode_list(value, _current_indent_level)
|
||||
elif isinstance(value, dict):
|
||||
chunks = _iterencode_dict(value, _current_indent_level)
|
||||
else:
|
||||
chunks = _iterencode(value, _current_indent_level)
|
||||
for chunk in chunks:
|
||||
yield chunk
|
||||
if newline_indent is not None:
|
||||
_current_indent_level -= 1
|
||||
yield '\n' + (_indent * _current_indent_level)
|
||||
yield ']'
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
|
||||
def _iterencode_dict(dct, _current_indent_level):
|
||||
if not dct:
|
||||
yield '{}'
|
||||
return
|
||||
if markers is not None:
|
||||
markerid = id(dct)
|
||||
if markerid in markers:
|
||||
raise ValueError("Circular reference detected")
|
||||
markers[markerid] = dct
|
||||
yield '{'
|
||||
if _indent is not None:
|
||||
_current_indent_level += 1
|
||||
newline_indent = '\n' + (_indent * _current_indent_level)
|
||||
item_separator = _item_separator + newline_indent
|
||||
yield newline_indent
|
||||
else:
|
||||
newline_indent = None
|
||||
item_separator = _item_separator
|
||||
first = True
|
||||
if _sort_keys:
|
||||
items = dct.items()
|
||||
items.sort(key=lambda kv: kv[0])
|
||||
else:
|
||||
items = dct.iteritems()
|
||||
for key, value in items:
|
||||
if isinstance(key, basestring):
|
||||
pass
|
||||
# JavaScript is weakly typed for these, so it makes sense to
|
||||
# also allow them. Many encoders seem to do something like this.
|
||||
elif isinstance(key, float):
|
||||
key = _floatstr(key)
|
||||
elif key is True:
|
||||
key = 'true'
|
||||
elif key is False:
|
||||
key = 'false'
|
||||
elif key is None:
|
||||
key = 'null'
|
||||
elif isinstance(key, (int, long)):
|
||||
key = str(key)
|
||||
elif _skipkeys:
|
||||
continue
|
||||
else:
|
||||
raise TypeError("key " + repr(key) + " is not a string")
|
||||
if first:
|
||||
first = False
|
||||
else:
|
||||
yield item_separator
|
||||
yield _encoder(key)
|
||||
yield _key_separator
|
||||
if isinstance(value, basestring):
|
||||
yield _encoder(value)
|
||||
elif value is None:
|
||||
yield 'null'
|
||||
elif value is True:
|
||||
yield 'true'
|
||||
elif value is False:
|
||||
yield 'false'
|
||||
elif isinstance(value, (int, long)):
|
||||
yield str(value)
|
||||
elif isinstance(value, float):
|
||||
yield _floatstr(value)
|
||||
elif _use_decimal and isinstance(value, Decimal):
|
||||
yield str(value)
|
||||
else:
|
||||
if isinstance(value, (list, tuple)):
|
||||
chunks = _iterencode_list(value, _current_indent_level)
|
||||
elif isinstance(value, dict):
|
||||
chunks = _iterencode_dict(value, _current_indent_level)
|
||||
else:
|
||||
chunks = _iterencode(value, _current_indent_level)
|
||||
for chunk in chunks:
|
||||
yield chunk
|
||||
if newline_indent is not None:
|
||||
_current_indent_level -= 1
|
||||
yield '\n' + (_indent * _current_indent_level)
|
||||
yield '}'
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
|
||||
def _iterencode(o, _current_indent_level):
|
||||
if isinstance(o, basestring):
|
||||
yield _encoder(o)
|
||||
elif o is None:
|
||||
yield 'null'
|
||||
elif o is True:
|
||||
yield 'true'
|
||||
elif o is False:
|
||||
yield 'false'
|
||||
elif isinstance(o, (int, long)):
|
||||
yield str(o)
|
||||
elif isinstance(o, float):
|
||||
yield _floatstr(o)
|
||||
elif isinstance(o, (list, tuple)):
|
||||
for chunk in _iterencode_list(o, _current_indent_level):
|
||||
yield chunk
|
||||
elif isinstance(o, dict):
|
||||
for chunk in _iterencode_dict(o, _current_indent_level):
|
||||
yield chunk
|
||||
elif _use_decimal and isinstance(o, Decimal):
|
||||
yield str(o)
|
||||
else:
|
||||
if markers is not None:
|
||||
markerid = id(o)
|
||||
if markerid in markers:
|
||||
raise ValueError("Circular reference detected")
|
||||
markers[markerid] = o
|
||||
o = _default(o)
|
||||
for chunk in _iterencode(o, _current_indent_level):
|
||||
yield chunk
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
|
||||
return _iterencode
|
||||
@@ -1,77 +0,0 @@
|
||||
"""JSON token scanner
|
||||
"""
|
||||
import re
|
||||
def _import_c_make_scanner():
|
||||
try:
|
||||
from simplejson._speedups import make_scanner
|
||||
return make_scanner
|
||||
except ImportError:
|
||||
return None
|
||||
c_make_scanner = _import_c_make_scanner()
|
||||
|
||||
__all__ = ['make_scanner']
|
||||
|
||||
NUMBER_RE = re.compile(
|
||||
r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
|
||||
(re.VERBOSE | re.MULTILINE | re.DOTALL))
|
||||
|
||||
def py_make_scanner(context):
|
||||
parse_object = context.parse_object
|
||||
parse_array = context.parse_array
|
||||
parse_string = context.parse_string
|
||||
match_number = NUMBER_RE.match
|
||||
encoding = context.encoding
|
||||
strict = context.strict
|
||||
parse_float = context.parse_float
|
||||
parse_int = context.parse_int
|
||||
parse_constant = context.parse_constant
|
||||
object_hook = context.object_hook
|
||||
object_pairs_hook = context.object_pairs_hook
|
||||
memo = context.memo
|
||||
|
||||
def _scan_once(string, idx):
|
||||
try:
|
||||
nextchar = string[idx]
|
||||
except IndexError:
|
||||
raise StopIteration
|
||||
|
||||
if nextchar == '"':
|
||||
return parse_string(string, idx + 1, encoding, strict)
|
||||
elif nextchar == '{':
|
||||
return parse_object((string, idx + 1), encoding, strict,
|
||||
_scan_once, object_hook, object_pairs_hook, memo)
|
||||
elif nextchar == '[':
|
||||
return parse_array((string, idx + 1), _scan_once)
|
||||
elif nextchar == 'n' and string[idx:idx + 4] == 'null':
|
||||
return None, idx + 4
|
||||
elif nextchar == 't' and string[idx:idx + 4] == 'true':
|
||||
return True, idx + 4
|
||||
elif nextchar == 'f' and string[idx:idx + 5] == 'false':
|
||||
return False, idx + 5
|
||||
|
||||
m = match_number(string, idx)
|
||||
if m is not None:
|
||||
integer, frac, exp = m.groups()
|
||||
if frac or exp:
|
||||
res = parse_float(integer + (frac or '') + (exp or ''))
|
||||
else:
|
||||
res = parse_int(integer)
|
||||
return res, m.end()
|
||||
elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
|
||||
return parse_constant('NaN'), idx + 3
|
||||
elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
|
||||
return parse_constant('Infinity'), idx + 8
|
||||
elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
|
||||
return parse_constant('-Infinity'), idx + 9
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
def scan_once(string, idx):
|
||||
try:
|
||||
return _scan_once(string, idx)
|
||||
finally:
|
||||
memo.clear()
|
||||
|
||||
return scan_once
|
||||
|
||||
make_scanner = c_make_scanner or py_make_scanner
|
||||
@@ -1,63 +0,0 @@
|
||||
import unittest
|
||||
import doctest
|
||||
|
||||
|
||||
class OptionalExtensionTestSuite(unittest.TestSuite):
|
||||
def run(self, result):
|
||||
import simplejson
|
||||
run = unittest.TestSuite.run
|
||||
run(self, result)
|
||||
simplejson._toggle_speedups(False)
|
||||
run(self, result)
|
||||
simplejson._toggle_speedups(True)
|
||||
return result
|
||||
|
||||
|
||||
def additional_tests(suite=None):
|
||||
import simplejson
|
||||
import simplejson.encoder
|
||||
import simplejson.decoder
|
||||
if suite is None:
|
||||
suite = unittest.TestSuite()
|
||||
for mod in (simplejson, simplejson.encoder, simplejson.decoder):
|
||||
suite.addTest(doctest.DocTestSuite(mod))
|
||||
suite.addTest(doctest.DocFileSuite('../../index.rst'))
|
||||
return suite
|
||||
|
||||
|
||||
def all_tests_suite():
|
||||
suite = unittest.TestLoader().loadTestsFromNames([
|
||||
'simplejson.tests.test_check_circular',
|
||||
'simplejson.tests.test_decode',
|
||||
'simplejson.tests.test_default',
|
||||
'simplejson.tests.test_dump',
|
||||
'simplejson.tests.test_encode_basestring_ascii',
|
||||
'simplejson.tests.test_encode_for_html',
|
||||
'simplejson.tests.test_fail',
|
||||
'simplejson.tests.test_float',
|
||||
'simplejson.tests.test_indent',
|
||||
'simplejson.tests.test_pass1',
|
||||
'simplejson.tests.test_pass2',
|
||||
'simplejson.tests.test_pass3',
|
||||
'simplejson.tests.test_recursion',
|
||||
'simplejson.tests.test_scanstring',
|
||||
'simplejson.tests.test_separators',
|
||||
'simplejson.tests.test_speedups',
|
||||
'simplejson.tests.test_unicode',
|
||||
'simplejson.tests.test_decimal',
|
||||
])
|
||||
suite = additional_tests(suite)
|
||||
return OptionalExtensionTestSuite([suite])
|
||||
|
||||
|
||||
def main():
|
||||
runner = unittest.TextTestRunner()
|
||||
suite = all_tests_suite()
|
||||
runner.run(suite)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
main()
|
||||
@@ -1,30 +0,0 @@
|
||||
from unittest import TestCase
|
||||
import simplejson as json
|
||||
|
||||
def default_iterable(obj):
|
||||
return list(obj)
|
||||
|
||||
class TestCheckCircular(TestCase):
|
||||
def test_circular_dict(self):
|
||||
dct = {}
|
||||
dct['a'] = dct
|
||||
self.assertRaises(ValueError, json.dumps, dct)
|
||||
|
||||
def test_circular_list(self):
|
||||
lst = []
|
||||
lst.append(lst)
|
||||
self.assertRaises(ValueError, json.dumps, lst)
|
||||
|
||||
def test_circular_composite(self):
|
||||
dct2 = {}
|
||||
dct2['a'] = []
|
||||
dct2['a'].append(dct2)
|
||||
self.assertRaises(ValueError, json.dumps, dct2)
|
||||
|
||||
def test_circular_default(self):
|
||||
json.dumps([set()], default=default_iterable)
|
||||
self.assertRaises(TypeError, json.dumps, [set()])
|
||||
|
||||
def test_circular_off_default(self):
|
||||
json.dumps([set()], default=default_iterable, check_circular=False)
|
||||
self.assertRaises(TypeError, json.dumps, [set()], check_circular=False)
|
||||
@@ -1,33 +0,0 @@
|
||||
from decimal import Decimal
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
class TestDecimal(TestCase):
|
||||
NUMS = "1.0", "10.00", "1.1", "1234567890.1234567890", "500"
|
||||
def test_decimal_encode(self):
|
||||
for d in map(Decimal, self.NUMS):
|
||||
self.assertEquals(json.dumps(d, use_decimal=True), str(d))
|
||||
|
||||
def test_decimal_decode(self):
|
||||
for s in self.NUMS:
|
||||
self.assertEquals(json.loads(s, parse_float=Decimal), Decimal(s))
|
||||
|
||||
def test_decimal_roundtrip(self):
|
||||
for d in map(Decimal, self.NUMS):
|
||||
# The type might not be the same (int and Decimal) but they
|
||||
# should still compare equal.
|
||||
self.assertEquals(
|
||||
json.loads(
|
||||
json.dumps(d, use_decimal=True), parse_float=Decimal),
|
||||
d)
|
||||
self.assertEquals(
|
||||
json.loads(
|
||||
json.dumps([d], use_decimal=True), parse_float=Decimal),
|
||||
[d])
|
||||
|
||||
def test_decimal_defaults(self):
|
||||
d = Decimal(1)
|
||||
# use_decimal=False is the default
|
||||
self.assertRaises(TypeError, json.dumps, d, use_decimal=False)
|
||||
self.assertRaises(TypeError, json.dumps, d)
|
||||
@@ -1,73 +0,0 @@
|
||||
import decimal
|
||||
from unittest import TestCase
|
||||
from StringIO import StringIO
|
||||
|
||||
import simplejson as json
|
||||
from simplejson import OrderedDict
|
||||
|
||||
class TestDecode(TestCase):
|
||||
if not hasattr(TestCase, 'assertIs'):
|
||||
def assertIs(self, a, b):
|
||||
self.assertTrue(a is b, '%r is %r' % (a, b))
|
||||
|
||||
def test_decimal(self):
|
||||
rval = json.loads('1.1', parse_float=decimal.Decimal)
|
||||
self.assertTrue(isinstance(rval, decimal.Decimal))
|
||||
self.assertEquals(rval, decimal.Decimal('1.1'))
|
||||
|
||||
def test_float(self):
|
||||
rval = json.loads('1', parse_int=float)
|
||||
self.assertTrue(isinstance(rval, float))
|
||||
self.assertEquals(rval, 1.0)
|
||||
|
||||
def test_decoder_optimizations(self):
|
||||
# Several optimizations were made that skip over calls to
|
||||
# the whitespace regex, so this test is designed to try and
|
||||
# exercise the uncommon cases. The array cases are already covered.
|
||||
rval = json.loads('{ "key" : "value" , "k":"v" }')
|
||||
self.assertEquals(rval, {"key":"value", "k":"v"})
|
||||
|
||||
def test_empty_objects(self):
|
||||
s = '{}'
|
||||
self.assertEqual(json.loads(s), eval(s))
|
||||
s = '[]'
|
||||
self.assertEqual(json.loads(s), eval(s))
|
||||
s = '""'
|
||||
self.assertEqual(json.loads(s), eval(s))
|
||||
|
||||
def test_object_pairs_hook(self):
|
||||
s = '{"xkd":1, "kcw":2, "art":3, "hxm":4, "qrt":5, "pad":6, "hoy":7}'
|
||||
p = [("xkd", 1), ("kcw", 2), ("art", 3), ("hxm", 4),
|
||||
("qrt", 5), ("pad", 6), ("hoy", 7)]
|
||||
self.assertEqual(json.loads(s), eval(s))
|
||||
self.assertEqual(json.loads(s, object_pairs_hook=lambda x: x), p)
|
||||
self.assertEqual(json.load(StringIO(s),
|
||||
object_pairs_hook=lambda x: x), p)
|
||||
od = json.loads(s, object_pairs_hook=OrderedDict)
|
||||
self.assertEqual(od, OrderedDict(p))
|
||||
self.assertEqual(type(od), OrderedDict)
|
||||
# the object_pairs_hook takes priority over the object_hook
|
||||
self.assertEqual(json.loads(s,
|
||||
object_pairs_hook=OrderedDict,
|
||||
object_hook=lambda x: None),
|
||||
OrderedDict(p))
|
||||
|
||||
def check_keys_reuse(self, source, loads):
|
||||
rval = loads(source)
|
||||
(a, b), (c, d) = sorted(rval[0]), sorted(rval[1])
|
||||
self.assertIs(a, c)
|
||||
self.assertIs(b, d)
|
||||
|
||||
def test_keys_reuse_str(self):
|
||||
s = u'[{"a_key": 1, "b_\xe9": 2}, {"a_key": 3, "b_\xe9": 4}]'.encode('utf8')
|
||||
self.check_keys_reuse(s, json.loads)
|
||||
|
||||
def test_keys_reuse_unicode(self):
|
||||
s = u'[{"a_key": 1, "b_\xe9": 2}, {"a_key": 3, "b_\xe9": 4}]'
|
||||
self.check_keys_reuse(s, json.loads)
|
||||
|
||||
def test_empty_strings(self):
|
||||
self.assertEqual(json.loads('""'), "")
|
||||
self.assertEqual(json.loads(u'""'), u"")
|
||||
self.assertEqual(json.loads('[""]'), [""])
|
||||
self.assertEqual(json.loads(u'[""]'), [u""])
|
||||
@@ -1,9 +0,0 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
class TestDefault(TestCase):
|
||||
def test_default(self):
|
||||
self.assertEquals(
|
||||
json.dumps(type, default=repr),
|
||||
json.dumps(repr(type)))
|
||||
@@ -1,27 +0,0 @@
|
||||
from unittest import TestCase
|
||||
from cStringIO import StringIO
|
||||
|
||||
import simplejson as json
|
||||
|
||||
class TestDump(TestCase):
|
||||
def test_dump(self):
|
||||
sio = StringIO()
|
||||
json.dump({}, sio)
|
||||
self.assertEquals(sio.getvalue(), '{}')
|
||||
|
||||
def test_dumps(self):
|
||||
self.assertEquals(json.dumps({}), '{}')
|
||||
|
||||
def test_encode_truefalse(self):
|
||||
self.assertEquals(json.dumps(
|
||||
{True: False, False: True}, sort_keys=True),
|
||||
'{"false": true, "true": false}')
|
||||
self.assertEquals(json.dumps(
|
||||
{2: 3.0, 4.0: 5L, False: 1, 6L: True, "7": 0}, sort_keys=True),
|
||||
'{"false": 1, "2": 3.0, "4.0": 5, "6": true, "7": 0}')
|
||||
|
||||
def test_ordered_dict(self):
|
||||
# http://bugs.python.org/issue6105
|
||||
items = [('one', 1), ('two', 2), ('three', 3), ('four', 4), ('five', 5)]
|
||||
s = json.dumps(json.OrderedDict(items))
|
||||
self.assertEqual(s, '{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5}')
|
||||
@@ -1,41 +0,0 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson.encoder
|
||||
|
||||
CASES = [
|
||||
(u'/\\"\ucafe\ubabe\uab98\ufcde\ubcda\uef4a\x08\x0c\n\r\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?', '"/\\\\\\"\\ucafe\\ubabe\\uab98\\ufcde\\ubcda\\uef4a\\b\\f\\n\\r\\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?"'),
|
||||
(u'\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
|
||||
(u'controls', '"controls"'),
|
||||
(u'\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'),
|
||||
(u'{"object with 1 member":["array with 1 element"]}', '"{\\"object with 1 member\\":[\\"array with 1 element\\"]}"'),
|
||||
(u' s p a c e d ', '" s p a c e d "'),
|
||||
(u'\U0001d120', '"\\ud834\\udd20"'),
|
||||
(u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
|
||||
('\xce\xb1\xce\xa9', '"\\u03b1\\u03a9"'),
|
||||
(u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
|
||||
('\xce\xb1\xce\xa9', '"\\u03b1\\u03a9"'),
|
||||
(u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
|
||||
(u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
|
||||
(u"`1~!@#$%^&*()_+-={':[,]}|;.</>?", '"`1~!@#$%^&*()_+-={\':[,]}|;.</>?"'),
|
||||
(u'\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'),
|
||||
(u'\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
|
||||
]
|
||||
|
||||
class TestEncodeBaseStringAscii(TestCase):
|
||||
def test_py_encode_basestring_ascii(self):
|
||||
self._test_encode_basestring_ascii(simplejson.encoder.py_encode_basestring_ascii)
|
||||
|
||||
def test_c_encode_basestring_ascii(self):
|
||||
if not simplejson.encoder.c_encode_basestring_ascii:
|
||||
return
|
||||
self._test_encode_basestring_ascii(simplejson.encoder.c_encode_basestring_ascii)
|
||||
|
||||
def _test_encode_basestring_ascii(self, encode_basestring_ascii):
|
||||
fname = encode_basestring_ascii.__name__
|
||||
for input_string, expect in CASES:
|
||||
result = encode_basestring_ascii(input_string)
|
||||
#self.assertEquals(result, expect,
|
||||
# '{0!r} != {1!r} for {2}({3!r})'.format(
|
||||
# result, expect, fname, input_string))
|
||||
self.assertEquals(result, expect,
|
||||
'%r != %r for %s(%r)' % (result, expect, fname, input_string))
|
||||
@@ -1,32 +0,0 @@
|
||||
import unittest
|
||||
|
||||
import simplejson.decoder
|
||||
import simplejson.encoder
|
||||
|
||||
|
||||
class TestEncodeForHTML(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.decoder = simplejson.decoder.JSONDecoder()
|
||||
self.encoder = simplejson.encoder.JSONEncoderForHTML()
|
||||
|
||||
def test_basic_encode(self):
|
||||
self.assertEqual(r'"\u0026"', self.encoder.encode('&'))
|
||||
self.assertEqual(r'"\u003c"', self.encoder.encode('<'))
|
||||
self.assertEqual(r'"\u003e"', self.encoder.encode('>'))
|
||||
|
||||
def test_basic_roundtrip(self):
|
||||
for char in '&<>':
|
||||
self.assertEqual(
|
||||
char, self.decoder.decode(
|
||||
self.encoder.encode(char)))
|
||||
|
||||
def test_prevent_script_breakout(self):
|
||||
bad_string = '</script><script>alert("gotcha")</script>'
|
||||
self.assertEqual(
|
||||
r'"\u003c/script\u003e\u003cscript\u003e'
|
||||
r'alert(\"gotcha\")\u003c/script\u003e"',
|
||||
self.encoder.encode(bad_string))
|
||||
self.assertEqual(
|
||||
bad_string, self.decoder.decode(
|
||||
self.encoder.encode(bad_string)))
|
||||
@@ -1,91 +0,0 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
# Fri Dec 30 18:57:26 2005
|
||||
JSONDOCS = [
|
||||
# http://json.org/JSON_checker/test/fail1.json
|
||||
'"A JSON payload should be an object or array, not a string."',
|
||||
# http://json.org/JSON_checker/test/fail2.json
|
||||
'["Unclosed array"',
|
||||
# http://json.org/JSON_checker/test/fail3.json
|
||||
'{unquoted_key: "keys must be quoted}',
|
||||
# http://json.org/JSON_checker/test/fail4.json
|
||||
'["extra comma",]',
|
||||
# http://json.org/JSON_checker/test/fail5.json
|
||||
'["double extra comma",,]',
|
||||
# http://json.org/JSON_checker/test/fail6.json
|
||||
'[ , "<-- missing value"]',
|
||||
# http://json.org/JSON_checker/test/fail7.json
|
||||
'["Comma after the close"],',
|
||||
# http://json.org/JSON_checker/test/fail8.json
|
||||
'["Extra close"]]',
|
||||
# http://json.org/JSON_checker/test/fail9.json
|
||||
'{"Extra comma": true,}',
|
||||
# http://json.org/JSON_checker/test/fail10.json
|
||||
'{"Extra value after close": true} "misplaced quoted value"',
|
||||
# http://json.org/JSON_checker/test/fail11.json
|
||||
'{"Illegal expression": 1 + 2}',
|
||||
# http://json.org/JSON_checker/test/fail12.json
|
||||
'{"Illegal invocation": alert()}',
|
||||
# http://json.org/JSON_checker/test/fail13.json
|
||||
'{"Numbers cannot have leading zeroes": 013}',
|
||||
# http://json.org/JSON_checker/test/fail14.json
|
||||
'{"Numbers cannot be hex": 0x14}',
|
||||
# http://json.org/JSON_checker/test/fail15.json
|
||||
'["Illegal backslash escape: \\x15"]',
|
||||
# http://json.org/JSON_checker/test/fail16.json
|
||||
'["Illegal backslash escape: \\\'"]',
|
||||
# http://json.org/JSON_checker/test/fail17.json
|
||||
'["Illegal backslash escape: \\017"]',
|
||||
# http://json.org/JSON_checker/test/fail18.json
|
||||
'[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]',
|
||||
# http://json.org/JSON_checker/test/fail19.json
|
||||
'{"Missing colon" null}',
|
||||
# http://json.org/JSON_checker/test/fail20.json
|
||||
'{"Double colon":: null}',
|
||||
# http://json.org/JSON_checker/test/fail21.json
|
||||
'{"Comma instead of colon", null}',
|
||||
# http://json.org/JSON_checker/test/fail22.json
|
||||
'["Colon instead of comma": false]',
|
||||
# http://json.org/JSON_checker/test/fail23.json
|
||||
'["Bad value", truth]',
|
||||
# http://json.org/JSON_checker/test/fail24.json
|
||||
"['single quote']",
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=3
|
||||
u'["A\u001FZ control characters in string"]',
|
||||
]
|
||||
|
||||
SKIPS = {
|
||||
1: "why not have a string payload?",
|
||||
18: "spec doesn't specify any nesting limitations",
|
||||
}
|
||||
|
||||
class TestFail(TestCase):
|
||||
def test_failures(self):
|
||||
for idx, doc in enumerate(JSONDOCS):
|
||||
idx = idx + 1
|
||||
if idx in SKIPS:
|
||||
json.loads(doc)
|
||||
continue
|
||||
try:
|
||||
json.loads(doc)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
else:
|
||||
#self.fail("Expected failure for fail{0}.json: {1!r}".format(idx, doc))
|
||||
self.fail("Expected failure for fail%d.json: %r" % (idx, doc))
|
||||
|
||||
def test_array_decoder_issue46(self):
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=46
|
||||
for doc in [u'[,]', '[,]']:
|
||||
try:
|
||||
json.loads(doc)
|
||||
except json.JSONDecodeError, e:
|
||||
self.assertEquals(e.pos, 1)
|
||||
self.assertEquals(e.lineno, 1)
|
||||
self.assertEquals(e.colno, 1)
|
||||
except Exception, e:
|
||||
self.fail("Unexpected exception raised %r %s" % (e, e))
|
||||
else:
|
||||
self.fail("Unexpected success parsing '[,]'")
|
||||
@@ -1,19 +0,0 @@
|
||||
import math
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
class TestFloat(TestCase):
|
||||
def test_floats(self):
|
||||
for num in [1617161771.7650001, math.pi, math.pi**100,
|
||||
math.pi**-100, 3.1]:
|
||||
self.assertEquals(float(json.dumps(num)), num)
|
||||
self.assertEquals(json.loads(json.dumps(num)), num)
|
||||
self.assertEquals(json.loads(unicode(json.dumps(num))), num)
|
||||
|
||||
def test_ints(self):
|
||||
for num in [1, 1L, 1<<32, 1<<64]:
|
||||
self.assertEquals(json.dumps(num), str(num))
|
||||
self.assertEquals(int(json.dumps(num)), num)
|
||||
self.assertEquals(json.loads(json.dumps(num)), num)
|
||||
self.assertEquals(json.loads(unicode(json.dumps(num))), num)
|
||||
@@ -1,53 +0,0 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
import textwrap
|
||||
|
||||
class TestIndent(TestCase):
|
||||
def test_indent(self):
|
||||
h = [['blorpie'], ['whoops'], [], 'd-shtaeou', 'd-nthiouh',
|
||||
'i-vhbjkhnth',
|
||||
{'nifty': 87}, {'field': 'yes', 'morefield': False} ]
|
||||
|
||||
expect = textwrap.dedent("""\
|
||||
[
|
||||
\t[
|
||||
\t\t"blorpie"
|
||||
\t],
|
||||
\t[
|
||||
\t\t"whoops"
|
||||
\t],
|
||||
\t[],
|
||||
\t"d-shtaeou",
|
||||
\t"d-nthiouh",
|
||||
\t"i-vhbjkhnth",
|
||||
\t{
|
||||
\t\t"nifty": 87
|
||||
\t},
|
||||
\t{
|
||||
\t\t"field": "yes",
|
||||
\t\t"morefield": false
|
||||
\t}
|
||||
]""")
|
||||
|
||||
|
||||
d1 = json.dumps(h)
|
||||
d2 = json.dumps(h, indent='\t', sort_keys=True, separators=(',', ': '))
|
||||
d3 = json.dumps(h, indent=' ', sort_keys=True, separators=(',', ': '))
|
||||
d4 = json.dumps(h, indent=2, sort_keys=True, separators=(',', ': '))
|
||||
|
||||
h1 = json.loads(d1)
|
||||
h2 = json.loads(d2)
|
||||
h3 = json.loads(d3)
|
||||
h4 = json.loads(d4)
|
||||
|
||||
self.assertEquals(h1, h)
|
||||
self.assertEquals(h2, h)
|
||||
self.assertEquals(h3, h)
|
||||
self.assertEquals(h4, h)
|
||||
self.assertEquals(d3, expect.replace('\t', ' '))
|
||||
self.assertEquals(d4, expect.replace('\t', ' '))
|
||||
# NOTE: Python 2.4 textwrap.dedent converts tabs to spaces,
|
||||
# so the following is expected to fail. Python 2.4 is not a
|
||||
# supported platform in simplejson 2.1.0+.
|
||||
self.assertEquals(d2, expect)
|
||||
@@ -1,76 +0,0 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
# from http://json.org/JSON_checker/test/pass1.json
|
||||
JSON = r'''
|
||||
[
|
||||
"JSON Test Pattern pass1",
|
||||
{"object with 1 member":["array with 1 element"]},
|
||||
{},
|
||||
[],
|
||||
-42,
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
{
|
||||
"integer": 1234567890,
|
||||
"real": -9876.543210,
|
||||
"e": 0.123456789e-12,
|
||||
"E": 1.234567890E+34,
|
||||
"": 23456789012E666,
|
||||
"zero": 0,
|
||||
"one": 1,
|
||||
"space": " ",
|
||||
"quote": "\"",
|
||||
"backslash": "\\",
|
||||
"controls": "\b\f\n\r\t",
|
||||
"slash": "/ & \/",
|
||||
"alpha": "abcdefghijklmnopqrstuvwyz",
|
||||
"ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
|
||||
"digit": "0123456789",
|
||||
"special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
|
||||
"hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
|
||||
"true": true,
|
||||
"false": false,
|
||||
"null": null,
|
||||
"array":[ ],
|
||||
"object":{ },
|
||||
"address": "50 St. James Street",
|
||||
"url": "http://www.JSON.org/",
|
||||
"comment": "// /* <!-- --",
|
||||
"# -- --> */": " ",
|
||||
" s p a c e d " :[1,2 , 3
|
||||
|
||||
,
|
||||
|
||||
4 , 5 , 6 ,7 ],
|
||||
"compact": [1,2,3,4,5,6,7],
|
||||
"jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
|
||||
"quotes": "" \u0022 %22 0x22 034 "",
|
||||
"\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
|
||||
: "A key can be any string"
|
||||
},
|
||||
0.5 ,98.6
|
||||
,
|
||||
99.44
|
||||
,
|
||||
|
||||
1066
|
||||
|
||||
|
||||
,"rosebud"]
|
||||
'''
|
||||
|
||||
class TestPass1(TestCase):
|
||||
def test_parse(self):
|
||||
# test in/out equivalence and parsing
|
||||
res = json.loads(JSON)
|
||||
out = json.dumps(res)
|
||||
self.assertEquals(res, json.loads(out))
|
||||
try:
|
||||
json.dumps(res, allow_nan=False)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.fail("23456789012E666 should be out of range")
|
||||
@@ -1,14 +0,0 @@
|
||||
from unittest import TestCase
|
||||
import simplejson as json
|
||||
|
||||
# from http://json.org/JSON_checker/test/pass2.json
|
||||
JSON = r'''
|
||||
[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]
|
||||
'''
|
||||
|
||||
class TestPass2(TestCase):
|
||||
def test_parse(self):
|
||||
# test in/out equivalence and parsing
|
||||
res = json.loads(JSON)
|
||||
out = json.dumps(res)
|
||||
self.assertEquals(res, json.loads(out))
|
||||
@@ -1,20 +0,0 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
# from http://json.org/JSON_checker/test/pass3.json
|
||||
JSON = r'''
|
||||
{
|
||||
"JSON Test Pattern pass3": {
|
||||
"The outermost value": "must be an object or array.",
|
||||
"In this test": "It is an object."
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
class TestPass3(TestCase):
|
||||
def test_parse(self):
|
||||
# test in/out equivalence and parsing
|
||||
res = json.loads(JSON)
|
||||
out = json.dumps(res)
|
||||
self.assertEquals(res, json.loads(out))
|
||||
@@ -1,67 +0,0 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
class JSONTestObject:
|
||||
pass
|
||||
|
||||
|
||||
class RecursiveJSONEncoder(json.JSONEncoder):
|
||||
recurse = False
|
||||
def default(self, o):
|
||||
if o is JSONTestObject:
|
||||
if self.recurse:
|
||||
return [JSONTestObject]
|
||||
else:
|
||||
return 'JSONTestObject'
|
||||
return json.JSONEncoder.default(o)
|
||||
|
||||
|
||||
class TestRecursion(TestCase):
|
||||
def test_listrecursion(self):
|
||||
x = []
|
||||
x.append(x)
|
||||
try:
|
||||
json.dumps(x)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.fail("didn't raise ValueError on list recursion")
|
||||
x = []
|
||||
y = [x]
|
||||
x.append(y)
|
||||
try:
|
||||
json.dumps(x)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.fail("didn't raise ValueError on alternating list recursion")
|
||||
y = []
|
||||
x = [y, y]
|
||||
# ensure that the marker is cleared
|
||||
json.dumps(x)
|
||||
|
||||
def test_dictrecursion(self):
|
||||
x = {}
|
||||
x["test"] = x
|
||||
try:
|
||||
json.dumps(x)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.fail("didn't raise ValueError on dict recursion")
|
||||
x = {}
|
||||
y = {"a": x, "b": x}
|
||||
# ensure that the marker is cleared
|
||||
json.dumps(x)
|
||||
|
||||
def test_defaultrecursion(self):
|
||||
enc = RecursiveJSONEncoder()
|
||||
self.assertEquals(enc.encode(JSONTestObject), '"JSONTestObject"')
|
||||
enc.recurse = True
|
||||
try:
|
||||
enc.encode(JSONTestObject)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.fail("didn't raise ValueError on default recursion")
|
||||
@@ -1,117 +0,0 @@
|
||||
import sys
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
import simplejson.decoder
|
||||
|
||||
class TestScanString(TestCase):
|
||||
def test_py_scanstring(self):
|
||||
self._test_scanstring(simplejson.decoder.py_scanstring)
|
||||
|
||||
def test_c_scanstring(self):
|
||||
if not simplejson.decoder.c_scanstring:
|
||||
return
|
||||
self._test_scanstring(simplejson.decoder.c_scanstring)
|
||||
|
||||
def _test_scanstring(self, scanstring):
|
||||
self.assertEquals(
|
||||
scanstring('"z\\ud834\\udd20x"', 1, None, True),
|
||||
(u'z\U0001d120x', 16))
|
||||
|
||||
if sys.maxunicode == 65535:
|
||||
self.assertEquals(
|
||||
scanstring(u'"z\U0001d120x"', 1, None, True),
|
||||
(u'z\U0001d120x', 6))
|
||||
else:
|
||||
self.assertEquals(
|
||||
scanstring(u'"z\U0001d120x"', 1, None, True),
|
||||
(u'z\U0001d120x', 5))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('"\\u007b"', 1, None, True),
|
||||
(u'{', 8))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('"A JSON payload should be an object or array, not a string."', 1, None, True),
|
||||
(u'A JSON payload should be an object or array, not a string.', 60))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('["Unclosed array"', 2, None, True),
|
||||
(u'Unclosed array', 17))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('["extra comma",]', 2, None, True),
|
||||
(u'extra comma', 14))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('["double extra comma",,]', 2, None, True),
|
||||
(u'double extra comma', 21))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('["Comma after the close"],', 2, None, True),
|
||||
(u'Comma after the close', 24))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('["Extra close"]]', 2, None, True),
|
||||
(u'Extra close', 14))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Extra comma": true,}', 2, None, True),
|
||||
(u'Extra comma', 14))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Extra value after close": true} "misplaced quoted value"', 2, None, True),
|
||||
(u'Extra value after close', 26))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Illegal expression": 1 + 2}', 2, None, True),
|
||||
(u'Illegal expression', 21))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Illegal invocation": alert()}', 2, None, True),
|
||||
(u'Illegal invocation', 21))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Numbers cannot have leading zeroes": 013}', 2, None, True),
|
||||
(u'Numbers cannot have leading zeroes', 37))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Numbers cannot be hex": 0x14}', 2, None, True),
|
||||
(u'Numbers cannot be hex', 24))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]', 21, None, True),
|
||||
(u'Too deep', 30))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Missing colon" null}', 2, None, True),
|
||||
(u'Missing colon', 16))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Double colon":: null}', 2, None, True),
|
||||
(u'Double colon', 15))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Comma instead of colon", null}', 2, None, True),
|
||||
(u'Comma instead of colon', 25))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('["Colon instead of comma": false]', 2, None, True),
|
||||
(u'Colon instead of comma', 25))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('["Bad value", truth]', 2, None, True),
|
||||
(u'Bad value', 12))
|
||||
|
||||
def test_issue3623(self):
|
||||
self.assertRaises(ValueError, json.decoder.scanstring, "xxx", 1,
|
||||
"xxx")
|
||||
self.assertRaises(UnicodeDecodeError,
|
||||
json.encoder.encode_basestring_ascii, "xx\xff")
|
||||
|
||||
def test_overflow(self):
|
||||
# Python 2.5 does not have maxsize
|
||||
maxsize = getattr(sys, 'maxsize', sys.maxint)
|
||||
self.assertRaises(OverflowError, json.decoder.scanstring, "xxx",
|
||||
maxsize + 1)
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import textwrap
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
|
||||
class TestSeparators(TestCase):
|
||||
def test_separators(self):
|
||||
h = [['blorpie'], ['whoops'], [], 'd-shtaeou', 'd-nthiouh', 'i-vhbjkhnth',
|
||||
{'nifty': 87}, {'field': 'yes', 'morefield': False} ]
|
||||
|
||||
expect = textwrap.dedent("""\
|
||||
[
|
||||
[
|
||||
"blorpie"
|
||||
] ,
|
||||
[
|
||||
"whoops"
|
||||
] ,
|
||||
[] ,
|
||||
"d-shtaeou" ,
|
||||
"d-nthiouh" ,
|
||||
"i-vhbjkhnth" ,
|
||||
{
|
||||
"nifty" : 87
|
||||
} ,
|
||||
{
|
||||
"field" : "yes" ,
|
||||
"morefield" : false
|
||||
}
|
||||
]""")
|
||||
|
||||
|
||||
d1 = json.dumps(h)
|
||||
d2 = json.dumps(h, indent=' ', sort_keys=True, separators=(' ,', ' : '))
|
||||
|
||||
h1 = json.loads(d1)
|
||||
h2 = json.loads(d2)
|
||||
|
||||
self.assertEquals(h1, h)
|
||||
self.assertEquals(h2, h)
|
||||
self.assertEquals(d2, expect)
|
||||
@@ -1,21 +0,0 @@
|
||||
import decimal
|
||||
from unittest import TestCase
|
||||
|
||||
from simplejson import decoder, encoder, scanner
|
||||
|
||||
def has_speedups():
|
||||
return encoder.c_make_encoder is not None
|
||||
|
||||
class TestDecode(TestCase):
|
||||
def test_make_scanner(self):
|
||||
if not has_speedups():
|
||||
return
|
||||
self.assertRaises(AttributeError, scanner.c_make_scanner, 1)
|
||||
|
||||
def test_make_encoder(self):
|
||||
if not has_speedups():
|
||||
return
|
||||
self.assertRaises(TypeError, encoder.c_make_encoder,
|
||||
None,
|
||||
"\xCD\x7D\x3D\x4E\x12\x4C\xF9\x79\xD7\x52\xBA\x82\xF2\x27\x4A\x7D\xA0\xCA\x75",
|
||||
None)
|
||||
@@ -1,99 +0,0 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
class TestUnicode(TestCase):
|
||||
def test_encoding1(self):
|
||||
encoder = json.JSONEncoder(encoding='utf-8')
|
||||
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
|
||||
s = u.encode('utf-8')
|
||||
ju = encoder.encode(u)
|
||||
js = encoder.encode(s)
|
||||
self.assertEquals(ju, js)
|
||||
|
||||
def test_encoding2(self):
|
||||
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
|
||||
s = u.encode('utf-8')
|
||||
ju = json.dumps(u, encoding='utf-8')
|
||||
js = json.dumps(s, encoding='utf-8')
|
||||
self.assertEquals(ju, js)
|
||||
|
||||
def test_encoding3(self):
|
||||
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
|
||||
j = json.dumps(u)
|
||||
self.assertEquals(j, '"\\u03b1\\u03a9"')
|
||||
|
||||
def test_encoding4(self):
|
||||
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
|
||||
j = json.dumps([u])
|
||||
self.assertEquals(j, '["\\u03b1\\u03a9"]')
|
||||
|
||||
def test_encoding5(self):
|
||||
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
|
||||
j = json.dumps(u, ensure_ascii=False)
|
||||
self.assertEquals(j, u'"' + u + u'"')
|
||||
|
||||
def test_encoding6(self):
|
||||
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
|
||||
j = json.dumps([u], ensure_ascii=False)
|
||||
self.assertEquals(j, u'["' + u + u'"]')
|
||||
|
||||
def test_big_unicode_encode(self):
|
||||
u = u'\U0001d120'
|
||||
self.assertEquals(json.dumps(u), '"\\ud834\\udd20"')
|
||||
self.assertEquals(json.dumps(u, ensure_ascii=False), u'"\U0001d120"')
|
||||
|
||||
def test_big_unicode_decode(self):
|
||||
u = u'z\U0001d120x'
|
||||
self.assertEquals(json.loads('"' + u + '"'), u)
|
||||
self.assertEquals(json.loads('"z\\ud834\\udd20x"'), u)
|
||||
|
||||
def test_unicode_decode(self):
|
||||
for i in range(0, 0xd7ff):
|
||||
u = unichr(i)
|
||||
#s = '"\\u{0:04x}"'.format(i)
|
||||
s = '"\\u%04x"' % (i,)
|
||||
self.assertEquals(json.loads(s), u)
|
||||
|
||||
def test_object_pairs_hook_with_unicode(self):
|
||||
s = u'{"xkd":1, "kcw":2, "art":3, "hxm":4, "qrt":5, "pad":6, "hoy":7}'
|
||||
p = [(u"xkd", 1), (u"kcw", 2), (u"art", 3), (u"hxm", 4),
|
||||
(u"qrt", 5), (u"pad", 6), (u"hoy", 7)]
|
||||
self.assertEqual(json.loads(s), eval(s))
|
||||
self.assertEqual(json.loads(s, object_pairs_hook=lambda x: x), p)
|
||||
od = json.loads(s, object_pairs_hook=json.OrderedDict)
|
||||
self.assertEqual(od, json.OrderedDict(p))
|
||||
self.assertEqual(type(od), json.OrderedDict)
|
||||
# the object_pairs_hook takes priority over the object_hook
|
||||
self.assertEqual(json.loads(s,
|
||||
object_pairs_hook=json.OrderedDict,
|
||||
object_hook=lambda x: None),
|
||||
json.OrderedDict(p))
|
||||
|
||||
|
||||
def test_default_encoding(self):
|
||||
self.assertEquals(json.loads(u'{"a": "\xe9"}'.encode('utf-8')),
|
||||
{'a': u'\xe9'})
|
||||
|
||||
def test_unicode_preservation(self):
|
||||
self.assertEquals(type(json.loads(u'""')), unicode)
|
||||
self.assertEquals(type(json.loads(u'"a"')), unicode)
|
||||
self.assertEquals(type(json.loads(u'["a"]')[0]), unicode)
|
||||
|
||||
def test_ensure_ascii_false_returns_unicode(self):
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=48
|
||||
self.assertEquals(type(json.dumps([], ensure_ascii=False)), unicode)
|
||||
self.assertEquals(type(json.dumps(0, ensure_ascii=False)), unicode)
|
||||
self.assertEquals(type(json.dumps({}, ensure_ascii=False)), unicode)
|
||||
self.assertEquals(type(json.dumps("", ensure_ascii=False)), unicode)
|
||||
|
||||
def test_ensure_ascii_false_bytestring_encoding(self):
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=48
|
||||
doc1 = {u'quux': 'Arr\xc3\xaat sur images'}
|
||||
doc2 = {u'quux': u'Arr\xeat sur images'}
|
||||
doc_ascii = '{"quux": "Arr\\u00eat sur images"}'
|
||||
doc_unicode = u'{"quux": "Arr\xeat sur images"}'
|
||||
self.assertEquals(json.dumps(doc1), doc_ascii)
|
||||
self.assertEquals(json.dumps(doc2), doc_ascii)
|
||||
self.assertEquals(json.dumps(doc1, ensure_ascii=False), doc_unicode)
|
||||
self.assertEquals(json.dumps(doc2, ensure_ascii=False), doc_unicode)
|
||||
@@ -1,39 +0,0 @@
|
||||
r"""Command-line tool to validate and pretty-print JSON
|
||||
|
||||
Usage::
|
||||
|
||||
$ echo '{"json":"obj"}' | python -m simplejson.tool
|
||||
{
|
||||
"json": "obj"
|
||||
}
|
||||
$ echo '{ 1.2:3.4}' | python -m simplejson.tool
|
||||
Expecting property name: line 1 column 2 (char 2)
|
||||
|
||||
"""
|
||||
import sys
|
||||
import simplejson as json
|
||||
|
||||
def main():
|
||||
if len(sys.argv) == 1:
|
||||
infile = sys.stdin
|
||||
outfile = sys.stdout
|
||||
elif len(sys.argv) == 2:
|
||||
infile = open(sys.argv[1], 'rb')
|
||||
outfile = sys.stdout
|
||||
elif len(sys.argv) == 3:
|
||||
infile = open(sys.argv[1], 'rb')
|
||||
outfile = open(sys.argv[2], 'wb')
|
||||
else:
|
||||
raise SystemExit(sys.argv[0] + " [infile [outfile]]")
|
||||
try:
|
||||
obj = json.load(infile,
|
||||
object_pairs_hook=json.OrderedDict,
|
||||
use_decimal=True)
|
||||
except ValueError, e:
|
||||
raise SystemExit(e)
|
||||
json.dump(obj, outfile, sort_keys=True, indent=' ', use_decimal=True)
|
||||
outfile.write('\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
+2393
-2393
File diff suppressed because it is too large
Load Diff
+262
-262
@@ -1,262 +1,262 @@
|
||||
# -*- coding: windows-1251 -*-
|
||||
|
||||
# Portions are Copyright (C) 2005 Roman V. Kiseliov
|
||||
# Portions are Copyright (c) 2004 Evgeny Filatov <fufff@users.sourceforge.net>
|
||||
# Portions are Copyright (c) 2002-2004 John McNamara (Perl Spreadsheet::WriteExcel)
|
||||
|
||||
from BIFFRecords import BiffRecord
|
||||
from struct import *
|
||||
|
||||
|
||||
def _size_col(sheet, col):
|
||||
return sheet.col_width(col)
|
||||
|
||||
|
||||
def _size_row(sheet, row):
|
||||
return sheet.row_height(row)
|
||||
|
||||
|
||||
def _position_image(sheet, row_start, col_start, x1, y1, width, height):
|
||||
"""Calculate the vertices that define the position of the image as required by
|
||||
the OBJ record.
|
||||
|
||||
+------------+------------+
|
||||
| A | B |
|
||||
+-----+------------+------------+
|
||||
| |(x1,y1) | |
|
||||
| 1 |(A1)._______|______ |
|
||||
| | | | |
|
||||
| | | | |
|
||||
+-----+----| BITMAP |-----+
|
||||
| | | | |
|
||||
| 2 | |______________. |
|
||||
| | | (B2)|
|
||||
| | | (x2,y2)|
|
||||
+---- +------------+------------+
|
||||
|
||||
Example of a bitmap that covers some of the area from cell A1 to cell B2.
|
||||
|
||||
Based on the width and height of the bitmap we need to calculate 8 vars:
|
||||
col_start, row_start, col_end, row_end, x1, y1, x2, y2.
|
||||
The width and height of the cells are also variable and have to be taken into
|
||||
account.
|
||||
The values of col_start and row_start are passed in from the calling
|
||||
function. The values of col_end and row_end are calculated by subtracting
|
||||
the width and height of the bitmap from the width and height of the
|
||||
underlying cells.
|
||||
The vertices are expressed as a percentage of the underlying cell width as
|
||||
follows (rhs values are in pixels):
|
||||
|
||||
x1 = X / W *1024
|
||||
y1 = Y / H *256
|
||||
x2 = (X-1) / W *1024
|
||||
y2 = (Y-1) / H *256
|
||||
|
||||
Where: X is distance from the left side of the underlying cell
|
||||
Y is distance from the top of the underlying cell
|
||||
W is the width of the cell
|
||||
H is the height of the cell
|
||||
|
||||
Note: the SDK incorrectly states that the height should be expressed as a
|
||||
percentage of 1024.
|
||||
|
||||
col_start - Col containing upper left corner of object
|
||||
row_start - Row containing top left corner of object
|
||||
x1 - Distance to left side of object
|
||||
y1 - Distance to top of object
|
||||
width - Width of image frame
|
||||
height - Height of image frame
|
||||
|
||||
"""
|
||||
# Adjust start column for offsets that are greater than the col width
|
||||
while x1 >= _size_col(sheet, col_start):
|
||||
x1 -= _size_col(sheet, col_start)
|
||||
col_start += 1
|
||||
# Adjust start row for offsets that are greater than the row height
|
||||
while y1 >= _size_row(sheet, row_start):
|
||||
y1 -= _size_row(sheet, row_start)
|
||||
row_start += 1
|
||||
# Initialise end cell to the same as the start cell
|
||||
row_end = row_start # Row containing bottom right corner of object
|
||||
col_end = col_start # Col containing lower right corner of object
|
||||
width = width + x1 - 1
|
||||
height = height + y1 - 1
|
||||
# Subtract the underlying cell widths to find the end cell of the image
|
||||
while (width >= _size_col(sheet, col_end)):
|
||||
width -= _size_col(sheet, col_end)
|
||||
col_end += 1
|
||||
# Subtract the underlying cell heights to find the end cell of the image
|
||||
while (height >= _size_row(sheet, row_end)):
|
||||
height -= _size_row(sheet, row_end)
|
||||
row_end += 1
|
||||
# Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
|
||||
# with zero height or width.
|
||||
if ((_size_col(sheet, col_start) == 0) or (_size_col(sheet, col_end) == 0)
|
||||
or (_size_row(sheet, row_start) == 0) or (_size_row(sheet, row_end) == 0)):
|
||||
return
|
||||
# Convert the pixel values to the percentage value expected by Excel
|
||||
x1 = int(float(x1) / _size_col(sheet, col_start) * 1024)
|
||||
y1 = int(float(y1) / _size_row(sheet, row_start) * 256)
|
||||
# Distance to right side of object
|
||||
x2 = int(float(width) / _size_col(sheet, col_end) * 1024)
|
||||
# Distance to bottom of object
|
||||
y2 = int(float(height) / _size_row(sheet, row_end) * 256)
|
||||
return (col_start, x1, row_start, y1, col_end, x2, row_end, y2)
|
||||
|
||||
|
||||
class ObjBmpRecord(BiffRecord):
|
||||
_REC_ID = 0x005D # Record identifier
|
||||
|
||||
def __init__(self, row, col, sheet, im_data_bmp, x, y, scale_x, scale_y):
|
||||
# Scale the frame of the image.
|
||||
width = im_data_bmp.width * scale_x
|
||||
height = im_data_bmp.height * scale_y
|
||||
|
||||
# Calculate the vertices of the image and write the OBJ record
|
||||
coordinates = _position_image(sheet, row, col, x, y, width, height)
|
||||
# print coordinates
|
||||
col_start, x1, row_start, y1, col_end, x2, row_end, y2 = coordinates
|
||||
|
||||
"""Store the OBJ record that precedes an IMDATA record. This could be generalise
|
||||
to support other Excel objects.
|
||||
|
||||
"""
|
||||
cObj = 0x0001 # Count of objects in file (set to 1)
|
||||
OT = 0x0008 # Object type. 8 = Picture
|
||||
id = 0x0001 # Object ID
|
||||
grbit = 0x0614 # Option flags
|
||||
colL = col_start # Col containing upper left corner of object
|
||||
dxL = x1 # Distance from left side of cell
|
||||
rwT = row_start # Row containing top left corner of object
|
||||
dyT = y1 # Distance from top of cell
|
||||
colR = col_end # Col containing lower right corner of object
|
||||
dxR = x2 # Distance from right of cell
|
||||
rwB = row_end # Row containing bottom right corner of object
|
||||
dyB = y2 # Distance from bottom of cell
|
||||
cbMacro = 0x0000 # Length of FMLA structure
|
||||
Reserved1 = 0x0000 # Reserved
|
||||
Reserved2 = 0x0000 # Reserved
|
||||
icvBack = 0x09 # Background colour
|
||||
icvFore = 0x09 # Foreground colour
|
||||
fls = 0x00 # Fill pattern
|
||||
fAuto = 0x00 # Automatic fill
|
||||
icv = 0x08 # Line colour
|
||||
lns = 0xff # Line style
|
||||
lnw = 0x01 # Line weight
|
||||
fAutoB = 0x00 # Automatic border
|
||||
frs = 0x0000 # Frame style
|
||||
cf = 0x0009 # Image format, 9 = bitmap
|
||||
Reserved3 = 0x0000 # Reserved
|
||||
cbPictFmla = 0x0000 # Length of FMLA structure
|
||||
Reserved4 = 0x0000 # Reserved
|
||||
grbit2 = 0x0001 # Option flags
|
||||
Reserved5 = 0x0000 # Reserved
|
||||
|
||||
data = pack("<L", cObj)
|
||||
data += pack("<H", OT)
|
||||
data += pack("<H", id)
|
||||
data += pack("<H", grbit)
|
||||
data += pack("<H", colL)
|
||||
data += pack("<H", dxL)
|
||||
data += pack("<H", rwT)
|
||||
data += pack("<H", dyT)
|
||||
data += pack("<H", colR)
|
||||
data += pack("<H", dxR)
|
||||
data += pack("<H", rwB)
|
||||
data += pack("<H", dyB)
|
||||
data += pack("<H", cbMacro)
|
||||
data += pack("<L", Reserved1)
|
||||
data += pack("<H", Reserved2)
|
||||
data += pack("<B", icvBack)
|
||||
data += pack("<B", icvFore)
|
||||
data += pack("<B", fls)
|
||||
data += pack("<B", fAuto)
|
||||
data += pack("<B", icv)
|
||||
data += pack("<B", lns)
|
||||
data += pack("<B", lnw)
|
||||
data += pack("<B", fAutoB)
|
||||
data += pack("<H", frs)
|
||||
data += pack("<L", cf)
|
||||
data += pack("<H", Reserved3)
|
||||
data += pack("<H", cbPictFmla)
|
||||
data += pack("<H", Reserved4)
|
||||
data += pack("<H", grbit2)
|
||||
data += pack("<L", Reserved5)
|
||||
|
||||
self._rec_data = data
|
||||
|
||||
def _process_bitmap(bitmap):
|
||||
"""Convert a 24 bit bitmap into the modified internal format used by Windows.
|
||||
This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
|
||||
MSDN library.
|
||||
|
||||
"""
|
||||
# Open file and binmode the data in case the platform needs it.
|
||||
fh = file(bitmap, "rb")
|
||||
try:
|
||||
# Slurp the file into a string.
|
||||
data = fh.read()
|
||||
finally:
|
||||
fh.close()
|
||||
# Check that the file is big enough to be a bitmap.
|
||||
if len(data) <= 0x36:
|
||||
raise Exception("bitmap doesn't contain enough data.")
|
||||
# The first 2 bytes are used to identify the bitmap.
|
||||
if (data[:2] != "BM"):
|
||||
raise Exception("bitmap doesn't appear to to be a valid bitmap image.")
|
||||
# Remove bitmap data: ID.
|
||||
data = data[2:]
|
||||
# Read and remove the bitmap size. This is more reliable than reading
|
||||
# the data size at offset 0x22.
|
||||
#
|
||||
size = unpack("<L", data[:4])[0]
|
||||
size -= 0x36 # Subtract size of bitmap header.
|
||||
size += 0x0C # Add size of BIFF header.
|
||||
data = data[4:]
|
||||
# Remove bitmap data: reserved, offset, header length.
|
||||
data = data[12:]
|
||||
# Read and remove the bitmap width and height. Verify the sizes.
|
||||
width, height = unpack("<LL", data[:8])
|
||||
data = data[8:]
|
||||
if (width > 0xFFFF):
|
||||
raise Exception("bitmap: largest image width supported is 65k.")
|
||||
if (height > 0xFFFF):
|
||||
raise Exception("bitmap: largest image height supported is 65k.")
|
||||
# Read and remove the bitmap planes and bpp data. Verify them.
|
||||
planes, bitcount = unpack("<HH", data[:4])
|
||||
data = data[4:]
|
||||
if (bitcount != 24):
|
||||
raise Exception("bitmap isn't a 24bit true color bitmap.")
|
||||
if (planes != 1):
|
||||
raise Exception("bitmap: only 1 plane supported in bitmap image.")
|
||||
# Read and remove the bitmap compression. Verify compression.
|
||||
compression = unpack("<L", data[:4])[0]
|
||||
data = data[4:]
|
||||
if (compression != 0):
|
||||
raise Exception("bitmap: compression not supported in bitmap image.")
|
||||
# Remove bitmap data: data size, hres, vres, colours, imp. colours.
|
||||
data = data[20:]
|
||||
# Add the BITMAPCOREHEADER data
|
||||
header = pack("<LHHHH", 0x000c, width, height, 0x01, 0x18)
|
||||
data = header + data
|
||||
return (width, height, size, data)
|
||||
|
||||
|
||||
class ImDataBmpRecord(BiffRecord):
|
||||
_REC_ID = 0x007F
|
||||
|
||||
def __init__(self, filename):
|
||||
"""Insert a 24bit bitmap image in a worksheet. The main record required is
|
||||
IMDATA but it must be proceeded by a OBJ record to define its position.
|
||||
|
||||
"""
|
||||
BiffRecord.__init__(self)
|
||||
|
||||
self.width, self.height, self.size, data = _process_bitmap(filename)
|
||||
# Write the IMDATA record to store the bitmap data
|
||||
cf = 0x09
|
||||
env = 0x01
|
||||
lcb = self.size
|
||||
self._rec_data = pack("<HHL", cf, env, lcb) + data
|
||||
|
||||
|
||||
# -*- coding: windows-1251 -*-
|
||||
|
||||
# Portions are Copyright (C) 2005 Roman V. Kiseliov
|
||||
# Portions are Copyright (c) 2004 Evgeny Filatov <fufff@users.sourceforge.net>
|
||||
# Portions are Copyright (c) 2002-2004 John McNamara (Perl Spreadsheet::WriteExcel)
|
||||
|
||||
from BIFFRecords import BiffRecord
|
||||
from struct import *
|
||||
|
||||
|
||||
def _size_col(sheet, col):
|
||||
return sheet.col_width(col)
|
||||
|
||||
|
||||
def _size_row(sheet, row):
|
||||
return sheet.row_height(row)
|
||||
|
||||
|
||||
def _position_image(sheet, row_start, col_start, x1, y1, width, height):
|
||||
"""Calculate the vertices that define the position of the image as required by
|
||||
the OBJ record.
|
||||
|
||||
+------------+------------+
|
||||
| A | B |
|
||||
+-----+------------+------------+
|
||||
| |(x1,y1) | |
|
||||
| 1 |(A1)._______|______ |
|
||||
| | | | |
|
||||
| | | | |
|
||||
+-----+----| BITMAP |-----+
|
||||
| | | | |
|
||||
| 2 | |______________. |
|
||||
| | | (B2)|
|
||||
| | | (x2,y2)|
|
||||
+---- +------------+------------+
|
||||
|
||||
Example of a bitmap that covers some of the area from cell A1 to cell B2.
|
||||
|
||||
Based on the width and height of the bitmap we need to calculate 8 vars:
|
||||
col_start, row_start, col_end, row_end, x1, y1, x2, y2.
|
||||
The width and height of the cells are also variable and have to be taken into
|
||||
account.
|
||||
The values of col_start and row_start are passed in from the calling
|
||||
function. The values of col_end and row_end are calculated by subtracting
|
||||
the width and height of the bitmap from the width and height of the
|
||||
underlying cells.
|
||||
The vertices are expressed as a percentage of the underlying cell width as
|
||||
follows (rhs values are in pixels):
|
||||
|
||||
x1 = X / W *1024
|
||||
y1 = Y / H *256
|
||||
x2 = (X-1) / W *1024
|
||||
y2 = (Y-1) / H *256
|
||||
|
||||
Where: X is distance from the left side of the underlying cell
|
||||
Y is distance from the top of the underlying cell
|
||||
W is the width of the cell
|
||||
H is the height of the cell
|
||||
|
||||
Note: the SDK incorrectly states that the height should be expressed as a
|
||||
percentage of 1024.
|
||||
|
||||
col_start - Col containing upper left corner of object
|
||||
row_start - Row containing top left corner of object
|
||||
x1 - Distance to left side of object
|
||||
y1 - Distance to top of object
|
||||
width - Width of image frame
|
||||
height - Height of image frame
|
||||
|
||||
"""
|
||||
# Adjust start column for offsets that are greater than the col width
|
||||
while x1 >= _size_col(sheet, col_start):
|
||||
x1 -= _size_col(sheet, col_start)
|
||||
col_start += 1
|
||||
# Adjust start row for offsets that are greater than the row height
|
||||
while y1 >= _size_row(sheet, row_start):
|
||||
y1 -= _size_row(sheet, row_start)
|
||||
row_start += 1
|
||||
# Initialise end cell to the same as the start cell
|
||||
row_end = row_start # Row containing bottom right corner of object
|
||||
col_end = col_start # Col containing lower right corner of object
|
||||
width = width + x1 - 1
|
||||
height = height + y1 - 1
|
||||
# Subtract the underlying cell widths to find the end cell of the image
|
||||
while (width >= _size_col(sheet, col_end)):
|
||||
width -= _size_col(sheet, col_end)
|
||||
col_end += 1
|
||||
# Subtract the underlying cell heights to find the end cell of the image
|
||||
while (height >= _size_row(sheet, row_end)):
|
||||
height -= _size_row(sheet, row_end)
|
||||
row_end += 1
|
||||
# Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
|
||||
# with zero height or width.
|
||||
if ((_size_col(sheet, col_start) == 0) or (_size_col(sheet, col_end) == 0)
|
||||
or (_size_row(sheet, row_start) == 0) or (_size_row(sheet, row_end) == 0)):
|
||||
return
|
||||
# Convert the pixel values to the percentage value expected by Excel
|
||||
x1 = int(float(x1) / _size_col(sheet, col_start) * 1024)
|
||||
y1 = int(float(y1) / _size_row(sheet, row_start) * 256)
|
||||
# Distance to right side of object
|
||||
x2 = int(float(width) / _size_col(sheet, col_end) * 1024)
|
||||
# Distance to bottom of object
|
||||
y2 = int(float(height) / _size_row(sheet, row_end) * 256)
|
||||
return (col_start, x1, row_start, y1, col_end, x2, row_end, y2)
|
||||
|
||||
|
||||
class ObjBmpRecord(BiffRecord):
|
||||
_REC_ID = 0x005D # Record identifier
|
||||
|
||||
def __init__(self, row, col, sheet, im_data_bmp, x, y, scale_x, scale_y):
|
||||
# Scale the frame of the image.
|
||||
width = im_data_bmp.width * scale_x
|
||||
height = im_data_bmp.height * scale_y
|
||||
|
||||
# Calculate the vertices of the image and write the OBJ record
|
||||
coordinates = _position_image(sheet, row, col, x, y, width, height)
|
||||
# print coordinates
|
||||
col_start, x1, row_start, y1, col_end, x2, row_end, y2 = coordinates
|
||||
|
||||
"""Store the OBJ record that precedes an IMDATA record. This could be generalise
|
||||
to support other Excel objects.
|
||||
|
||||
"""
|
||||
cObj = 0x0001 # Count of objects in file (set to 1)
|
||||
OT = 0x0008 # Object type. 8 = Picture
|
||||
id = 0x0001 # Object ID
|
||||
grbit = 0x0614 # Option flags
|
||||
colL = col_start # Col containing upper left corner of object
|
||||
dxL = x1 # Distance from left side of cell
|
||||
rwT = row_start # Row containing top left corner of object
|
||||
dyT = y1 # Distance from top of cell
|
||||
colR = col_end # Col containing lower right corner of object
|
||||
dxR = x2 # Distance from right of cell
|
||||
rwB = row_end # Row containing bottom right corner of object
|
||||
dyB = y2 # Distance from bottom of cell
|
||||
cbMacro = 0x0000 # Length of FMLA structure
|
||||
Reserved1 = 0x0000 # Reserved
|
||||
Reserved2 = 0x0000 # Reserved
|
||||
icvBack = 0x09 # Background colour
|
||||
icvFore = 0x09 # Foreground colour
|
||||
fls = 0x00 # Fill pattern
|
||||
fAuto = 0x00 # Automatic fill
|
||||
icv = 0x08 # Line colour
|
||||
lns = 0xff # Line style
|
||||
lnw = 0x01 # Line weight
|
||||
fAutoB = 0x00 # Automatic border
|
||||
frs = 0x0000 # Frame style
|
||||
cf = 0x0009 # Image format, 9 = bitmap
|
||||
Reserved3 = 0x0000 # Reserved
|
||||
cbPictFmla = 0x0000 # Length of FMLA structure
|
||||
Reserved4 = 0x0000 # Reserved
|
||||
grbit2 = 0x0001 # Option flags
|
||||
Reserved5 = 0x0000 # Reserved
|
||||
|
||||
data = pack("<L", cObj)
|
||||
data += pack("<H", OT)
|
||||
data += pack("<H", id)
|
||||
data += pack("<H", grbit)
|
||||
data += pack("<H", colL)
|
||||
data += pack("<H", dxL)
|
||||
data += pack("<H", rwT)
|
||||
data += pack("<H", dyT)
|
||||
data += pack("<H", colR)
|
||||
data += pack("<H", dxR)
|
||||
data += pack("<H", rwB)
|
||||
data += pack("<H", dyB)
|
||||
data += pack("<H", cbMacro)
|
||||
data += pack("<L", Reserved1)
|
||||
data += pack("<H", Reserved2)
|
||||
data += pack("<B", icvBack)
|
||||
data += pack("<B", icvFore)
|
||||
data += pack("<B", fls)
|
||||
data += pack("<B", fAuto)
|
||||
data += pack("<B", icv)
|
||||
data += pack("<B", lns)
|
||||
data += pack("<B", lnw)
|
||||
data += pack("<B", fAutoB)
|
||||
data += pack("<H", frs)
|
||||
data += pack("<L", cf)
|
||||
data += pack("<H", Reserved3)
|
||||
data += pack("<H", cbPictFmla)
|
||||
data += pack("<H", Reserved4)
|
||||
data += pack("<H", grbit2)
|
||||
data += pack("<L", Reserved5)
|
||||
|
||||
self._rec_data = data
|
||||
|
||||
def _process_bitmap(bitmap):
|
||||
"""Convert a 24 bit bitmap into the modified internal format used by Windows.
|
||||
This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
|
||||
MSDN library.
|
||||
|
||||
"""
|
||||
# Open file and binmode the data in case the platform needs it.
|
||||
fh = file(bitmap, "rb")
|
||||
try:
|
||||
# Slurp the file into a string.
|
||||
data = fh.read()
|
||||
finally:
|
||||
fh.close()
|
||||
# Check that the file is big enough to be a bitmap.
|
||||
if len(data) <= 0x36:
|
||||
raise Exception("bitmap doesn't contain enough data.")
|
||||
# The first 2 bytes are used to identify the bitmap.
|
||||
if (data[:2] != "BM"):
|
||||
raise Exception("bitmap doesn't appear to to be a valid bitmap image.")
|
||||
# Remove bitmap data: ID.
|
||||
data = data[2:]
|
||||
# Read and remove the bitmap size. This is more reliable than reading
|
||||
# the data size at offset 0x22.
|
||||
#
|
||||
size = unpack("<L", data[:4])[0]
|
||||
size -= 0x36 # Subtract size of bitmap header.
|
||||
size += 0x0C # Add size of BIFF header.
|
||||
data = data[4:]
|
||||
# Remove bitmap data: reserved, offset, header length.
|
||||
data = data[12:]
|
||||
# Read and remove the bitmap width and height. Verify the sizes.
|
||||
width, height = unpack("<LL", data[:8])
|
||||
data = data[8:]
|
||||
if (width > 0xFFFF):
|
||||
raise Exception("bitmap: largest image width supported is 65k.")
|
||||
if (height > 0xFFFF):
|
||||
raise Exception("bitmap: largest image height supported is 65k.")
|
||||
# Read and remove the bitmap planes and bpp data. Verify them.
|
||||
planes, bitcount = unpack("<HH", data[:4])
|
||||
data = data[4:]
|
||||
if (bitcount != 24):
|
||||
raise Exception("bitmap isn't a 24bit true color bitmap.")
|
||||
if (planes != 1):
|
||||
raise Exception("bitmap: only 1 plane supported in bitmap image.")
|
||||
# Read and remove the bitmap compression. Verify compression.
|
||||
compression = unpack("<L", data[:4])[0]
|
||||
data = data[4:]
|
||||
if (compression != 0):
|
||||
raise Exception("bitmap: compression not supported in bitmap image.")
|
||||
# Remove bitmap data: data size, hres, vres, colours, imp. colours.
|
||||
data = data[20:]
|
||||
# Add the BITMAPCOREHEADER data
|
||||
header = pack("<LHHHH", 0x000c, width, height, 0x01, 0x18)
|
||||
data = header + data
|
||||
return (width, height, size, data)
|
||||
|
||||
|
||||
class ImDataBmpRecord(BiffRecord):
|
||||
_REC_ID = 0x007F
|
||||
|
||||
def __init__(self, filename):
|
||||
"""Insert a 24bit bitmap image in a worksheet. The main record required is
|
||||
IMDATA but it must be proceeded by a OBJ record to define its position.
|
||||
|
||||
"""
|
||||
BiffRecord.__init__(self)
|
||||
|
||||
self.width, self.height, self.size, data = _process_bitmap(filename)
|
||||
# Write the IMDATA record to store the bitmap data
|
||||
cf = 0x09
|
||||
env = 0x01
|
||||
lcb = self.size
|
||||
self._rec_data = pack("<HHL", cf, env, lcb) + data
|
||||
|
||||
|
||||
|
||||
+243
-243
@@ -1,243 +1,243 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
from struct import unpack, pack
|
||||
import BIFFRecords
|
||||
|
||||
class StrCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "sst_idx"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, sst_idx):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
self.sst_idx = sst_idx
|
||||
|
||||
def get_biff_data(self):
|
||||
# return BIFFRecords.LabelSSTRecord(self.rowx, self.colx, self.xf_idx, self.sst_idx).get()
|
||||
return pack('<5HL', 0x00FD, 10, self.rowx, self.colx, self.xf_idx, self.sst_idx)
|
||||
|
||||
class BlankCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
|
||||
def get_biff_data(self):
|
||||
# return BIFFRecords.BlankRecord(self.rowx, self.colx, self.xf_idx).get()
|
||||
return pack('<5H', 0x0201, 6, self.rowx, self.colx, self.xf_idx)
|
||||
|
||||
class MulBlankCell(object):
|
||||
__slots__ = ["rowx", "colx1", "colx2", "xf_idx"]
|
||||
|
||||
def __init__(self, rowx, colx1, colx2, xf_idx):
|
||||
self.rowx = rowx
|
||||
self.colx1 = colx1
|
||||
self.colx2 = colx2
|
||||
self.xf_idx = xf_idx
|
||||
|
||||
def get_biff_data(self):
|
||||
return BIFFRecords.MulBlankRecord(self.rowx,
|
||||
self.colx1, self.colx2, self.xf_idx).get()
|
||||
|
||||
class NumberCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "number"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, number):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
self.number = float(number)
|
||||
|
||||
def get_encoded_data(self):
|
||||
rk_encoded = 0
|
||||
num = self.number
|
||||
|
||||
# The four possible kinds of RK encoding are *not* mutually exclusive.
|
||||
# The 30-bit integer variety picks up the most.
|
||||
# In the code below, the four varieties are checked in descending order
|
||||
# of bangs per buck, or not at all.
|
||||
# SJM 2007-10-01
|
||||
|
||||
if -0x20000000 <= num < 0x20000000: # fits in 30-bit *signed* int
|
||||
inum = int(num)
|
||||
if inum == num: # survives round-trip
|
||||
# print "30-bit integer RK", inum, hex(inum)
|
||||
rk_encoded = 2 | (inum << 2)
|
||||
return 1, rk_encoded
|
||||
|
||||
temp = num * 100
|
||||
|
||||
if -0x20000000 <= temp < 0x20000000:
|
||||
# That was step 1: the coded value will fit in
|
||||
# a 30-bit signed integer.
|
||||
itemp = int(round(temp, 0))
|
||||
# That was step 2: "itemp" is the best candidate coded value.
|
||||
# Now for step 3: simulate the decoding,
|
||||
# to check for round-trip correctness.
|
||||
if itemp / 100.0 == num:
|
||||
# print "30-bit integer RK*100", itemp, hex(itemp)
|
||||
rk_encoded = 3 | (itemp << 2)
|
||||
return 1, rk_encoded
|
||||
|
||||
if 0: # Cost of extra pack+unpack not justified by tiny yield.
|
||||
packed = pack('<d', num)
|
||||
w01, w23 = unpack('<2i', packed)
|
||||
if not w01 and not(w23 & 3):
|
||||
# 34 lsb are 0
|
||||
# print "float RK", w23, hex(w23)
|
||||
return 1, w23
|
||||
|
||||
packed100 = pack('<d', temp)
|
||||
w01, w23 = unpack('<2i', packed100)
|
||||
if not w01 and not(w23 & 3):
|
||||
# 34 lsb are 0
|
||||
# print "float RK*100", w23, hex(w23)
|
||||
return 1, w23 | 1
|
||||
|
||||
#print "Number"
|
||||
#print
|
||||
return 0, pack('<5Hd', 0x0203, 14, self.rowx, self.colx, self.xf_idx, num)
|
||||
|
||||
def get_biff_data(self):
|
||||
isRK, value = self.get_encoded_data()
|
||||
if isRK:
|
||||
return pack('<5Hi', 0x27E, 10, self.rowx, self.colx, self.xf_idx, value)
|
||||
return value # NUMBER record already packed
|
||||
|
||||
class BooleanCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "number"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, number):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
self.number = number
|
||||
|
||||
def get_biff_data(self):
|
||||
return BIFFRecords.BoolErrRecord(self.rowx,
|
||||
self.colx, self.xf_idx, self.number, 0).get()
|
||||
|
||||
error_code_map = {
|
||||
0x00: 0, # Intersection of two cell ranges is empty
|
||||
0x07: 7, # Division by zero
|
||||
0x0F: 15, # Wrong type of operand
|
||||
0x17: 23, # Illegal or deleted cell reference
|
||||
0x1D: 29, # Wrong function or range name
|
||||
0x24: 36, # Value range overflow
|
||||
0x2A: 42, # Argument or function not available
|
||||
'#NULL!' : 0, # Intersection of two cell ranges is empty
|
||||
'#DIV/0!': 7, # Division by zero
|
||||
'#VALUE!': 36, # Wrong type of operand
|
||||
'#REF!' : 23, # Illegal or deleted cell reference
|
||||
'#NAME?' : 29, # Wrong function or range name
|
||||
'#NUM!' : 36, # Value range overflow
|
||||
'#N/A!' : 42, # Argument or function not available
|
||||
}
|
||||
|
||||
class ErrorCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "number"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, error_string_or_code):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
try:
|
||||
self.number = error_code_map[error_string_or_code]
|
||||
except KeyError:
|
||||
raise Exception('Illegal error value (%r)' % error_string_or_code)
|
||||
|
||||
def get_biff_data(self):
|
||||
return BIFFRecords.BoolErrRecord(self.rowx,
|
||||
self.colx, self.xf_idx, self.number, 1).get()
|
||||
|
||||
class FormulaCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "frmla", "calc_flags"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, frmla, calc_flags=0):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
self.frmla = frmla
|
||||
self.calc_flags = calc_flags
|
||||
|
||||
def get_biff_data(self):
|
||||
return BIFFRecords.FormulaRecord(self.rowx,
|
||||
self.colx, self.xf_idx, self.frmla.rpn(), self.calc_flags).get()
|
||||
|
||||
# module-level function for *internal* use by the Row module
|
||||
|
||||
def _get_cells_biff_data_mul(rowx, cell_items):
|
||||
# Return the BIFF data for all cell records in the row.
|
||||
# Adjacent BLANK|RK records are combined into MUL(BLANK|RK) records.
|
||||
pieces = []
|
||||
nitems = len(cell_items)
|
||||
i = 0
|
||||
while i < nitems:
|
||||
icolx, icell = cell_items[i]
|
||||
if isinstance(icell, NumberCell):
|
||||
isRK, value = icell.get_encoded_data()
|
||||
if not isRK:
|
||||
pieces.append(value) # pre-packed NUMBER record
|
||||
i += 1
|
||||
continue
|
||||
muldata = [(value, icell.xf_idx)]
|
||||
target = NumberCell
|
||||
elif isinstance(icell, BlankCell):
|
||||
muldata = [icell.xf_idx]
|
||||
target = BlankCell
|
||||
else:
|
||||
pieces.append(icell.get_biff_data())
|
||||
i += 1
|
||||
continue
|
||||
lastcolx = icolx
|
||||
j = i
|
||||
packed_record = ''
|
||||
for j in xrange(i+1, nitems):
|
||||
jcolx, jcell = cell_items[j]
|
||||
if jcolx != lastcolx + 1:
|
||||
nexti = j
|
||||
break
|
||||
if not isinstance(jcell, target):
|
||||
nexti = j
|
||||
break
|
||||
if target == NumberCell:
|
||||
isRK, value = jcell.get_encoded_data()
|
||||
if not isRK:
|
||||
packed_record = value
|
||||
nexti = j + 1
|
||||
break
|
||||
muldata.append((value, jcell.xf_idx))
|
||||
else:
|
||||
muldata.append(jcell.xf_idx)
|
||||
lastcolx = jcolx
|
||||
else:
|
||||
nexti = j + 1
|
||||
if target == NumberCell:
|
||||
if lastcolx == icolx:
|
||||
# RK record
|
||||
value, xf_idx = muldata[0]
|
||||
pieces.append(pack('<5Hi', 0x027E, 10, rowx, icolx, xf_idx, value))
|
||||
else:
|
||||
# MULRK record
|
||||
nc = lastcolx - icolx + 1
|
||||
pieces.append(pack('<4H', 0x00BD, 6 * nc + 6, rowx, icolx))
|
||||
pieces.append(''.join([pack('<Hi', xf_idx, value) for value, xf_idx in muldata]))
|
||||
pieces.append(pack('<H', lastcolx))
|
||||
else:
|
||||
if lastcolx == icolx:
|
||||
# BLANK record
|
||||
xf_idx = muldata[0]
|
||||
pieces.append(pack('<5H', 0x0201, 6, rowx, icolx, xf_idx))
|
||||
else:
|
||||
# MULBLANK record
|
||||
nc = lastcolx - icolx + 1
|
||||
pieces.append(pack('<4H', 0x00BE, 2 * nc + 6, rowx, icolx))
|
||||
pieces.append(''.join([pack('<H', xf_idx) for xf_idx in muldata]))
|
||||
pieces.append(pack('<H', lastcolx))
|
||||
if packed_record:
|
||||
pieces.append(packed_record)
|
||||
i = nexti
|
||||
return ''.join(pieces)
|
||||
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
from struct import unpack, pack
|
||||
import BIFFRecords
|
||||
|
||||
class StrCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "sst_idx"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, sst_idx):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
self.sst_idx = sst_idx
|
||||
|
||||
def get_biff_data(self):
|
||||
# return BIFFRecords.LabelSSTRecord(self.rowx, self.colx, self.xf_idx, self.sst_idx).get()
|
||||
return pack('<5HL', 0x00FD, 10, self.rowx, self.colx, self.xf_idx, self.sst_idx)
|
||||
|
||||
class BlankCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
|
||||
def get_biff_data(self):
|
||||
# return BIFFRecords.BlankRecord(self.rowx, self.colx, self.xf_idx).get()
|
||||
return pack('<5H', 0x0201, 6, self.rowx, self.colx, self.xf_idx)
|
||||
|
||||
class MulBlankCell(object):
|
||||
__slots__ = ["rowx", "colx1", "colx2", "xf_idx"]
|
||||
|
||||
def __init__(self, rowx, colx1, colx2, xf_idx):
|
||||
self.rowx = rowx
|
||||
self.colx1 = colx1
|
||||
self.colx2 = colx2
|
||||
self.xf_idx = xf_idx
|
||||
|
||||
def get_biff_data(self):
|
||||
return BIFFRecords.MulBlankRecord(self.rowx,
|
||||
self.colx1, self.colx2, self.xf_idx).get()
|
||||
|
||||
class NumberCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "number"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, number):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
self.number = float(number)
|
||||
|
||||
def get_encoded_data(self):
|
||||
rk_encoded = 0
|
||||
num = self.number
|
||||
|
||||
# The four possible kinds of RK encoding are *not* mutually exclusive.
|
||||
# The 30-bit integer variety picks up the most.
|
||||
# In the code below, the four varieties are checked in descending order
|
||||
# of bangs per buck, or not at all.
|
||||
# SJM 2007-10-01
|
||||
|
||||
if -0x20000000 <= num < 0x20000000: # fits in 30-bit *signed* int
|
||||
inum = int(num)
|
||||
if inum == num: # survives round-trip
|
||||
# print "30-bit integer RK", inum, hex(inum)
|
||||
rk_encoded = 2 | (inum << 2)
|
||||
return 1, rk_encoded
|
||||
|
||||
temp = num * 100
|
||||
|
||||
if -0x20000000 <= temp < 0x20000000:
|
||||
# That was step 1: the coded value will fit in
|
||||
# a 30-bit signed integer.
|
||||
itemp = int(round(temp, 0))
|
||||
# That was step 2: "itemp" is the best candidate coded value.
|
||||
# Now for step 3: simulate the decoding,
|
||||
# to check for round-trip correctness.
|
||||
if itemp / 100.0 == num:
|
||||
# print "30-bit integer RK*100", itemp, hex(itemp)
|
||||
rk_encoded = 3 | (itemp << 2)
|
||||
return 1, rk_encoded
|
||||
|
||||
if 0: # Cost of extra pack+unpack not justified by tiny yield.
|
||||
packed = pack('<d', num)
|
||||
w01, w23 = unpack('<2i', packed)
|
||||
if not w01 and not(w23 & 3):
|
||||
# 34 lsb are 0
|
||||
# print "float RK", w23, hex(w23)
|
||||
return 1, w23
|
||||
|
||||
packed100 = pack('<d', temp)
|
||||
w01, w23 = unpack('<2i', packed100)
|
||||
if not w01 and not(w23 & 3):
|
||||
# 34 lsb are 0
|
||||
# print "float RK*100", w23, hex(w23)
|
||||
return 1, w23 | 1
|
||||
|
||||
#print "Number"
|
||||
#print
|
||||
return 0, pack('<5Hd', 0x0203, 14, self.rowx, self.colx, self.xf_idx, num)
|
||||
|
||||
def get_biff_data(self):
|
||||
isRK, value = self.get_encoded_data()
|
||||
if isRK:
|
||||
return pack('<5Hi', 0x27E, 10, self.rowx, self.colx, self.xf_idx, value)
|
||||
return value # NUMBER record already packed
|
||||
|
||||
class BooleanCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "number"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, number):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
self.number = number
|
||||
|
||||
def get_biff_data(self):
|
||||
return BIFFRecords.BoolErrRecord(self.rowx,
|
||||
self.colx, self.xf_idx, self.number, 0).get()
|
||||
|
||||
error_code_map = {
|
||||
0x00: 0, # Intersection of two cell ranges is empty
|
||||
0x07: 7, # Division by zero
|
||||
0x0F: 15, # Wrong type of operand
|
||||
0x17: 23, # Illegal or deleted cell reference
|
||||
0x1D: 29, # Wrong function or range name
|
||||
0x24: 36, # Value range overflow
|
||||
0x2A: 42, # Argument or function not available
|
||||
'#NULL!' : 0, # Intersection of two cell ranges is empty
|
||||
'#DIV/0!': 7, # Division by zero
|
||||
'#VALUE!': 36, # Wrong type of operand
|
||||
'#REF!' : 23, # Illegal or deleted cell reference
|
||||
'#NAME?' : 29, # Wrong function or range name
|
||||
'#NUM!' : 36, # Value range overflow
|
||||
'#N/A!' : 42, # Argument or function not available
|
||||
}
|
||||
|
||||
class ErrorCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "number"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, error_string_or_code):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
try:
|
||||
self.number = error_code_map[error_string_or_code]
|
||||
except KeyError:
|
||||
raise Exception('Illegal error value (%r)' % error_string_or_code)
|
||||
|
||||
def get_biff_data(self):
|
||||
return BIFFRecords.BoolErrRecord(self.rowx,
|
||||
self.colx, self.xf_idx, self.number, 1).get()
|
||||
|
||||
class FormulaCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "frmla", "calc_flags"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, frmla, calc_flags=0):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
self.frmla = frmla
|
||||
self.calc_flags = calc_flags
|
||||
|
||||
def get_biff_data(self):
|
||||
return BIFFRecords.FormulaRecord(self.rowx,
|
||||
self.colx, self.xf_idx, self.frmla.rpn(), self.calc_flags).get()
|
||||
|
||||
# module-level function for *internal* use by the Row module
|
||||
|
||||
def _get_cells_biff_data_mul(rowx, cell_items):
|
||||
# Return the BIFF data for all cell records in the row.
|
||||
# Adjacent BLANK|RK records are combined into MUL(BLANK|RK) records.
|
||||
pieces = []
|
||||
nitems = len(cell_items)
|
||||
i = 0
|
||||
while i < nitems:
|
||||
icolx, icell = cell_items[i]
|
||||
if isinstance(icell, NumberCell):
|
||||
isRK, value = icell.get_encoded_data()
|
||||
if not isRK:
|
||||
pieces.append(value) # pre-packed NUMBER record
|
||||
i += 1
|
||||
continue
|
||||
muldata = [(value, icell.xf_idx)]
|
||||
target = NumberCell
|
||||
elif isinstance(icell, BlankCell):
|
||||
muldata = [icell.xf_idx]
|
||||
target = BlankCell
|
||||
else:
|
||||
pieces.append(icell.get_biff_data())
|
||||
i += 1
|
||||
continue
|
||||
lastcolx = icolx
|
||||
j = i
|
||||
packed_record = ''
|
||||
for j in xrange(i+1, nitems):
|
||||
jcolx, jcell = cell_items[j]
|
||||
if jcolx != lastcolx + 1:
|
||||
nexti = j
|
||||
break
|
||||
if not isinstance(jcell, target):
|
||||
nexti = j
|
||||
break
|
||||
if target == NumberCell:
|
||||
isRK, value = jcell.get_encoded_data()
|
||||
if not isRK:
|
||||
packed_record = value
|
||||
nexti = j + 1
|
||||
break
|
||||
muldata.append((value, jcell.xf_idx))
|
||||
else:
|
||||
muldata.append(jcell.xf_idx)
|
||||
lastcolx = jcolx
|
||||
else:
|
||||
nexti = j + 1
|
||||
if target == NumberCell:
|
||||
if lastcolx == icolx:
|
||||
# RK record
|
||||
value, xf_idx = muldata[0]
|
||||
pieces.append(pack('<5Hi', 0x027E, 10, rowx, icolx, xf_idx, value))
|
||||
else:
|
||||
# MULRK record
|
||||
nc = lastcolx - icolx + 1
|
||||
pieces.append(pack('<4H', 0x00BD, 6 * nc + 6, rowx, icolx))
|
||||
pieces.append(''.join([pack('<Hi', xf_idx, value) for value, xf_idx in muldata]))
|
||||
pieces.append(pack('<H', lastcolx))
|
||||
else:
|
||||
if lastcolx == icolx:
|
||||
# BLANK record
|
||||
xf_idx = muldata[0]
|
||||
pieces.append(pack('<5H', 0x0201, 6, rowx, icolx, xf_idx))
|
||||
else:
|
||||
# MULBLANK record
|
||||
nc = lastcolx - icolx + 1
|
||||
pieces.append(pack('<4H', 0x00BE, 2 * nc + 6, rowx, icolx))
|
||||
pieces.append(''.join([pack('<H', xf_idx) for xf_idx in muldata]))
|
||||
pieces.append(pack('<H', lastcolx))
|
||||
if packed_record:
|
||||
pieces.append(packed_record)
|
||||
i = nexti
|
||||
return ''.join(pieces)
|
||||
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
from BIFFRecords import ColInfoRecord
|
||||
|
||||
class Column(object):
|
||||
def __init__(self, colx, parent_sheet):
|
||||
if not(isinstance(colx, int) and 0 <= colx <= 255):
|
||||
raise ValueError("column index (%r) not an int in range(256)" % colx)
|
||||
self._index = colx
|
||||
self._parent = parent_sheet
|
||||
self._parent_wb = parent_sheet.get_parent()
|
||||
self._xf_index = 0x0F
|
||||
|
||||
self.width = 0x0B92
|
||||
self.hidden = 0
|
||||
self.level = 0
|
||||
self.collapse = 0
|
||||
|
||||
def set_style(self, style):
|
||||
self._xf_index = self._parent_wb.add_style(style)
|
||||
|
||||
def width_in_pixels(self):
|
||||
# *** Approximation ****
|
||||
return int(round(self.width * 0.0272 + 0.446, 0))
|
||||
|
||||
def get_biff_record(self):
|
||||
options = (self.hidden & 0x01) << 0
|
||||
options |= (self.level & 0x07) << 8
|
||||
options |= (self.collapse & 0x01) << 12
|
||||
|
||||
return ColInfoRecord(self._index, self._index, self.width, self._xf_index, options).get()
|
||||
|
||||
|
||||
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
from BIFFRecords import ColInfoRecord
|
||||
|
||||
class Column(object):
|
||||
def __init__(self, colx, parent_sheet):
|
||||
if not(isinstance(colx, int) and 0 <= colx <= 255):
|
||||
raise ValueError("column index (%r) not an int in range(256)" % colx)
|
||||
self._index = colx
|
||||
self._parent = parent_sheet
|
||||
self._parent_wb = parent_sheet.get_parent()
|
||||
self._xf_index = 0x0F
|
||||
|
||||
self.width = 0x0B92
|
||||
self.hidden = 0
|
||||
self.level = 0
|
||||
self.collapse = 0
|
||||
|
||||
def set_style(self, style):
|
||||
self._xf_index = self._parent_wb.add_style(style)
|
||||
|
||||
def width_in_pixels(self):
|
||||
# *** Approximation ****
|
||||
return int(round(self.width * 0.0272 + 0.446, 0))
|
||||
|
||||
def get_biff_record(self):
|
||||
options = (self.hidden & 0x01) << 0
|
||||
options |= (self.level & 0x07) << 8
|
||||
options |= (self.collapse & 0x01) << 12
|
||||
|
||||
return ColInfoRecord(self._index, self._index, self.width, self._xf_index, options).get()
|
||||
|
||||
|
||||
|
||||
|
||||
+516
-516
File diff suppressed because it is too large
Load Diff
@@ -1,43 +1,43 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
import ExcelFormulaParser, ExcelFormulaLexer
|
||||
import struct
|
||||
from antlr import ANTLRException
|
||||
|
||||
|
||||
class Formula(object):
|
||||
__slots__ = ["__init__", "__s", "__parser", "__sheet_refs", "__xcall_refs"]
|
||||
|
||||
|
||||
def __init__(self, s):
|
||||
try:
|
||||
self.__s = s
|
||||
lexer = ExcelFormulaLexer.Lexer(s)
|
||||
self.__parser = ExcelFormulaParser.Parser(lexer)
|
||||
self.__parser.formula()
|
||||
self.__sheet_refs = self.__parser.sheet_references
|
||||
self.__xcall_refs = self.__parser.xcall_references
|
||||
except ANTLRException, e:
|
||||
# print e
|
||||
raise ExcelFormulaParser.FormulaParseException, "can't parse formula " + s
|
||||
|
||||
def get_references(self):
|
||||
return self.__sheet_refs, self.__xcall_refs
|
||||
|
||||
def patch_references(self, patches):
|
||||
for offset, idx in patches:
|
||||
self.__parser.rpn = self.__parser.rpn[:offset] + struct.pack('<H', idx) + self.__parser.rpn[offset+2:]
|
||||
|
||||
def text(self):
|
||||
return self.__s
|
||||
|
||||
def rpn(self):
|
||||
'''
|
||||
Offset Size Contents
|
||||
0 2 Size of the following formula data (sz)
|
||||
2 sz Formula data (RPN token array)
|
||||
[2+sz] var. (optional) Additional data for specific tokens
|
||||
|
||||
'''
|
||||
return struct.pack("<H", len(self.__parser.rpn)) + self.__parser.rpn
|
||||
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
import ExcelFormulaParser, ExcelFormulaLexer
|
||||
import struct
|
||||
from antlr import ANTLRException
|
||||
|
||||
|
||||
class Formula(object):
|
||||
__slots__ = ["__init__", "__s", "__parser", "__sheet_refs", "__xcall_refs"]
|
||||
|
||||
|
||||
def __init__(self, s):
|
||||
try:
|
||||
self.__s = s
|
||||
lexer = ExcelFormulaLexer.Lexer(s)
|
||||
self.__parser = ExcelFormulaParser.Parser(lexer)
|
||||
self.__parser.formula()
|
||||
self.__sheet_refs = self.__parser.sheet_references
|
||||
self.__xcall_refs = self.__parser.xcall_references
|
||||
except ANTLRException, e:
|
||||
# print e
|
||||
raise ExcelFormulaParser.FormulaParseException, "can't parse formula " + s
|
||||
|
||||
def get_references(self):
|
||||
return self.__sheet_refs, self.__xcall_refs
|
||||
|
||||
def patch_references(self, patches):
|
||||
for offset, idx in patches:
|
||||
self.__parser.rpn = self.__parser.rpn[:offset] + struct.pack('<H', idx) + self.__parser.rpn[offset+2:]
|
||||
|
||||
def text(self):
|
||||
return self.__s
|
||||
|
||||
def rpn(self):
|
||||
'''
|
||||
Offset Size Contents
|
||||
0 2 Size of the following formula data (sz)
|
||||
2 sz Formula data (RPN token array)
|
||||
[2+sz] var. (optional) Additional data for specific tokens
|
||||
|
||||
'''
|
||||
return struct.pack("<H", len(self.__parser.rpn)) + self.__parser.rpn
|
||||
|
||||
|
||||
@@ -1,128 +1,128 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
import sys
|
||||
from antlr import EOF, CommonToken as Tok, TokenStream, TokenStreamException
|
||||
import struct
|
||||
import ExcelFormulaParser
|
||||
from re import compile as recompile, match, LOCALE, UNICODE, IGNORECASE, VERBOSE
|
||||
|
||||
|
||||
int_const_pattern = r"\d+\b"
|
||||
flt_const_pattern = r"""
|
||||
(?:
|
||||
(?: \d* \. \d+ ) # .1 .12 .123 etc 9.1 etc 98.1 etc
|
||||
|
|
||||
(?: \d+ \. ) # 1. 12. 123. etc
|
||||
)
|
||||
# followed by optional exponent part
|
||||
(?: [Ee] [+-]? \d+ ) ?
|
||||
"""
|
||||
str_const_pattern = r'"(?:[^"]|"")*"'
|
||||
#range2d_pattern = recompile(r"\$?[A-I]?[A-Z]\$?\d+:\$?[A-I]?[A-Z]\$?\d+"
|
||||
ref2d_r1c1_pattern = r"[Rr]0*[1-9][0-9]*[Cc]0*[1-9][0-9]*"
|
||||
ref2d_pattern = r"\$?[A-I]?[A-Z]\$?0*[1-9][0-9]*"
|
||||
true_pattern = r"TRUE\b"
|
||||
false_pattern = r"FALSE\b"
|
||||
if_pattern = r"IF\b"
|
||||
choose_pattern = r"CHOOSE\b"
|
||||
name_pattern = r"\w[\.\w]*"
|
||||
quotename_pattern = r"'(?:[^']|'')*'" #### It's essential that this bracket be non-grouping.
|
||||
ne_pattern = r"<>"
|
||||
ge_pattern = r">="
|
||||
le_pattern = r"<="
|
||||
|
||||
pattern_type_tuples = (
|
||||
(flt_const_pattern, ExcelFormulaParser.NUM_CONST),
|
||||
(int_const_pattern, ExcelFormulaParser.INT_CONST),
|
||||
(str_const_pattern, ExcelFormulaParser.STR_CONST),
|
||||
# (range2d_pattern , ExcelFormulaParser.RANGE2D),
|
||||
(ref2d_r1c1_pattern, ExcelFormulaParser.REF2D_R1C1),
|
||||
(ref2d_pattern , ExcelFormulaParser.REF2D),
|
||||
(true_pattern , ExcelFormulaParser.TRUE_CONST),
|
||||
(false_pattern , ExcelFormulaParser.FALSE_CONST),
|
||||
(if_pattern , ExcelFormulaParser.FUNC_IF),
|
||||
(choose_pattern , ExcelFormulaParser.FUNC_CHOOSE),
|
||||
(name_pattern , ExcelFormulaParser.NAME),
|
||||
(quotename_pattern, ExcelFormulaParser.QUOTENAME),
|
||||
(ne_pattern, ExcelFormulaParser.NE),
|
||||
(ge_pattern, ExcelFormulaParser.GE),
|
||||
(le_pattern, ExcelFormulaParser.LE),
|
||||
)
|
||||
|
||||
_re = recompile(
|
||||
'(' + ')|('.join([i[0] for i in pattern_type_tuples]) + ')',
|
||||
VERBOSE+LOCALE+IGNORECASE)
|
||||
|
||||
_toktype = [None] + [i[1] for i in pattern_type_tuples]
|
||||
# need dummy at start because re.MatchObject.lastindex counts from 1
|
||||
|
||||
single_char_lookup = {
|
||||
'=': ExcelFormulaParser.EQ,
|
||||
'<': ExcelFormulaParser.LT,
|
||||
'>': ExcelFormulaParser.GT,
|
||||
'+': ExcelFormulaParser.ADD,
|
||||
'-': ExcelFormulaParser.SUB,
|
||||
'*': ExcelFormulaParser.MUL,
|
||||
'/': ExcelFormulaParser.DIV,
|
||||
':': ExcelFormulaParser.COLON,
|
||||
';': ExcelFormulaParser.SEMICOLON,
|
||||
',': ExcelFormulaParser.COMMA,
|
||||
'(': ExcelFormulaParser.LP,
|
||||
')': ExcelFormulaParser.RP,
|
||||
'&': ExcelFormulaParser.CONCAT,
|
||||
'%': ExcelFormulaParser.PERCENT,
|
||||
'^': ExcelFormulaParser.POWER,
|
||||
'!': ExcelFormulaParser.BANG,
|
||||
}
|
||||
|
||||
class Lexer(TokenStream):
|
||||
def __init__(self, text):
|
||||
self._text = text[:]
|
||||
self._pos = 0
|
||||
self._line = 0
|
||||
|
||||
def isEOF(self):
|
||||
return len(self._text) <= self._pos
|
||||
|
||||
def curr_ch(self):
|
||||
return self._text[self._pos]
|
||||
|
||||
def next_ch(self, n = 1):
|
||||
self._pos += n
|
||||
|
||||
def is_whitespace(self):
|
||||
return self.curr_ch() in " \t\n\r\f\v"
|
||||
|
||||
def match_pattern(self):
|
||||
m = _re.match(self._text, self._pos)
|
||||
if not m:
|
||||
return None
|
||||
self._pos = m.end(0)
|
||||
return Tok(type = _toktype[m.lastindex], text = m.group(0), col = m.start(0) + 1)
|
||||
|
||||
def nextToken(self):
|
||||
# skip whitespace
|
||||
while not self.isEOF() and self.is_whitespace():
|
||||
self.next_ch()
|
||||
if self.isEOF():
|
||||
return Tok(type = EOF)
|
||||
# first, try to match token with 2 or more chars
|
||||
t = self.match_pattern()
|
||||
if t:
|
||||
return t
|
||||
# second, we want 1-char tokens
|
||||
te = self.curr_ch()
|
||||
try:
|
||||
ty = single_char_lookup[te]
|
||||
except KeyError:
|
||||
raise TokenStreamException(
|
||||
"Unexpected char %r in column %u." % (self.curr_ch(), self._pos))
|
||||
self.next_ch()
|
||||
return Tok(type=ty, text=te, col=self._pos)
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
for t in Lexer(""" 1.23 456 "abcd" R2C2 a1 iv65536 true false if choose a_name 'qname' <> >= <= """):
|
||||
print t
|
||||
except TokenStreamException, e:
|
||||
print "error:", e
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
import sys
|
||||
from antlr import EOF, CommonToken as Tok, TokenStream, TokenStreamException
|
||||
import struct
|
||||
import ExcelFormulaParser
|
||||
from re import compile as recompile, match, LOCALE, UNICODE, IGNORECASE, VERBOSE
|
||||
|
||||
|
||||
int_const_pattern = r"\d+\b"
|
||||
flt_const_pattern = r"""
|
||||
(?:
|
||||
(?: \d* \. \d+ ) # .1 .12 .123 etc 9.1 etc 98.1 etc
|
||||
|
|
||||
(?: \d+ \. ) # 1. 12. 123. etc
|
||||
)
|
||||
# followed by optional exponent part
|
||||
(?: [Ee] [+-]? \d+ ) ?
|
||||
"""
|
||||
str_const_pattern = r'"(?:[^"]|"")*"'
|
||||
#range2d_pattern = recompile(r"\$?[A-I]?[A-Z]\$?\d+:\$?[A-I]?[A-Z]\$?\d+"
|
||||
ref2d_r1c1_pattern = r"[Rr]0*[1-9][0-9]*[Cc]0*[1-9][0-9]*"
|
||||
ref2d_pattern = r"\$?[A-I]?[A-Z]\$?0*[1-9][0-9]*"
|
||||
true_pattern = r"TRUE\b"
|
||||
false_pattern = r"FALSE\b"
|
||||
if_pattern = r"IF\b"
|
||||
choose_pattern = r"CHOOSE\b"
|
||||
name_pattern = r"\w[\.\w]*"
|
||||
quotename_pattern = r"'(?:[^']|'')*'" #### It's essential that this bracket be non-grouping.
|
||||
ne_pattern = r"<>"
|
||||
ge_pattern = r">="
|
||||
le_pattern = r"<="
|
||||
|
||||
pattern_type_tuples = (
|
||||
(flt_const_pattern, ExcelFormulaParser.NUM_CONST),
|
||||
(int_const_pattern, ExcelFormulaParser.INT_CONST),
|
||||
(str_const_pattern, ExcelFormulaParser.STR_CONST),
|
||||
# (range2d_pattern , ExcelFormulaParser.RANGE2D),
|
||||
(ref2d_r1c1_pattern, ExcelFormulaParser.REF2D_R1C1),
|
||||
(ref2d_pattern , ExcelFormulaParser.REF2D),
|
||||
(true_pattern , ExcelFormulaParser.TRUE_CONST),
|
||||
(false_pattern , ExcelFormulaParser.FALSE_CONST),
|
||||
(if_pattern , ExcelFormulaParser.FUNC_IF),
|
||||
(choose_pattern , ExcelFormulaParser.FUNC_CHOOSE),
|
||||
(name_pattern , ExcelFormulaParser.NAME),
|
||||
(quotename_pattern, ExcelFormulaParser.QUOTENAME),
|
||||
(ne_pattern, ExcelFormulaParser.NE),
|
||||
(ge_pattern, ExcelFormulaParser.GE),
|
||||
(le_pattern, ExcelFormulaParser.LE),
|
||||
)
|
||||
|
||||
_re = recompile(
|
||||
'(' + ')|('.join([i[0] for i in pattern_type_tuples]) + ')',
|
||||
VERBOSE+LOCALE+IGNORECASE)
|
||||
|
||||
_toktype = [None] + [i[1] for i in pattern_type_tuples]
|
||||
# need dummy at start because re.MatchObject.lastindex counts from 1
|
||||
|
||||
single_char_lookup = {
|
||||
'=': ExcelFormulaParser.EQ,
|
||||
'<': ExcelFormulaParser.LT,
|
||||
'>': ExcelFormulaParser.GT,
|
||||
'+': ExcelFormulaParser.ADD,
|
||||
'-': ExcelFormulaParser.SUB,
|
||||
'*': ExcelFormulaParser.MUL,
|
||||
'/': ExcelFormulaParser.DIV,
|
||||
':': ExcelFormulaParser.COLON,
|
||||
';': ExcelFormulaParser.SEMICOLON,
|
||||
',': ExcelFormulaParser.COMMA,
|
||||
'(': ExcelFormulaParser.LP,
|
||||
')': ExcelFormulaParser.RP,
|
||||
'&': ExcelFormulaParser.CONCAT,
|
||||
'%': ExcelFormulaParser.PERCENT,
|
||||
'^': ExcelFormulaParser.POWER,
|
||||
'!': ExcelFormulaParser.BANG,
|
||||
}
|
||||
|
||||
class Lexer(TokenStream):
|
||||
def __init__(self, text):
|
||||
self._text = text[:]
|
||||
self._pos = 0
|
||||
self._line = 0
|
||||
|
||||
def isEOF(self):
|
||||
return len(self._text) <= self._pos
|
||||
|
||||
def curr_ch(self):
|
||||
return self._text[self._pos]
|
||||
|
||||
def next_ch(self, n = 1):
|
||||
self._pos += n
|
||||
|
||||
def is_whitespace(self):
|
||||
return self.curr_ch() in " \t\n\r\f\v"
|
||||
|
||||
def match_pattern(self):
|
||||
m = _re.match(self._text, self._pos)
|
||||
if not m:
|
||||
return None
|
||||
self._pos = m.end(0)
|
||||
return Tok(type = _toktype[m.lastindex], text = m.group(0), col = m.start(0) + 1)
|
||||
|
||||
def nextToken(self):
|
||||
# skip whitespace
|
||||
while not self.isEOF() and self.is_whitespace():
|
||||
self.next_ch()
|
||||
if self.isEOF():
|
||||
return Tok(type = EOF)
|
||||
# first, try to match token with 2 or more chars
|
||||
t = self.match_pattern()
|
||||
if t:
|
||||
return t
|
||||
# second, we want 1-char tokens
|
||||
te = self.curr_ch()
|
||||
try:
|
||||
ty = single_char_lookup[te]
|
||||
except KeyError:
|
||||
raise TokenStreamException(
|
||||
"Unexpected char %r in column %u." % (self.curr_ch(), self._pos))
|
||||
self.next_ch()
|
||||
return Tok(type=ty, text=te, col=self._pos)
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
for t in Lexer(""" 1.23 456 "abcd" R2C2 a1 iv65536 true false if choose a_name 'qname' <> >= <= """):
|
||||
print t
|
||||
except TokenStreamException, e:
|
||||
print "error:", e
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+862
-862
File diff suppressed because it is too large
Load Diff
+261
-261
@@ -1,261 +1,261 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
The XF record is able to store explicit cell formatting attributes or the
|
||||
attributes of a cell style. Explicit formatting includes the reference to
|
||||
a cell style XF record. This allows to extend a defined cell style with
|
||||
some explicit attributes. The formatting attributes are divided into
|
||||
6 groups:
|
||||
|
||||
Group Attributes
|
||||
-------------------------------------
|
||||
Number format Number format index (index to FORMAT record)
|
||||
Font Font index (index to FONT record)
|
||||
Alignment Horizontal and vertical alignment, text wrap, indentation,
|
||||
orientation/rotation, text direction
|
||||
Border Border line styles and colours
|
||||
Background Background area style and colours
|
||||
Protection Cell locked, formula hidden
|
||||
|
||||
For each group a flag in the cell XF record specifies whether to use the
|
||||
attributes contained in that XF record or in the referenced style
|
||||
XF record. In style XF records, these flags specify whether the attributes
|
||||
will overwrite explicit cell formatting when the style is applied to
|
||||
a cell. Changing a cell style (without applying this style to a cell) will
|
||||
change all cells which already use that style and do not contain explicit
|
||||
cell attributes for the changed style attributes. If a cell XF record does
|
||||
not contain explicit attributes in a group (if the attribute group flag
|
||||
is not set), it repeats the attributes of its style XF record.
|
||||
|
||||
'''
|
||||
|
||||
import BIFFRecords
|
||||
|
||||
class Font(object):
|
||||
|
||||
ESCAPEMENT_NONE = 0x00
|
||||
ESCAPEMENT_SUPERSCRIPT = 0x01
|
||||
ESCAPEMENT_SUBSCRIPT = 0x02
|
||||
|
||||
UNDERLINE_NONE = 0x00
|
||||
UNDERLINE_SINGLE = 0x01
|
||||
UNDERLINE_SINGLE_ACC = 0x21
|
||||
UNDERLINE_DOUBLE = 0x02
|
||||
UNDERLINE_DOUBLE_ACC = 0x22
|
||||
|
||||
FAMILY_NONE = 0x00
|
||||
FAMILY_ROMAN = 0x01
|
||||
FAMILY_SWISS = 0x02
|
||||
FAMILY_MODERN = 0x03
|
||||
FAMILY_SCRIPT = 0x04
|
||||
FAMILY_DECORATIVE = 0x05
|
||||
|
||||
CHARSET_ANSI_LATIN = 0x00
|
||||
CHARSET_SYS_DEFAULT = 0x01
|
||||
CHARSET_SYMBOL = 0x02
|
||||
CHARSET_APPLE_ROMAN = 0x4D
|
||||
CHARSET_ANSI_JAP_SHIFT_JIS = 0x80
|
||||
CHARSET_ANSI_KOR_HANGUL = 0x81
|
||||
CHARSET_ANSI_KOR_JOHAB = 0x82
|
||||
CHARSET_ANSI_CHINESE_GBK = 0x86
|
||||
CHARSET_ANSI_CHINESE_BIG5 = 0x88
|
||||
CHARSET_ANSI_GREEK = 0xA1
|
||||
CHARSET_ANSI_TURKISH = 0xA2
|
||||
CHARSET_ANSI_VIETNAMESE = 0xA3
|
||||
CHARSET_ANSI_HEBREW = 0xB1
|
||||
CHARSET_ANSI_ARABIC = 0xB2
|
||||
CHARSET_ANSI_BALTIC = 0xBA
|
||||
CHARSET_ANSI_CYRILLIC = 0xCC
|
||||
CHARSET_ANSI_THAI = 0xDE
|
||||
CHARSET_ANSI_LATIN_II = 0xEE
|
||||
CHARSET_OEM_LATIN_I = 0xFF
|
||||
|
||||
def __init__(self):
|
||||
# twip = 1/20 of a point = 1/1440 of a inch
|
||||
# usually resolution == 96 pixels per 1 inch
|
||||
# (rarely 120 pixels per 1 inch or another one)
|
||||
|
||||
self.height = 0x00C8 # 200: this is font with height 10 points
|
||||
self.italic = False
|
||||
self.struck_out = False
|
||||
self.outline = False
|
||||
self.shadow = False
|
||||
self.colour_index = 0x7FFF
|
||||
self.bold = False
|
||||
self._weight = 0x0190 # 0x02BC gives bold font
|
||||
self.escapement = self.ESCAPEMENT_NONE
|
||||
self.underline = self.UNDERLINE_NONE
|
||||
self.family = self.FAMILY_NONE
|
||||
self.charset = self.CHARSET_SYS_DEFAULT
|
||||
self.name = 'Arial'
|
||||
|
||||
def get_biff_record(self):
|
||||
height = self.height
|
||||
|
||||
options = 0x00
|
||||
if self.bold:
|
||||
options |= 0x01
|
||||
self._weight = 0x02BC
|
||||
if self.italic:
|
||||
options |= 0x02
|
||||
if self.underline != self.UNDERLINE_NONE:
|
||||
options |= 0x04
|
||||
if self.struck_out:
|
||||
options |= 0x08
|
||||
if self.outline:
|
||||
options |= 0x010
|
||||
if self.shadow:
|
||||
options |= 0x020
|
||||
|
||||
colour_index = self.colour_index
|
||||
weight = self._weight
|
||||
escapement = self.escapement
|
||||
underline = self.underline
|
||||
family = self.family
|
||||
charset = self.charset
|
||||
name = self.name
|
||||
|
||||
return BIFFRecords.FontRecord(height, options, colour_index, weight, escapement,
|
||||
underline, family, charset,
|
||||
name)
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.height,
|
||||
self.italic,
|
||||
self.struck_out,
|
||||
self.outline,
|
||||
self.shadow,
|
||||
self.colour_index,
|
||||
self.bold,
|
||||
self._weight,
|
||||
self.escapement,
|
||||
self.underline,
|
||||
self.family,
|
||||
self.charset,
|
||||
self.name,
|
||||
)
|
||||
|
||||
class Alignment(object):
|
||||
HORZ_GENERAL = 0x00
|
||||
HORZ_LEFT = 0x01
|
||||
HORZ_CENTER = 0x02
|
||||
HORZ_RIGHT = 0x03
|
||||
HORZ_FILLED = 0x04
|
||||
HORZ_JUSTIFIED = 0x05 # BIFF4-BIFF8X
|
||||
HORZ_CENTER_ACROSS_SEL = 0x06 # Centred across selection (BIFF4-BIFF8X)
|
||||
HORZ_DISTRIBUTED = 0x07 # Distributed (BIFF8X)
|
||||
|
||||
VERT_TOP = 0x00
|
||||
VERT_CENTER = 0x01
|
||||
VERT_BOTTOM = 0x02
|
||||
VERT_JUSTIFIED = 0x03 # Justified (BIFF5-BIFF8X)
|
||||
VERT_DISTRIBUTED = 0x04 # Distributed (BIFF8X)
|
||||
|
||||
DIRECTION_GENERAL = 0x00 # BIFF8X
|
||||
DIRECTION_LR = 0x01
|
||||
DIRECTION_RL = 0x02
|
||||
|
||||
ORIENTATION_NOT_ROTATED = 0x00
|
||||
ORIENTATION_STACKED = 0x01
|
||||
ORIENTATION_90_CC = 0x02
|
||||
ORIENTATION_90_CW = 0x03
|
||||
|
||||
ROTATION_0_ANGLE = 0x00
|
||||
ROTATION_STACKED = 0xFF
|
||||
|
||||
WRAP_AT_RIGHT = 0x01
|
||||
NOT_WRAP_AT_RIGHT = 0x00
|
||||
|
||||
SHRINK_TO_FIT = 0x01
|
||||
NOT_SHRINK_TO_FIT = 0x00
|
||||
|
||||
def __init__(self):
|
||||
self.horz = self.HORZ_GENERAL
|
||||
self.vert = self.VERT_BOTTOM
|
||||
self.dire = self.DIRECTION_GENERAL
|
||||
self.orie = self.ORIENTATION_NOT_ROTATED
|
||||
self.rota = self.ROTATION_0_ANGLE
|
||||
self.wrap = self.NOT_WRAP_AT_RIGHT
|
||||
self.shri = self.NOT_SHRINK_TO_FIT
|
||||
self.inde = 0
|
||||
self.merg = 0
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.horz, self.vert, self.dire, self.orie, self.rota,
|
||||
self.wrap, self.shri, self.inde, self.merg,
|
||||
)
|
||||
|
||||
class Borders(object):
|
||||
NO_LINE = 0x00
|
||||
THIN = 0x01
|
||||
MEDIUM = 0x02
|
||||
DASHED = 0x03
|
||||
DOTTED = 0x04
|
||||
THICK = 0x05
|
||||
DOUBLE = 0x06
|
||||
HAIR = 0x07
|
||||
#The following for BIFF8
|
||||
MEDIUM_DASHED = 0x08
|
||||
THIN_DASH_DOTTED = 0x09
|
||||
MEDIUM_DASH_DOTTED = 0x0A
|
||||
THIN_DASH_DOT_DOTTED = 0x0B
|
||||
MEDIUM_DASH_DOT_DOTTED = 0x0C
|
||||
SLANTED_MEDIUM_DASH_DOTTED = 0x0D
|
||||
|
||||
NEED_DIAG1 = 0x01
|
||||
NEED_DIAG2 = 0x01
|
||||
NO_NEED_DIAG1 = 0x00
|
||||
NO_NEED_DIAG2 = 0x00
|
||||
|
||||
def __init__(self):
|
||||
self.left = self.NO_LINE
|
||||
self.right = self.NO_LINE
|
||||
self.top = self.NO_LINE
|
||||
self.bottom = self.NO_LINE
|
||||
self.diag = self.NO_LINE
|
||||
|
||||
self.left_colour = 0x40
|
||||
self.right_colour = 0x40
|
||||
self.top_colour = 0x40
|
||||
self.bottom_colour = 0x40
|
||||
self.diag_colour = 0x40
|
||||
|
||||
self.need_diag1 = self.NO_NEED_DIAG1
|
||||
self.need_diag2 = self.NO_NEED_DIAG2
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.left, self.right, self.top, self.bottom, self.diag,
|
||||
self.left_colour, self.right_colour, self.top_colour,
|
||||
self.bottom_colour, self.diag_colour,
|
||||
self.need_diag1, self.need_diag2,
|
||||
)
|
||||
|
||||
class Pattern(object):
|
||||
# patterns 0x00 - 0x12
|
||||
NO_PATTERN = 0x00
|
||||
SOLID_PATTERN = 0x01
|
||||
|
||||
def __init__(self):
|
||||
self.pattern = self.NO_PATTERN
|
||||
self.pattern_fore_colour = 0x40
|
||||
self.pattern_back_colour = 0x41
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.pattern,
|
||||
self.pattern_fore_colour,
|
||||
self.pattern_back_colour,
|
||||
)
|
||||
|
||||
class Protection(object):
|
||||
def __init__(self):
|
||||
self.cell_locked = 1
|
||||
self.formula_hidden = 0
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.cell_locked,
|
||||
self.formula_hidden,
|
||||
)
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
The XF record is able to store explicit cell formatting attributes or the
|
||||
attributes of a cell style. Explicit formatting includes the reference to
|
||||
a cell style XF record. This allows to extend a defined cell style with
|
||||
some explicit attributes. The formatting attributes are divided into
|
||||
6 groups:
|
||||
|
||||
Group Attributes
|
||||
-------------------------------------
|
||||
Number format Number format index (index to FORMAT record)
|
||||
Font Font index (index to FONT record)
|
||||
Alignment Horizontal and vertical alignment, text wrap, indentation,
|
||||
orientation/rotation, text direction
|
||||
Border Border line styles and colours
|
||||
Background Background area style and colours
|
||||
Protection Cell locked, formula hidden
|
||||
|
||||
For each group a flag in the cell XF record specifies whether to use the
|
||||
attributes contained in that XF record or in the referenced style
|
||||
XF record. In style XF records, these flags specify whether the attributes
|
||||
will overwrite explicit cell formatting when the style is applied to
|
||||
a cell. Changing a cell style (without applying this style to a cell) will
|
||||
change all cells which already use that style and do not contain explicit
|
||||
cell attributes for the changed style attributes. If a cell XF record does
|
||||
not contain explicit attributes in a group (if the attribute group flag
|
||||
is not set), it repeats the attributes of its style XF record.
|
||||
|
||||
'''
|
||||
|
||||
import BIFFRecords
|
||||
|
||||
class Font(object):
|
||||
|
||||
ESCAPEMENT_NONE = 0x00
|
||||
ESCAPEMENT_SUPERSCRIPT = 0x01
|
||||
ESCAPEMENT_SUBSCRIPT = 0x02
|
||||
|
||||
UNDERLINE_NONE = 0x00
|
||||
UNDERLINE_SINGLE = 0x01
|
||||
UNDERLINE_SINGLE_ACC = 0x21
|
||||
UNDERLINE_DOUBLE = 0x02
|
||||
UNDERLINE_DOUBLE_ACC = 0x22
|
||||
|
||||
FAMILY_NONE = 0x00
|
||||
FAMILY_ROMAN = 0x01
|
||||
FAMILY_SWISS = 0x02
|
||||
FAMILY_MODERN = 0x03
|
||||
FAMILY_SCRIPT = 0x04
|
||||
FAMILY_DECORATIVE = 0x05
|
||||
|
||||
CHARSET_ANSI_LATIN = 0x00
|
||||
CHARSET_SYS_DEFAULT = 0x01
|
||||
CHARSET_SYMBOL = 0x02
|
||||
CHARSET_APPLE_ROMAN = 0x4D
|
||||
CHARSET_ANSI_JAP_SHIFT_JIS = 0x80
|
||||
CHARSET_ANSI_KOR_HANGUL = 0x81
|
||||
CHARSET_ANSI_KOR_JOHAB = 0x82
|
||||
CHARSET_ANSI_CHINESE_GBK = 0x86
|
||||
CHARSET_ANSI_CHINESE_BIG5 = 0x88
|
||||
CHARSET_ANSI_GREEK = 0xA1
|
||||
CHARSET_ANSI_TURKISH = 0xA2
|
||||
CHARSET_ANSI_VIETNAMESE = 0xA3
|
||||
CHARSET_ANSI_HEBREW = 0xB1
|
||||
CHARSET_ANSI_ARABIC = 0xB2
|
||||
CHARSET_ANSI_BALTIC = 0xBA
|
||||
CHARSET_ANSI_CYRILLIC = 0xCC
|
||||
CHARSET_ANSI_THAI = 0xDE
|
||||
CHARSET_ANSI_LATIN_II = 0xEE
|
||||
CHARSET_OEM_LATIN_I = 0xFF
|
||||
|
||||
def __init__(self):
|
||||
# twip = 1/20 of a point = 1/1440 of a inch
|
||||
# usually resolution == 96 pixels per 1 inch
|
||||
# (rarely 120 pixels per 1 inch or another one)
|
||||
|
||||
self.height = 0x00C8 # 200: this is font with height 10 points
|
||||
self.italic = False
|
||||
self.struck_out = False
|
||||
self.outline = False
|
||||
self.shadow = False
|
||||
self.colour_index = 0x7FFF
|
||||
self.bold = False
|
||||
self._weight = 0x0190 # 0x02BC gives bold font
|
||||
self.escapement = self.ESCAPEMENT_NONE
|
||||
self.underline = self.UNDERLINE_NONE
|
||||
self.family = self.FAMILY_NONE
|
||||
self.charset = self.CHARSET_SYS_DEFAULT
|
||||
self.name = 'Arial'
|
||||
|
||||
def get_biff_record(self):
|
||||
height = self.height
|
||||
|
||||
options = 0x00
|
||||
if self.bold:
|
||||
options |= 0x01
|
||||
self._weight = 0x02BC
|
||||
if self.italic:
|
||||
options |= 0x02
|
||||
if self.underline != self.UNDERLINE_NONE:
|
||||
options |= 0x04
|
||||
if self.struck_out:
|
||||
options |= 0x08
|
||||
if self.outline:
|
||||
options |= 0x010
|
||||
if self.shadow:
|
||||
options |= 0x020
|
||||
|
||||
colour_index = self.colour_index
|
||||
weight = self._weight
|
||||
escapement = self.escapement
|
||||
underline = self.underline
|
||||
family = self.family
|
||||
charset = self.charset
|
||||
name = self.name
|
||||
|
||||
return BIFFRecords.FontRecord(height, options, colour_index, weight, escapement,
|
||||
underline, family, charset,
|
||||
name)
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.height,
|
||||
self.italic,
|
||||
self.struck_out,
|
||||
self.outline,
|
||||
self.shadow,
|
||||
self.colour_index,
|
||||
self.bold,
|
||||
self._weight,
|
||||
self.escapement,
|
||||
self.underline,
|
||||
self.family,
|
||||
self.charset,
|
||||
self.name,
|
||||
)
|
||||
|
||||
class Alignment(object):
|
||||
HORZ_GENERAL = 0x00
|
||||
HORZ_LEFT = 0x01
|
||||
HORZ_CENTER = 0x02
|
||||
HORZ_RIGHT = 0x03
|
||||
HORZ_FILLED = 0x04
|
||||
HORZ_JUSTIFIED = 0x05 # BIFF4-BIFF8X
|
||||
HORZ_CENTER_ACROSS_SEL = 0x06 # Centred across selection (BIFF4-BIFF8X)
|
||||
HORZ_DISTRIBUTED = 0x07 # Distributed (BIFF8X)
|
||||
|
||||
VERT_TOP = 0x00
|
||||
VERT_CENTER = 0x01
|
||||
VERT_BOTTOM = 0x02
|
||||
VERT_JUSTIFIED = 0x03 # Justified (BIFF5-BIFF8X)
|
||||
VERT_DISTRIBUTED = 0x04 # Distributed (BIFF8X)
|
||||
|
||||
DIRECTION_GENERAL = 0x00 # BIFF8X
|
||||
DIRECTION_LR = 0x01
|
||||
DIRECTION_RL = 0x02
|
||||
|
||||
ORIENTATION_NOT_ROTATED = 0x00
|
||||
ORIENTATION_STACKED = 0x01
|
||||
ORIENTATION_90_CC = 0x02
|
||||
ORIENTATION_90_CW = 0x03
|
||||
|
||||
ROTATION_0_ANGLE = 0x00
|
||||
ROTATION_STACKED = 0xFF
|
||||
|
||||
WRAP_AT_RIGHT = 0x01
|
||||
NOT_WRAP_AT_RIGHT = 0x00
|
||||
|
||||
SHRINK_TO_FIT = 0x01
|
||||
NOT_SHRINK_TO_FIT = 0x00
|
||||
|
||||
def __init__(self):
|
||||
self.horz = self.HORZ_GENERAL
|
||||
self.vert = self.VERT_BOTTOM
|
||||
self.dire = self.DIRECTION_GENERAL
|
||||
self.orie = self.ORIENTATION_NOT_ROTATED
|
||||
self.rota = self.ROTATION_0_ANGLE
|
||||
self.wrap = self.NOT_WRAP_AT_RIGHT
|
||||
self.shri = self.NOT_SHRINK_TO_FIT
|
||||
self.inde = 0
|
||||
self.merg = 0
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.horz, self.vert, self.dire, self.orie, self.rota,
|
||||
self.wrap, self.shri, self.inde, self.merg,
|
||||
)
|
||||
|
||||
class Borders(object):
|
||||
NO_LINE = 0x00
|
||||
THIN = 0x01
|
||||
MEDIUM = 0x02
|
||||
DASHED = 0x03
|
||||
DOTTED = 0x04
|
||||
THICK = 0x05
|
||||
DOUBLE = 0x06
|
||||
HAIR = 0x07
|
||||
#The following for BIFF8
|
||||
MEDIUM_DASHED = 0x08
|
||||
THIN_DASH_DOTTED = 0x09
|
||||
MEDIUM_DASH_DOTTED = 0x0A
|
||||
THIN_DASH_DOT_DOTTED = 0x0B
|
||||
MEDIUM_DASH_DOT_DOTTED = 0x0C
|
||||
SLANTED_MEDIUM_DASH_DOTTED = 0x0D
|
||||
|
||||
NEED_DIAG1 = 0x01
|
||||
NEED_DIAG2 = 0x01
|
||||
NO_NEED_DIAG1 = 0x00
|
||||
NO_NEED_DIAG2 = 0x00
|
||||
|
||||
def __init__(self):
|
||||
self.left = self.NO_LINE
|
||||
self.right = self.NO_LINE
|
||||
self.top = self.NO_LINE
|
||||
self.bottom = self.NO_LINE
|
||||
self.diag = self.NO_LINE
|
||||
|
||||
self.left_colour = 0x40
|
||||
self.right_colour = 0x40
|
||||
self.top_colour = 0x40
|
||||
self.bottom_colour = 0x40
|
||||
self.diag_colour = 0x40
|
||||
|
||||
self.need_diag1 = self.NO_NEED_DIAG1
|
||||
self.need_diag2 = self.NO_NEED_DIAG2
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.left, self.right, self.top, self.bottom, self.diag,
|
||||
self.left_colour, self.right_colour, self.top_colour,
|
||||
self.bottom_colour, self.diag_colour,
|
||||
self.need_diag1, self.need_diag2,
|
||||
)
|
||||
|
||||
class Pattern(object):
|
||||
# patterns 0x00 - 0x12
|
||||
NO_PATTERN = 0x00
|
||||
SOLID_PATTERN = 0x01
|
||||
|
||||
def __init__(self):
|
||||
self.pattern = self.NO_PATTERN
|
||||
self.pattern_fore_colour = 0x40
|
||||
self.pattern_back_colour = 0x41
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.pattern,
|
||||
self.pattern_fore_colour,
|
||||
self.pattern_back_colour,
|
||||
)
|
||||
|
||||
class Protection(object):
|
||||
def __init__(self):
|
||||
self.cell_locked = 1
|
||||
self.formula_hidden = 0
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.cell_locked,
|
||||
self.formula_hidden,
|
||||
)
|
||||
|
||||
+253
-253
@@ -1,253 +1,253 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
import BIFFRecords
|
||||
import Style
|
||||
from Cell import StrCell, BlankCell, NumberCell, FormulaCell, MulBlankCell, BooleanCell, ErrorCell, \
|
||||
_get_cells_biff_data_mul
|
||||
import ExcelFormula
|
||||
import datetime as dt
|
||||
try:
|
||||
from decimal import Decimal
|
||||
except ImportError:
|
||||
# Python 2.3: decimal not supported; create dummy Decimal class
|
||||
class Decimal(object):
|
||||
pass
|
||||
|
||||
|
||||
class Row(object):
|
||||
__slots__ = [# private variables
|
||||
"__idx",
|
||||
"__parent",
|
||||
"__parent_wb",
|
||||
"__cells",
|
||||
"__min_col_idx",
|
||||
"__max_col_idx",
|
||||
"__xf_index",
|
||||
"__has_default_xf_index",
|
||||
"__height_in_pixels",
|
||||
# public variables
|
||||
"height",
|
||||
"has_default_height",
|
||||
"height_mismatch",
|
||||
"level",
|
||||
"collapse",
|
||||
"hidden",
|
||||
"space_above",
|
||||
"space_below"]
|
||||
|
||||
def __init__(self, rowx, parent_sheet):
|
||||
if not (isinstance(rowx, int) and 0 <= rowx <= 65535):
|
||||
raise ValueError("row index (%r) not an int in range(65536)" % rowx)
|
||||
self.__idx = rowx
|
||||
self.__parent = parent_sheet
|
||||
self.__parent_wb = parent_sheet.get_parent()
|
||||
self.__cells = {}
|
||||
self.__min_col_idx = 0
|
||||
self.__max_col_idx = 0
|
||||
self.__xf_index = 0x0F
|
||||
self.__has_default_xf_index = 0
|
||||
self.__height_in_pixels = 0x11
|
||||
|
||||
self.height = 0x00FF
|
||||
self.has_default_height = 0x00
|
||||
self.height_mismatch = 0
|
||||
self.level = 0
|
||||
self.collapse = 0
|
||||
self.hidden = 0
|
||||
self.space_above = 0
|
||||
self.space_below = 0
|
||||
|
||||
|
||||
def __adjust_height(self, style):
|
||||
twips = style.font.height
|
||||
points = float(twips)/20.0
|
||||
# Cell height in pixels can be calcuted by following approx. formula:
|
||||
# cell height in pixels = font height in points * 83/50 + 2/5
|
||||
# It works when screen resolution is 96 dpi
|
||||
pix = int(round(points*83.0/50.0 + 2.0/5.0))
|
||||
if pix > self.__height_in_pixels:
|
||||
self.__height_in_pixels = pix
|
||||
|
||||
|
||||
def __adjust_bound_col_idx(self, *args):
|
||||
for arg in args:
|
||||
iarg = int(arg)
|
||||
if not ((0 <= iarg <= 255) and arg == iarg):
|
||||
raise ValueError("column index (%r) not an int in range(256)" % arg)
|
||||
sheet = self.__parent
|
||||
if iarg < self.__min_col_idx:
|
||||
self.__min_col_idx = iarg
|
||||
if iarg > self.__max_col_idx:
|
||||
self.__max_col_idx = iarg
|
||||
if iarg < sheet.first_used_col:
|
||||
sheet.first_used_col = iarg
|
||||
if iarg > sheet.last_used_col:
|
||||
sheet.last_used_col = iarg
|
||||
|
||||
def __excel_date_dt(self, date):
|
||||
if isinstance(date, dt.date) and (not isinstance(date, dt.datetime)):
|
||||
epoch = dt.date(1899, 12, 31)
|
||||
elif isinstance(date, dt.time):
|
||||
date = dt.datetime.combine(dt.datetime(1900, 1, 1), date)
|
||||
epoch = dt.datetime(1900, 1, 1, 0, 0, 0)
|
||||
else:
|
||||
epoch = dt.datetime(1899, 12, 31, 0, 0, 0)
|
||||
delta = date - epoch
|
||||
xldate = delta.days + float(delta.seconds) / (24*60*60)
|
||||
# Add a day for Excel's missing leap day in 1900
|
||||
if xldate > 59:
|
||||
xldate += 1
|
||||
return xldate
|
||||
|
||||
def get_height_in_pixels(self):
|
||||
return self.__height_in_pixels
|
||||
|
||||
|
||||
def set_style(self, style):
|
||||
self.__adjust_height(style)
|
||||
self.__xf_index = self.__parent_wb.add_style(style)
|
||||
self.__has_default_xf_index = 1
|
||||
|
||||
|
||||
def get_xf_index(self):
|
||||
return self.__xf_index
|
||||
|
||||
|
||||
def get_cells_count(self):
|
||||
return len(self.__cells)
|
||||
|
||||
|
||||
def get_min_col(self):
|
||||
return self.__min_col_idx
|
||||
|
||||
|
||||
def get_max_col(self):
|
||||
return self.__max_col_idx
|
||||
|
||||
|
||||
def get_row_biff_data(self):
|
||||
height_options = (self.height & 0x07FFF)
|
||||
height_options |= (self.has_default_height & 0x01) << 15
|
||||
|
||||
options = (self.level & 0x07) << 0
|
||||
options |= (self.collapse & 0x01) << 4
|
||||
options |= (self.hidden & 0x01) << 5
|
||||
options |= (self.height_mismatch & 0x01) << 6
|
||||
options |= (self.__has_default_xf_index & 0x01) << 7
|
||||
options |= (0x01 & 0x01) << 8
|
||||
options |= (self.__xf_index & 0x0FFF) << 16
|
||||
options |= (self.space_above & 1) << 28
|
||||
options |= (self.space_below & 1) << 29
|
||||
|
||||
return BIFFRecords.RowRecord(self.__idx, self.__min_col_idx,
|
||||
self.__max_col_idx, height_options, options).get()
|
||||
|
||||
def insert_cell(self, col_index, cell_obj):
|
||||
if col_index in self.__cells:
|
||||
if not self.__parent._cell_overwrite_ok:
|
||||
msg = "Attempt to overwrite cell: sheetname=%r rowx=%d colx=%d" \
|
||||
% (self.__parent.name, self.__idx, col_index)
|
||||
raise Exception(msg)
|
||||
prev_cell_obj = self.__cells[col_index]
|
||||
sst_idx = getattr(prev_cell_obj, 'sst_idx', None)
|
||||
if sst_idx is not None:
|
||||
self.__parent_wb.del_str(sst_idx)
|
||||
self.__cells[col_index] = cell_obj
|
||||
|
||||
def insert_mulcells(self, colx1, colx2, cell_obj):
|
||||
self.insert_cell(colx1, cell_obj)
|
||||
for col_index in xrange(colx1+1, colx2+1):
|
||||
self.insert_cell(col_index, None)
|
||||
|
||||
def get_cells_biff_data(self):
|
||||
cell_items = [item for item in self.__cells.iteritems() if item[1] is not None]
|
||||
cell_items.sort() # in column order
|
||||
return _get_cells_biff_data_mul(self.__idx, cell_items)
|
||||
# previously:
|
||||
# return ''.join([cell.get_biff_data() for colx, cell in cell_items])
|
||||
|
||||
def get_index(self):
|
||||
return self.__idx
|
||||
|
||||
def set_cell_text(self, colx, value, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, StrCell(self.__idx, colx, xf_index, self.__parent_wb.add_str(value)))
|
||||
|
||||
def set_cell_blank(self, colx, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, BlankCell(self.__idx, colx, xf_index))
|
||||
|
||||
def set_cell_mulblanks(self, first_colx, last_colx, style=Style.default_style):
|
||||
assert 0 <= first_colx <= last_colx <= 255
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(first_colx, last_colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
# ncols = last_colx - first_colx + 1
|
||||
self.insert_mulcells(first_colx, last_colx, MulBlankCell(self.__idx, first_colx, last_colx, xf_index))
|
||||
|
||||
def set_cell_number(self, colx, number, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, NumberCell(self.__idx, colx, xf_index, number))
|
||||
|
||||
def set_cell_date(self, colx, datetime_obj, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx,
|
||||
NumberCell(self.__idx, colx, xf_index, self.__excel_date_dt(datetime_obj)))
|
||||
|
||||
def set_cell_formula(self, colx, formula, style=Style.default_style, calc_flags=0):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.__parent_wb.add_sheet_reference(formula)
|
||||
self.insert_cell(colx, FormulaCell(self.__idx, colx, xf_index, formula, calc_flags=0))
|
||||
|
||||
def set_cell_boolean(self, colx, value, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, BooleanCell(self.__idx, colx, xf_index, bool(value)))
|
||||
|
||||
def set_cell_error(self, colx, error_string_or_code, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, ErrorCell(self.__idx, colx, xf_index, error_string_or_code))
|
||||
|
||||
def write(self, col, label, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(col)
|
||||
style_index = self.__parent_wb.add_style(style)
|
||||
if isinstance(label, basestring):
|
||||
if len(label) > 0:
|
||||
self.insert_cell(col,
|
||||
StrCell(self.__idx, col, style_index, self.__parent_wb.add_str(label))
|
||||
)
|
||||
else:
|
||||
self.insert_cell(col, BlankCell(self.__idx, col, style_index))
|
||||
elif isinstance(label, bool): # bool is subclass of int; test bool first
|
||||
self.insert_cell(col, BooleanCell(self.__idx, col, style_index, label))
|
||||
elif isinstance(label, (float, int, long, Decimal)):
|
||||
self.insert_cell(col, NumberCell(self.__idx, col, style_index, label))
|
||||
elif isinstance(label, (dt.datetime, dt.date, dt.time)):
|
||||
date_number = self.__excel_date_dt(label)
|
||||
self.insert_cell(col, NumberCell(self.__idx, col, style_index, date_number))
|
||||
elif label is None:
|
||||
self.insert_cell(col, BlankCell(self.__idx, col, style_index))
|
||||
elif isinstance(label, ExcelFormula.Formula):
|
||||
self.__parent_wb.add_sheet_reference(label)
|
||||
self.insert_cell(col, FormulaCell(self.__idx, col, style_index, label))
|
||||
else:
|
||||
raise Exception("Unexpected data type %r" % type(label))
|
||||
|
||||
write_blanks = set_cell_mulblanks
|
||||
|
||||
|
||||
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
import BIFFRecords
|
||||
import Style
|
||||
from Cell import StrCell, BlankCell, NumberCell, FormulaCell, MulBlankCell, BooleanCell, ErrorCell, \
|
||||
_get_cells_biff_data_mul
|
||||
import ExcelFormula
|
||||
import datetime as dt
|
||||
try:
|
||||
from decimal import Decimal
|
||||
except ImportError:
|
||||
# Python 2.3: decimal not supported; create dummy Decimal class
|
||||
class Decimal(object):
|
||||
pass
|
||||
|
||||
|
||||
class Row(object):
|
||||
__slots__ = [# private variables
|
||||
"__idx",
|
||||
"__parent",
|
||||
"__parent_wb",
|
||||
"__cells",
|
||||
"__min_col_idx",
|
||||
"__max_col_idx",
|
||||
"__xf_index",
|
||||
"__has_default_xf_index",
|
||||
"__height_in_pixels",
|
||||
# public variables
|
||||
"height",
|
||||
"has_default_height",
|
||||
"height_mismatch",
|
||||
"level",
|
||||
"collapse",
|
||||
"hidden",
|
||||
"space_above",
|
||||
"space_below"]
|
||||
|
||||
def __init__(self, rowx, parent_sheet):
|
||||
if not (isinstance(rowx, int) and 0 <= rowx <= 65535):
|
||||
raise ValueError("row index (%r) not an int in range(65536)" % rowx)
|
||||
self.__idx = rowx
|
||||
self.__parent = parent_sheet
|
||||
self.__parent_wb = parent_sheet.get_parent()
|
||||
self.__cells = {}
|
||||
self.__min_col_idx = 0
|
||||
self.__max_col_idx = 0
|
||||
self.__xf_index = 0x0F
|
||||
self.__has_default_xf_index = 0
|
||||
self.__height_in_pixels = 0x11
|
||||
|
||||
self.height = 0x00FF
|
||||
self.has_default_height = 0x00
|
||||
self.height_mismatch = 0
|
||||
self.level = 0
|
||||
self.collapse = 0
|
||||
self.hidden = 0
|
||||
self.space_above = 0
|
||||
self.space_below = 0
|
||||
|
||||
|
||||
def __adjust_height(self, style):
|
||||
twips = style.font.height
|
||||
points = float(twips)/20.0
|
||||
# Cell height in pixels can be calcuted by following approx. formula:
|
||||
# cell height in pixels = font height in points * 83/50 + 2/5
|
||||
# It works when screen resolution is 96 dpi
|
||||
pix = int(round(points*83.0/50.0 + 2.0/5.0))
|
||||
if pix > self.__height_in_pixels:
|
||||
self.__height_in_pixels = pix
|
||||
|
||||
|
||||
def __adjust_bound_col_idx(self, *args):
|
||||
for arg in args:
|
||||
iarg = int(arg)
|
||||
if not ((0 <= iarg <= 255) and arg == iarg):
|
||||
raise ValueError("column index (%r) not an int in range(256)" % arg)
|
||||
sheet = self.__parent
|
||||
if iarg < self.__min_col_idx:
|
||||
self.__min_col_idx = iarg
|
||||
if iarg > self.__max_col_idx:
|
||||
self.__max_col_idx = iarg
|
||||
if iarg < sheet.first_used_col:
|
||||
sheet.first_used_col = iarg
|
||||
if iarg > sheet.last_used_col:
|
||||
sheet.last_used_col = iarg
|
||||
|
||||
def __excel_date_dt(self, date):
|
||||
if isinstance(date, dt.date) and (not isinstance(date, dt.datetime)):
|
||||
epoch = dt.date(1899, 12, 31)
|
||||
elif isinstance(date, dt.time):
|
||||
date = dt.datetime.combine(dt.datetime(1900, 1, 1), date)
|
||||
epoch = dt.datetime(1900, 1, 1, 0, 0, 0)
|
||||
else:
|
||||
epoch = dt.datetime(1899, 12, 31, 0, 0, 0)
|
||||
delta = date - epoch
|
||||
xldate = delta.days + float(delta.seconds) / (24*60*60)
|
||||
# Add a day for Excel's missing leap day in 1900
|
||||
if xldate > 59:
|
||||
xldate += 1
|
||||
return xldate
|
||||
|
||||
def get_height_in_pixels(self):
|
||||
return self.__height_in_pixels
|
||||
|
||||
|
||||
def set_style(self, style):
|
||||
self.__adjust_height(style)
|
||||
self.__xf_index = self.__parent_wb.add_style(style)
|
||||
self.__has_default_xf_index = 1
|
||||
|
||||
|
||||
def get_xf_index(self):
|
||||
return self.__xf_index
|
||||
|
||||
|
||||
def get_cells_count(self):
|
||||
return len(self.__cells)
|
||||
|
||||
|
||||
def get_min_col(self):
|
||||
return self.__min_col_idx
|
||||
|
||||
|
||||
def get_max_col(self):
|
||||
return self.__max_col_idx
|
||||
|
||||
|
||||
def get_row_biff_data(self):
|
||||
height_options = (self.height & 0x07FFF)
|
||||
height_options |= (self.has_default_height & 0x01) << 15
|
||||
|
||||
options = (self.level & 0x07) << 0
|
||||
options |= (self.collapse & 0x01) << 4
|
||||
options |= (self.hidden & 0x01) << 5
|
||||
options |= (self.height_mismatch & 0x01) << 6
|
||||
options |= (self.__has_default_xf_index & 0x01) << 7
|
||||
options |= (0x01 & 0x01) << 8
|
||||
options |= (self.__xf_index & 0x0FFF) << 16
|
||||
options |= (self.space_above & 1) << 28
|
||||
options |= (self.space_below & 1) << 29
|
||||
|
||||
return BIFFRecords.RowRecord(self.__idx, self.__min_col_idx,
|
||||
self.__max_col_idx, height_options, options).get()
|
||||
|
||||
def insert_cell(self, col_index, cell_obj):
|
||||
if col_index in self.__cells:
|
||||
if not self.__parent._cell_overwrite_ok:
|
||||
msg = "Attempt to overwrite cell: sheetname=%r rowx=%d colx=%d" \
|
||||
% (self.__parent.name, self.__idx, col_index)
|
||||
raise Exception(msg)
|
||||
prev_cell_obj = self.__cells[col_index]
|
||||
sst_idx = getattr(prev_cell_obj, 'sst_idx', None)
|
||||
if sst_idx is not None:
|
||||
self.__parent_wb.del_str(sst_idx)
|
||||
self.__cells[col_index] = cell_obj
|
||||
|
||||
def insert_mulcells(self, colx1, colx2, cell_obj):
|
||||
self.insert_cell(colx1, cell_obj)
|
||||
for col_index in xrange(colx1+1, colx2+1):
|
||||
self.insert_cell(col_index, None)
|
||||
|
||||
def get_cells_biff_data(self):
|
||||
cell_items = [item for item in self.__cells.iteritems() if item[1] is not None]
|
||||
cell_items.sort() # in column order
|
||||
return _get_cells_biff_data_mul(self.__idx, cell_items)
|
||||
# previously:
|
||||
# return ''.join([cell.get_biff_data() for colx, cell in cell_items])
|
||||
|
||||
def get_index(self):
|
||||
return self.__idx
|
||||
|
||||
def set_cell_text(self, colx, value, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, StrCell(self.__idx, colx, xf_index, self.__parent_wb.add_str(value)))
|
||||
|
||||
def set_cell_blank(self, colx, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, BlankCell(self.__idx, colx, xf_index))
|
||||
|
||||
def set_cell_mulblanks(self, first_colx, last_colx, style=Style.default_style):
|
||||
assert 0 <= first_colx <= last_colx <= 255
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(first_colx, last_colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
# ncols = last_colx - first_colx + 1
|
||||
self.insert_mulcells(first_colx, last_colx, MulBlankCell(self.__idx, first_colx, last_colx, xf_index))
|
||||
|
||||
def set_cell_number(self, colx, number, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, NumberCell(self.__idx, colx, xf_index, number))
|
||||
|
||||
def set_cell_date(self, colx, datetime_obj, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx,
|
||||
NumberCell(self.__idx, colx, xf_index, self.__excel_date_dt(datetime_obj)))
|
||||
|
||||
def set_cell_formula(self, colx, formula, style=Style.default_style, calc_flags=0):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.__parent_wb.add_sheet_reference(formula)
|
||||
self.insert_cell(colx, FormulaCell(self.__idx, colx, xf_index, formula, calc_flags=0))
|
||||
|
||||
def set_cell_boolean(self, colx, value, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, BooleanCell(self.__idx, colx, xf_index, bool(value)))
|
||||
|
||||
def set_cell_error(self, colx, error_string_or_code, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, ErrorCell(self.__idx, colx, xf_index, error_string_or_code))
|
||||
|
||||
def write(self, col, label, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(col)
|
||||
style_index = self.__parent_wb.add_style(style)
|
||||
if isinstance(label, basestring):
|
||||
if len(label) > 0:
|
||||
self.insert_cell(col,
|
||||
StrCell(self.__idx, col, style_index, self.__parent_wb.add_str(label))
|
||||
)
|
||||
else:
|
||||
self.insert_cell(col, BlankCell(self.__idx, col, style_index))
|
||||
elif isinstance(label, bool): # bool is subclass of int; test bool first
|
||||
self.insert_cell(col, BooleanCell(self.__idx, col, style_index, label))
|
||||
elif isinstance(label, (float, int, long, Decimal)):
|
||||
self.insert_cell(col, NumberCell(self.__idx, col, style_index, label))
|
||||
elif isinstance(label, (dt.datetime, dt.date, dt.time)):
|
||||
date_number = self.__excel_date_dt(label)
|
||||
self.insert_cell(col, NumberCell(self.__idx, col, style_index, date_number))
|
||||
elif label is None:
|
||||
self.insert_cell(col, BlankCell(self.__idx, col, style_index))
|
||||
elif isinstance(label, ExcelFormula.Formula):
|
||||
self.__parent_wb.add_sheet_reference(label)
|
||||
self.insert_cell(col, FormulaCell(self.__idx, col, style_index, label))
|
||||
else:
|
||||
raise Exception("Unexpected data type %r" % type(label))
|
||||
|
||||
write_blanks = set_cell_mulblanks
|
||||
|
||||
|
||||
|
||||
|
||||
+592
-592
File diff suppressed because it is too large
Load Diff
@@ -1,81 +1,81 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
'''
|
||||
From BIFF8 on, strings are always stored using UTF-16LE text encoding. The
|
||||
character array is a sequence of 16-bit values4. Additionally it is
|
||||
possible to use a compressed format, which omits the high bytes of all
|
||||
characters, if they are all zero.
|
||||
|
||||
The following tables describe the standard format of the entire string, but
|
||||
in many records the strings differ from this format. This will be mentioned
|
||||
separately. It is possible (but not required) to store Rich-Text formatting
|
||||
information and Asian phonetic information inside a Unicode string. This
|
||||
results in four different ways to store a string. The character array
|
||||
is not zero-terminated.
|
||||
|
||||
The string consists of the character count (as usual an 8-bit value or
|
||||
a 16-bit value), option flags, the character array and optional formatting
|
||||
information. If the string is empty, sometimes the option flags field will
|
||||
not occur. This is mentioned at the respective place.
|
||||
|
||||
Offset Size Contents
|
||||
0 1 or 2 Length of the string (character count, ln)
|
||||
1 or 2 1 Option flags:
|
||||
Bit Mask Contents
|
||||
0 01H Character compression (ccompr):
|
||||
0 = Compressed (8-bit characters)
|
||||
1 = Uncompressed (16-bit characters)
|
||||
2 04H Asian phonetic settings (phonetic):
|
||||
0 = Does not contain Asian phonetic settings
|
||||
1 = Contains Asian phonetic settings
|
||||
3 08H Rich-Text settings (richtext):
|
||||
0 = Does not contain Rich-Text settings
|
||||
1 = Contains Rich-Text settings
|
||||
[2 or 3] 2 (optional, only if richtext=1) Number of Rich-Text formatting runs (rt)
|
||||
[var.] 4 (optional, only if phonetic=1) Size of Asian phonetic settings block (in bytes, sz)
|
||||
var. ln or
|
||||
2·ln Character array (8-bit characters or 16-bit characters, dependent on ccompr)
|
||||
[var.] 4·rt (optional, only if richtext=1) List of rt formatting runs
|
||||
[var.] sz (optional, only if phonetic=1) Asian Phonetic Settings Block
|
||||
'''
|
||||
|
||||
|
||||
from struct import pack
|
||||
|
||||
def upack2(s, encoding='ascii'):
|
||||
# If not unicode, make it so.
|
||||
if isinstance(s, unicode):
|
||||
us = s
|
||||
else:
|
||||
us = unicode(s, encoding)
|
||||
# Limit is based on number of content characters
|
||||
# (not on number of bytes in packed result)
|
||||
len_us = len(us)
|
||||
if len_us > 65535:
|
||||
raise Exception('String longer than 65535 characters')
|
||||
try:
|
||||
encs = us.encode('latin1')
|
||||
# Success here means all chars are in U+0000 to U+00FF
|
||||
# inclusive, meaning that we can use "compressed format".
|
||||
flag = 0
|
||||
except UnicodeEncodeError:
|
||||
encs = us.encode('utf_16_le')
|
||||
flag = 1
|
||||
return pack('<HB', len_us, flag) + encs
|
||||
|
||||
def upack1(s, encoding='ascii'):
|
||||
# Same as upack2(), but with a one-byte length field.
|
||||
if isinstance(s, unicode):
|
||||
us = s
|
||||
else:
|
||||
us = unicode(s, encoding)
|
||||
len_us = len(us)
|
||||
if len_us > 255:
|
||||
raise Exception('String longer than 255 characters')
|
||||
try:
|
||||
encs = us.encode('latin1')
|
||||
flag = 0
|
||||
except UnicodeEncodeError:
|
||||
encs = us.encode('utf_16_le')
|
||||
flag = 1
|
||||
return pack('<BB', len_us, flag) + encs
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
'''
|
||||
From BIFF8 on, strings are always stored using UTF-16LE text encoding. The
|
||||
character array is a sequence of 16-bit values4. Additionally it is
|
||||
possible to use a compressed format, which omits the high bytes of all
|
||||
characters, if they are all zero.
|
||||
|
||||
The following tables describe the standard format of the entire string, but
|
||||
in many records the strings differ from this format. This will be mentioned
|
||||
separately. It is possible (but not required) to store Rich-Text formatting
|
||||
information and Asian phonetic information inside a Unicode string. This
|
||||
results in four different ways to store a string. The character array
|
||||
is not zero-terminated.
|
||||
|
||||
The string consists of the character count (as usual an 8-bit value or
|
||||
a 16-bit value), option flags, the character array and optional formatting
|
||||
information. If the string is empty, sometimes the option flags field will
|
||||
not occur. This is mentioned at the respective place.
|
||||
|
||||
Offset Size Contents
|
||||
0 1 or 2 Length of the string (character count, ln)
|
||||
1 or 2 1 Option flags:
|
||||
Bit Mask Contents
|
||||
0 01H Character compression (ccompr):
|
||||
0 = Compressed (8-bit characters)
|
||||
1 = Uncompressed (16-bit characters)
|
||||
2 04H Asian phonetic settings (phonetic):
|
||||
0 = Does not contain Asian phonetic settings
|
||||
1 = Contains Asian phonetic settings
|
||||
3 08H Rich-Text settings (richtext):
|
||||
0 = Does not contain Rich-Text settings
|
||||
1 = Contains Rich-Text settings
|
||||
[2 or 3] 2 (optional, only if richtext=1) Number of Rich-Text formatting runs (rt)
|
||||
[var.] 4 (optional, only if phonetic=1) Size of Asian phonetic settings block (in bytes, sz)
|
||||
var. ln or
|
||||
2·ln Character array (8-bit characters or 16-bit characters, dependent on ccompr)
|
||||
[var.] 4·rt (optional, only if richtext=1) List of rt formatting runs
|
||||
[var.] sz (optional, only if phonetic=1) Asian Phonetic Settings Block
|
||||
'''
|
||||
|
||||
|
||||
from struct import pack
|
||||
|
||||
def upack2(s, encoding='ascii'):
|
||||
# If not unicode, make it so.
|
||||
if isinstance(s, unicode):
|
||||
us = s
|
||||
else:
|
||||
us = unicode(s, encoding)
|
||||
# Limit is based on number of content characters
|
||||
# (not on number of bytes in packed result)
|
||||
len_us = len(us)
|
||||
if len_us > 65535:
|
||||
raise Exception('String longer than 65535 characters')
|
||||
try:
|
||||
encs = us.encode('latin1')
|
||||
# Success here means all chars are in U+0000 to U+00FF
|
||||
# inclusive, meaning that we can use "compressed format".
|
||||
flag = 0
|
||||
except UnicodeEncodeError:
|
||||
encs = us.encode('utf_16_le')
|
||||
flag = 1
|
||||
return pack('<HB', len_us, flag) + encs
|
||||
|
||||
def upack1(s, encoding='ascii'):
|
||||
# Same as upack2(), but with a one-byte length field.
|
||||
if isinstance(s, unicode):
|
||||
us = s
|
||||
else:
|
||||
us = unicode(s, encoding)
|
||||
len_us = len(us)
|
||||
if len_us > 255:
|
||||
raise Exception('String longer than 255 characters')
|
||||
try:
|
||||
encs = us.encode('latin1')
|
||||
flag = 0
|
||||
except UnicodeEncodeError:
|
||||
encs = us.encode('utf_16_le')
|
||||
flag = 1
|
||||
return pack('<BB', len_us, flag) + encs
|
||||
|
||||
+196
-196
@@ -1,196 +1,196 @@
|
||||
# pyXLWriter: A library for generating Excel Spreadsheets
|
||||
# Copyright (c) 2004 Evgeny Filatov <fufff@users.sourceforge.net>
|
||||
# Copyright (c) 2002-2004 John McNamara (Perl Spreadsheet::WriteExcel)
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this library; if not, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#----------------------------------------------------------------------------
|
||||
# This module was written/ported from PERL Spreadsheet::WriteExcel module
|
||||
# The author of the PERL Spreadsheet::WriteExcel module is John McNamara
|
||||
# <jmcnamara@cpan.org>
|
||||
#----------------------------------------------------------------------------
|
||||
# See the README.txt distributed with pyXLWriter for more details.
|
||||
|
||||
# Portions are (C) Roman V. Kiseliov, 2005
|
||||
|
||||
|
||||
# Utilities for work with reference to cells and with sheetnames
|
||||
|
||||
|
||||
__rev_id__ = """$Id: Utils.py 3844 2009-05-20 01:02:54Z sjmachin $"""
|
||||
|
||||
import re
|
||||
from struct import pack
|
||||
from ExcelMagic import MAX_ROW, MAX_COL
|
||||
|
||||
|
||||
_re_cell_ex = re.compile(r"(\$?)([A-I]?[A-Z])(\$?)(\d+)", re.IGNORECASE)
|
||||
_re_row_range = re.compile(r"\$?(\d+):\$?(\d+)")
|
||||
_re_col_range = re.compile(r"\$?([A-I]?[A-Z]):\$?([A-I]?[A-Z])", re.IGNORECASE)
|
||||
_re_cell_range = re.compile(r"\$?([A-I]?[A-Z]\$?\d+):\$?([A-I]?[A-Z]\$?\d+)", re.IGNORECASE)
|
||||
_re_cell_ref = re.compile(r"\$?([A-I]?[A-Z]\$?\d+)", re.IGNORECASE)
|
||||
|
||||
|
||||
def col_by_name(colname):
|
||||
"""
|
||||
"""
|
||||
col = 0
|
||||
pow = 1
|
||||
for i in xrange(len(colname)-1, -1, -1):
|
||||
ch = colname[i]
|
||||
col += (ord(ch) - ord('A') + 1) * pow
|
||||
pow *= 26
|
||||
return col - 1
|
||||
|
||||
|
||||
def cell_to_rowcol(cell):
|
||||
"""Convert an Excel cell reference string in A1 notation
|
||||
to numeric row/col notation.
|
||||
|
||||
Returns: row, col, row_abs, col_abs
|
||||
|
||||
"""
|
||||
m = _re_cell_ex.match(cell)
|
||||
if not m:
|
||||
raise Exception("Ill-formed single_cell reference: %s" % cell)
|
||||
col_abs, col, row_abs, row = m.groups()
|
||||
row_abs = bool(row_abs)
|
||||
col_abs = bool(col_abs)
|
||||
row = int(row) - 1
|
||||
col = col_by_name(col.upper())
|
||||
return row, col, row_abs, col_abs
|
||||
|
||||
|
||||
def cell_to_rowcol2(cell):
|
||||
"""Convert an Excel cell reference string in A1 notation
|
||||
to numeric row/col notation.
|
||||
|
||||
Returns: row, col
|
||||
|
||||
"""
|
||||
m = _re_cell_ex.match(cell)
|
||||
if not m:
|
||||
raise Exception("Error in cell format")
|
||||
col_abs, col, row_abs, row = m.groups()
|
||||
# Convert base26 column string to number
|
||||
# All your Base are belong to us.
|
||||
row = int(row) - 1
|
||||
col = col_by_name(col.upper())
|
||||
return row, col
|
||||
|
||||
|
||||
def rowcol_to_cell(row, col, row_abs=False, col_abs=False):
|
||||
"""Convert numeric row/col notation to an Excel cell reference string in
|
||||
A1 notation.
|
||||
|
||||
"""
|
||||
assert 0 <= row < MAX_ROW # MAX_ROW counts from 1
|
||||
assert 0 <= col < MAX_COL # MAX_COL counts from 1
|
||||
d = col // 26
|
||||
m = col % 26
|
||||
chr1 = "" # Most significant character in AA1
|
||||
if row_abs:
|
||||
row_abs = '$'
|
||||
else:
|
||||
row_abs = ''
|
||||
if col_abs:
|
||||
col_abs = '$'
|
||||
else:
|
||||
col_abs = ''
|
||||
if d > 0:
|
||||
chr1 = chr(ord('A') + d - 1)
|
||||
chr2 = chr(ord('A') + m)
|
||||
# Zero index to 1-index
|
||||
return col_abs + chr1 + chr2 + row_abs + str(row + 1)
|
||||
|
||||
def rowcol_pair_to_cellrange(row1, col1, row2, col2,
|
||||
row1_abs=False, col1_abs=False, row2_abs=False, col2_abs=False):
|
||||
"""Convert two (row,column) pairs
|
||||
into a cell range string in A1:B2 notation.
|
||||
|
||||
Returns: cell range string
|
||||
"""
|
||||
assert row1 <= row2
|
||||
assert col1 <= col2
|
||||
return (
|
||||
rowcol_to_cell(row1, col1, row1_abs, col1_abs)
|
||||
+ ":"
|
||||
+ rowcol_to_cell(row2, col2, row2_abs, col2_abs)
|
||||
)
|
||||
|
||||
def cellrange_to_rowcol_pair(cellrange):
|
||||
"""Convert cell range string in A1 notation to numeric row/col
|
||||
pair.
|
||||
|
||||
Returns: row1, col1, row2, col2
|
||||
|
||||
"""
|
||||
cellrange = cellrange.upper()
|
||||
# Convert a row range: '1:3'
|
||||
res = _re_row_range.match(cellrange)
|
||||
if res:
|
||||
row1 = int(res.group(1)) - 1
|
||||
col1 = 0
|
||||
row2 = int(res.group(2)) - 1
|
||||
col2 = -1
|
||||
return row1, col1, row2, col2
|
||||
# Convert a column range: 'A:A' or 'B:G'.
|
||||
# A range such as A:A is equivalent to A1:A16384, so add rows as required
|
||||
res = _re_col_range.match(cellrange)
|
||||
if res:
|
||||
col1 = col_by_name(res.group(1).upper())
|
||||
row1 = 0
|
||||
col2 = col_by_name(res.group(2).upper())
|
||||
row2 = -1
|
||||
return row1, col1, row2, col2
|
||||
# Convert a cell range: 'A1:B7'
|
||||
res = _re_cell_range.match(cellrange)
|
||||
if res:
|
||||
row1, col1 = cell_to_rowcol2(res.group(1))
|
||||
row2, col2 = cell_to_rowcol2(res.group(2))
|
||||
return row1, col1, row2, col2
|
||||
# Convert a cell reference: 'A1' or 'AD2000'
|
||||
res = _re_cell_ref.match(cellrange)
|
||||
if res:
|
||||
row1, col1 = cell_to_rowcol2(res.group(1))
|
||||
return row1, col1, row1, col1
|
||||
raise Exception("Unknown cell reference %s" % (cell))
|
||||
|
||||
|
||||
def cell_to_packed_rowcol(cell):
|
||||
""" pack row and column into the required 4 byte format """
|
||||
row, col, row_abs, col_abs = cell_to_rowcol(cell)
|
||||
if col >= MAX_COL:
|
||||
raise Exception("Column %s greater than IV in formula" % cell)
|
||||
if row >= MAX_ROW: # this for BIFF8. for BIFF7 available 2^14
|
||||
raise Exception("Row %s greater than %d in formula" % (cell, MAX_ROW))
|
||||
col |= int(not row_abs) << 15
|
||||
col |= int(not col_abs) << 14
|
||||
return row, col
|
||||
|
||||
# === sheetname functions ===
|
||||
|
||||
def valid_sheet_name(sheet_name):
|
||||
if sheet_name == u"" or sheet_name[0] == u"'" or len(sheet_name) > 31:
|
||||
return False
|
||||
for c in sheet_name:
|
||||
if c in u"[]:\\?/*\x00":
|
||||
return False
|
||||
return True
|
||||
|
||||
def quote_sheet_name(unquoted_sheet_name):
|
||||
if not valid_sheet_name(unquoted_sheet_name):
|
||||
raise Exception(
|
||||
'attempt to quote an invalid worksheet name %r' % unquoted_sheet_name)
|
||||
return u"'" + unquoted_sheet_name.replace(u"'", u"''") + u"'"
|
||||
# pyXLWriter: A library for generating Excel Spreadsheets
|
||||
# Copyright (c) 2004 Evgeny Filatov <fufff@users.sourceforge.net>
|
||||
# Copyright (c) 2002-2004 John McNamara (Perl Spreadsheet::WriteExcel)
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this library; if not, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#----------------------------------------------------------------------------
|
||||
# This module was written/ported from PERL Spreadsheet::WriteExcel module
|
||||
# The author of the PERL Spreadsheet::WriteExcel module is John McNamara
|
||||
# <jmcnamara@cpan.org>
|
||||
#----------------------------------------------------------------------------
|
||||
# See the README.txt distributed with pyXLWriter for more details.
|
||||
|
||||
# Portions are (C) Roman V. Kiseliov, 2005
|
||||
|
||||
|
||||
# Utilities for work with reference to cells and with sheetnames
|
||||
|
||||
|
||||
__rev_id__ = """$Id: Utils.py 3844 2009-05-20 01:02:54Z sjmachin $"""
|
||||
|
||||
import re
|
||||
from struct import pack
|
||||
from ExcelMagic import MAX_ROW, MAX_COL
|
||||
|
||||
|
||||
_re_cell_ex = re.compile(r"(\$?)([A-I]?[A-Z])(\$?)(\d+)", re.IGNORECASE)
|
||||
_re_row_range = re.compile(r"\$?(\d+):\$?(\d+)")
|
||||
_re_col_range = re.compile(r"\$?([A-I]?[A-Z]):\$?([A-I]?[A-Z])", re.IGNORECASE)
|
||||
_re_cell_range = re.compile(r"\$?([A-I]?[A-Z]\$?\d+):\$?([A-I]?[A-Z]\$?\d+)", re.IGNORECASE)
|
||||
_re_cell_ref = re.compile(r"\$?([A-I]?[A-Z]\$?\d+)", re.IGNORECASE)
|
||||
|
||||
|
||||
def col_by_name(colname):
|
||||
"""
|
||||
"""
|
||||
col = 0
|
||||
pow = 1
|
||||
for i in xrange(len(colname)-1, -1, -1):
|
||||
ch = colname[i]
|
||||
col += (ord(ch) - ord('A') + 1) * pow
|
||||
pow *= 26
|
||||
return col - 1
|
||||
|
||||
|
||||
def cell_to_rowcol(cell):
|
||||
"""Convert an Excel cell reference string in A1 notation
|
||||
to numeric row/col notation.
|
||||
|
||||
Returns: row, col, row_abs, col_abs
|
||||
|
||||
"""
|
||||
m = _re_cell_ex.match(cell)
|
||||
if not m:
|
||||
raise Exception("Ill-formed single_cell reference: %s" % cell)
|
||||
col_abs, col, row_abs, row = m.groups()
|
||||
row_abs = bool(row_abs)
|
||||
col_abs = bool(col_abs)
|
||||
row = int(row) - 1
|
||||
col = col_by_name(col.upper())
|
||||
return row, col, row_abs, col_abs
|
||||
|
||||
|
||||
def cell_to_rowcol2(cell):
|
||||
"""Convert an Excel cell reference string in A1 notation
|
||||
to numeric row/col notation.
|
||||
|
||||
Returns: row, col
|
||||
|
||||
"""
|
||||
m = _re_cell_ex.match(cell)
|
||||
if not m:
|
||||
raise Exception("Error in cell format")
|
||||
col_abs, col, row_abs, row = m.groups()
|
||||
# Convert base26 column string to number
|
||||
# All your Base are belong to us.
|
||||
row = int(row) - 1
|
||||
col = col_by_name(col.upper())
|
||||
return row, col
|
||||
|
||||
|
||||
def rowcol_to_cell(row, col, row_abs=False, col_abs=False):
|
||||
"""Convert numeric row/col notation to an Excel cell reference string in
|
||||
A1 notation.
|
||||
|
||||
"""
|
||||
assert 0 <= row < MAX_ROW # MAX_ROW counts from 1
|
||||
assert 0 <= col < MAX_COL # MAX_COL counts from 1
|
||||
d = col // 26
|
||||
m = col % 26
|
||||
chr1 = "" # Most significant character in AA1
|
||||
if row_abs:
|
||||
row_abs = '$'
|
||||
else:
|
||||
row_abs = ''
|
||||
if col_abs:
|
||||
col_abs = '$'
|
||||
else:
|
||||
col_abs = ''
|
||||
if d > 0:
|
||||
chr1 = chr(ord('A') + d - 1)
|
||||
chr2 = chr(ord('A') + m)
|
||||
# Zero index to 1-index
|
||||
return col_abs + chr1 + chr2 + row_abs + str(row + 1)
|
||||
|
||||
def rowcol_pair_to_cellrange(row1, col1, row2, col2,
|
||||
row1_abs=False, col1_abs=False, row2_abs=False, col2_abs=False):
|
||||
"""Convert two (row,column) pairs
|
||||
into a cell range string in A1:B2 notation.
|
||||
|
||||
Returns: cell range string
|
||||
"""
|
||||
assert row1 <= row2
|
||||
assert col1 <= col2
|
||||
return (
|
||||
rowcol_to_cell(row1, col1, row1_abs, col1_abs)
|
||||
+ ":"
|
||||
+ rowcol_to_cell(row2, col2, row2_abs, col2_abs)
|
||||
)
|
||||
|
||||
def cellrange_to_rowcol_pair(cellrange):
|
||||
"""Convert cell range string in A1 notation to numeric row/col
|
||||
pair.
|
||||
|
||||
Returns: row1, col1, row2, col2
|
||||
|
||||
"""
|
||||
cellrange = cellrange.upper()
|
||||
# Convert a row range: '1:3'
|
||||
res = _re_row_range.match(cellrange)
|
||||
if res:
|
||||
row1 = int(res.group(1)) - 1
|
||||
col1 = 0
|
||||
row2 = int(res.group(2)) - 1
|
||||
col2 = -1
|
||||
return row1, col1, row2, col2
|
||||
# Convert a column range: 'A:A' or 'B:G'.
|
||||
# A range such as A:A is equivalent to A1:A16384, so add rows as required
|
||||
res = _re_col_range.match(cellrange)
|
||||
if res:
|
||||
col1 = col_by_name(res.group(1).upper())
|
||||
row1 = 0
|
||||
col2 = col_by_name(res.group(2).upper())
|
||||
row2 = -1
|
||||
return row1, col1, row2, col2
|
||||
# Convert a cell range: 'A1:B7'
|
||||
res = _re_cell_range.match(cellrange)
|
||||
if res:
|
||||
row1, col1 = cell_to_rowcol2(res.group(1))
|
||||
row2, col2 = cell_to_rowcol2(res.group(2))
|
||||
return row1, col1, row2, col2
|
||||
# Convert a cell reference: 'A1' or 'AD2000'
|
||||
res = _re_cell_ref.match(cellrange)
|
||||
if res:
|
||||
row1, col1 = cell_to_rowcol2(res.group(1))
|
||||
return row1, col1, row1, col1
|
||||
raise Exception("Unknown cell reference %s" % (cell))
|
||||
|
||||
|
||||
def cell_to_packed_rowcol(cell):
|
||||
""" pack row and column into the required 4 byte format """
|
||||
row, col, row_abs, col_abs = cell_to_rowcol(cell)
|
||||
if col >= MAX_COL:
|
||||
raise Exception("Column %s greater than IV in formula" % cell)
|
||||
if row >= MAX_ROW: # this for BIFF8. for BIFF7 available 2^14
|
||||
raise Exception("Row %s greater than %d in formula" % (cell, MAX_ROW))
|
||||
col |= int(not row_abs) << 15
|
||||
col |= int(not col_abs) << 14
|
||||
return row, col
|
||||
|
||||
# === sheetname functions ===
|
||||
|
||||
def valid_sheet_name(sheet_name):
|
||||
if sheet_name == u"" or sheet_name[0] == u"'" or len(sheet_name) > 31:
|
||||
return False
|
||||
for c in sheet_name:
|
||||
if c in u"[]:\\?/*\x00":
|
||||
return False
|
||||
return True
|
||||
|
||||
def quote_sheet_name(unquoted_sheet_name):
|
||||
if not valid_sheet_name(unquoted_sheet_name):
|
||||
raise Exception(
|
||||
'attempt to quote an invalid worksheet name %r' % unquoted_sheet_name)
|
||||
return u"'" + unquoted_sheet_name.replace(u"'", u"''") + u"'"
|
||||
|
||||
+636
-636
File diff suppressed because it is too large
Load Diff
+1297
-1297
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,13 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
__VERSION__ = '0.7.2'
|
||||
|
||||
import sys
|
||||
if sys.version_info[:2] < (2, 3):
|
||||
print >> sys.stderr, "Sorry, xlwt requires Python 2.3 or later"
|
||||
sys.exit(1)
|
||||
|
||||
from Workbook import Workbook
|
||||
from Worksheet import Worksheet
|
||||
from Row import Row
|
||||
from Column import Column
|
||||
from Formatting import Font, Alignment, Borders, Pattern, Protection
|
||||
from Style import XFStyle, easyxf
|
||||
from ExcelFormula import *
|
||||
|
||||
import CompoundDoc
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
__VERSION__ = '0.7.2'
|
||||
|
||||
import sys
|
||||
|
||||
from Workbook import Workbook
|
||||
from Worksheet import Worksheet
|
||||
from Row import Row
|
||||
from Column import Column
|
||||
from Formatting import Font, Alignment, Borders, Pattern, Protection
|
||||
from Style import XFStyle, easyxf
|
||||
from ExcelFormula import *
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user