mirror of
https://github.com/kennethreitz/tablib.git
synced 2026-06-05 15:00:19 +00:00
Compare commits
531 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 993af5b0b4 | |||
| 0accb4c437 | |||
| 0821716983 | |||
| 660990b6b0 | |||
| 6152d995f0 | |||
| 0ea6d706a9 | |||
| 00d8ab0b37 | |||
| 06c2326dc0 | |||
| fa30ea858d | |||
| 4de2e17984 | |||
| 52b64757b7 | |||
| 5ff4a55ae6 | |||
| ce7d887adc | |||
| 57a535f577 | |||
| 357a5594c5 | |||
| f61b8d8926 | |||
| 22a193dafb | |||
| b539e96697 | |||
| 626a062747 | |||
| 9d2f7d6999 | |||
| a9d9671b7f | |||
| f1046cd13e | |||
| d21bd10908 | |||
| c26159d48f | |||
| 34fe72305e | |||
| d94420d968 | |||
| 51a720b21c | |||
| 20f51d0bc1 | |||
| 87d15a1529 | |||
| 08a6759520 | |||
| 205403d377 | |||
| 9858539c87 | |||
| 201d8d9910 | |||
| fede4a4f13 | |||
| a76933edd5 | |||
| 3197e59b25 | |||
| 1f000f2f2c | |||
| 7879fef65a | |||
| b8bff1190e | |||
| d77aba6210 | |||
| bf6e5c2e78 | |||
| 088b916bab | |||
| e4ac50260e | |||
| 7347d07624 | |||
| c9027b446c | |||
| 825de0193b | |||
| 78b483d39e | |||
| 8f09789d40 | |||
| e0e75ed43c | |||
| bdc84255a8 | |||
| b3c7145c40 | |||
| 44f43516a5 | |||
| e0a40577fd | |||
| 0329eb6168 | |||
| 4c3dc847b0 | |||
| 89fbd54b00 | |||
| 067dc769dc | |||
| 1726c1cf37 | |||
| debe77e432 | |||
| 0e022d89e5 | |||
| a40852d1c6 | |||
| 9b9fb0aa8a | |||
| ca1aa3ad30 | |||
| 2cfde95fe2 | |||
| 5b94682df3 | |||
| f6bf14afd2 | |||
| f3d02aa3b0 | |||
| ca8dbcf9be | |||
| 4418535030 | |||
| 5bd896b954 | |||
| af414b69d7 | |||
| 91062672b5 | |||
| 34334e72a1 | |||
| 5595bb7993 | |||
| 5fde5259d9 | |||
| 591e8f7448 | |||
| 4dfe2c2f89 | |||
| 91608895d6 | |||
| 0d36390254 | |||
| 8ea082ce60 | |||
| a0df54ca22 | |||
| 0e06b7e328 | |||
| 8aeb5e5158 | |||
| a0d19a56cb | |||
| 8cc024e61b | |||
| a7c40a0881 | |||
| 20de7fad98 | |||
| 4969a71f7f | |||
| 743776371a | |||
| 326d07c2ed | |||
| 2f6ea8c644 | |||
| 8aaed50cc8 | |||
| a21b276d9c | |||
| e8838b5ce6 | |||
| 923711d99a | |||
| aac129db66 | |||
| d9df89f5da | |||
| 22bb20c74b | |||
| d25d24a9bb | |||
| 513bba2c20 | |||
| 2b9ce02e3c | |||
| f9f28d3d86 | |||
| 0cb50bb008 | |||
| f55f56ae1d | |||
| 0937c9f9ec | |||
| 25a66f95ac | |||
| 6ab511f8c0 | |||
| 64816258e6 | |||
| 41cbaa04b9 | |||
| c136940801 | |||
| cf03ecfe25 | |||
| 193b840da2 | |||
| 733d77ad1e | |||
| 3abd7e8c53 | |||
| 0be9e6a74b | |||
| ecd0afbcec | |||
| addaa090ef | |||
| f7b3fd4601 | |||
| 79dc77de49 | |||
| b057cdf05e | |||
| fc2f3c07c8 | |||
| a10327a283 | |||
| e0de42ef06 | |||
| f757ab84d1 | |||
| dc24fda415 | |||
| 3ba8d529fc | |||
| a8bdb4b28f | |||
| 1aaf235751 | |||
| 36ec60d5dd | |||
| babcbfd949 | |||
| 29b2c08da0 | |||
| 862a681263 | |||
| 102073c426 | |||
| 499ce52304 | |||
| c650b67e06 | |||
| 3e4d6fb5aa | |||
| dd2ba714d3 | |||
| a28a057559 | |||
| d38549ef1e | |||
| 5a359ba4de | |||
| 359007444c | |||
| 4f8949417e | |||
| 3d5943a8a4 | |||
| 38486231cc | |||
| 75f1bafd69 | |||
| 4749760e6f | |||
| ac3cf67620 | |||
| f812c29275 | |||
| 4c5d0b1a45 | |||
| 61063e2b09 | |||
| 4c300e65a5 | |||
| edbb16ec97 | |||
| dec5cea722 | |||
| 38183938dc | |||
| 7f1db4023f | |||
| b09fface1b | |||
| 69edb9def3 | |||
| ec54918f4a | |||
| ab6633549f | |||
| 56005d8022 | |||
| 36fa7ef097 | |||
| bb0abc863e | |||
| 58f6eefe01 | |||
| e4726cb85c | |||
| 412e690289 | |||
| 44e797d70e | |||
| 34c14aca18 | |||
| 7c318adde4 | |||
| 5dd74c0104 | |||
| a50ff92ff2 | |||
| 383d4b9c4e | |||
| 00e2ffa2ef | |||
| a3cd2c9cff | |||
| d89d243a30 | |||
| 69abfc3ada | |||
| 80e72cfa27 | |||
| 05bd0d1d42 | |||
| 62807734bd | |||
| c5c2dffe42 | |||
| 46102d4be7 | |||
| 44e9e24fec | |||
| 0ca5520bbc | |||
| e66eb4a189 | |||
| 0e720d78ca | |||
| 6afe716d64 | |||
| 76cbf9fadf | |||
| a93f93a458 | |||
| 3d44bdec40 | |||
| 319505817a | |||
| 6cb9a69746 | |||
| bb1354b61f | |||
| ddc4bd30f2 | |||
| 52e547daf9 | |||
| 7f0b7a0a22 | |||
| ddac443732 | |||
| e13f4d0aba | |||
| 54f9041f2c | |||
| 91d3299280 | |||
| cd67a63b43 | |||
| 19b3d6d06a | |||
| 59090d33a8 | |||
| a4f974287b | |||
| f59abe84be | |||
| cf23f2344f | |||
| e16bb38c48 | |||
| 71ca275dd1 | |||
| 75bbfbbaf4 | |||
| b35d505621 | |||
| cd491c062c | |||
| 9fdb72cc5c | |||
| a5b1f7987e | |||
| 8cf6770a76 | |||
| 5fa3d2f886 | |||
| d4c66c7a4e | |||
| af17586581 | |||
| 23d21f00f3 | |||
| 7ee924b5a6 | |||
| d720beadac | |||
| ee9666a146 | |||
| 77a9e25795 | |||
| d515724817 | |||
| 2814fbc381 | |||
| 9ca1d4ec54 | |||
| abbb4e32d8 | |||
| f6e757d569 | |||
| 9ba0451843 | |||
| d99db57d75 | |||
| 2299c00883 | |||
| 5ba6f5d91a | |||
| bbdf5f11ab | |||
| 851ba25702 | |||
| 039272b274 | |||
| d6a7832e60 | |||
| e51c4faec7 | |||
| f7fc3244ee | |||
| 53d69bd3ea | |||
| fcc9700d11 | |||
| 1ec9c18a66 | |||
| 99c28fa560 | |||
| fa7fb579fd | |||
| be24de19dc | |||
| 1d4f4b68ca | |||
| 8debeb26ac | |||
| 38e1ee6c3d | |||
| a774789252 | |||
| 995eabad37 | |||
| d90358bf69 | |||
| c5920249de | |||
| 9b6a73c97c | |||
| 679bd115b6 | |||
| 32cbc36fc1 | |||
| 8bded88559 | |||
| f8f57a467e | |||
| a11a993955 | |||
| 25894f2948 | |||
| 591b89693e | |||
| 85d9c2497e | |||
| eaf52b691e | |||
| 6f53c5d2b9 | |||
| 90ee799576 | |||
| c02a21ccd2 | |||
| fa045ca114 | |||
| 65703550c3 | |||
| 1fcb98f9ae | |||
| e2d45ecff7 | |||
| 47d92277cc | |||
| fdd74b5b0c | |||
| de052f0fac | |||
| 2f3acf5af4 | |||
| c4e8755cd2 | |||
| 79dc4524a0 | |||
| a785d77901 | |||
| b3485ec942 | |||
| 28b358c9da | |||
| 24657520e9 | |||
| 66d9e50984 | |||
| 541fba6786 | |||
| bc6398ffb0 | |||
| dca7bc9a7d | |||
| 2fbda0f43d | |||
| e350f9428b | |||
| 68dba0a77d | |||
| 028be03c2c | |||
| e1d65ba3c8 | |||
| e4cb3bcd9b | |||
| bf9510e0c7 | |||
| 82ae3ca507 | |||
| 5fbdd56fba | |||
| f187cef5f4 | |||
| 87892d7266 | |||
| 20e2ce5ba0 | |||
| 48e576954d | |||
| a21f8187f8 | |||
| 8479df725e | |||
| 333deb2311 | |||
| 0b714f21e1 | |||
| ae730b00b1 | |||
| 84e8b0384f | |||
| 7a2842a8af | |||
| 954bbdccf3 | |||
| 7acaa8460d | |||
| 84e7e251ae | |||
| dc868eff31 | |||
| 43356e908c | |||
| f7acc19523 | |||
| c5972db8f0 | |||
| 1cc051f3e8 | |||
| 3da155ce0d | |||
| 9a34cf0980 | |||
| 434f66b4eb | |||
| d056916c53 | |||
| cf5239f097 | |||
| 49d8cb816f | |||
| fbd277ff2e | |||
| 6f4572fa56 | |||
| 453fc8614c | |||
| 01cf58e431 | |||
| f6cd89c76c | |||
| 1e0f30e8a6 | |||
| 569d35bfca | |||
| d40cdfbcd0 | |||
| 86bbaf9bea | |||
| 0ed01d85b9 | |||
| fc4cc7fa14 | |||
| 70716fdd21 | |||
| 1146ec2341 | |||
| 1a7d597745 | |||
| 56b627a561 | |||
| 98e182bed2 | |||
| c8a5563309 | |||
| c225a64d68 | |||
| d611d5a14f | |||
| 45121ddd65 | |||
| c74357cb20 | |||
| 939b0af551 | |||
| 9c2018653f | |||
| 2bc6122ee8 | |||
| 7f0748aac9 | |||
| 41a5c67159 | |||
| 3efefcc8da | |||
| d19de6025b | |||
| 65ba937c0d | |||
| 79a2bb888f | |||
| 25eacaf6f0 | |||
| c2a9af7fb3 | |||
| 3b06f3760d | |||
| e7ee3195a7 | |||
| 5bd2e3df52 | |||
| 837b3f83e6 | |||
| ff8f23edd5 | |||
| 5ffcfd56f2 | |||
| 955c24c974 | |||
| 192a5efabb | |||
| 1aafc7e2f4 | |||
| 9e45b95d12 | |||
| d8f0a018ae | |||
| 7545f3726e | |||
| 85e2bd73fc | |||
| 37033903c5 | |||
| 02c38c2520 | |||
| 26748deb9f | |||
| 63f6cea132 | |||
| 1b035f9774 | |||
| 2c14486c33 | |||
| 8bc69c9d85 | |||
| d36a2cbd42 | |||
| 1ab0eb3fae | |||
| cd71e1a5b1 | |||
| 47f79a7ca1 | |||
| 9f38efe413 | |||
| 5d98239a7e | |||
| a3f0d02633 | |||
| b29007a0df | |||
| e75c3c1a66 | |||
| 47cebbc328 | |||
| e4c39524f7 | |||
| c88c794314 | |||
| 752443f077 | |||
| 7c0507bcce | |||
| 652ac85549 | |||
| 05ea3c35fc | |||
| d5fada7e1d | |||
| 511c58d4e1 | |||
| c469360a0e | |||
| 97b4401b18 | |||
| 40e0f41b4c | |||
| 39435727ba | |||
| eda9d5af03 | |||
| 15435047c6 | |||
| a3781e3c89 | |||
| 6a825a8a39 | |||
| 6a449d497a | |||
| d807c60346 | |||
| 71603662b1 | |||
| 21c11b9911 | |||
| e8c923d712 | |||
| bc581c08df | |||
| 4f9c9d09ec | |||
| 63e8a7172d | |||
| 45e0af9f0e | |||
| fa6f5b3af3 | |||
| 0528e0a500 | |||
| 8e83734985 | |||
| 783eccc67d | |||
| 7236415f42 | |||
| c0a3c3ea1e | |||
| 14bd964fb1 | |||
| 6bfc6634ba | |||
| 54affad292 | |||
| 7c963a0f4d | |||
| 02f27f15c5 | |||
| 9c65515e7a | |||
| c87a954a9e | |||
| 42e40ed0ab | |||
| 23ab6c4724 | |||
| 32a09ccd6a | |||
| 81a7f79b3d | |||
| 05c9b33003 | |||
| ec7273d02d | |||
| 19ee1997b5 | |||
| f01d65c2e9 | |||
| 9778a96351 | |||
| 906138b138 | |||
| 43c68b396f | |||
| d611233c80 | |||
| 3d02b866ce | |||
| 887ee2fbac | |||
| bfd211854a | |||
| bc75911500 | |||
| a2b4e4c6ba | |||
| fde6f11763 | |||
| 33a83316df | |||
| f6d7888d9e | |||
| c19e2f2c5b | |||
| eaa2b9b8ea | |||
| 2f8083bda6 | |||
| 2c5a9af76e | |||
| e74a8f41cc | |||
| cd5aa4fc06 | |||
| 1d460bac40 | |||
| 4a3fde37a3 | |||
| 62ad123ad8 | |||
| fefc7b4d1f | |||
| 6313437a27 | |||
| 23a5bb1443 | |||
| 864f29cc4b | |||
| c136b794a7 | |||
| d254c2d2b0 | |||
| 9b235150cf | |||
| 9f3e6eeaa1 | |||
| 51728f954f | |||
| 2949b7c656 | |||
| 07d243bbc9 | |||
| bf3484e606 | |||
| 9b2ab6fae9 | |||
| 7a3d55daab | |||
| eec0595c5c | |||
| 0c7c248b96 | |||
| 0d14f7f2b9 | |||
| d5f713024d | |||
| 415bc819e7 | |||
| 974258094e | |||
| ab16f69be6 | |||
| 28d9af852a | |||
| 39c6ea6503 | |||
| 39b66ad8e9 | |||
| 004b3da680 | |||
| d4923533eb | |||
| 29e0b76910 | |||
| 4f54de2630 | |||
| 1f0d68ee79 | |||
| cae8fa1276 | |||
| 4c0a20a7b9 | |||
| 6c1fa87138 | |||
| 0e30255836 | |||
| 1156d5a220 | |||
| 83b71967b9 | |||
| 4dab48cd76 | |||
| 5324526329 | |||
| 1dfcd42233 | |||
| f162b19bd6 | |||
| 707164e459 | |||
| 42f0a285c3 | |||
| d111cc7cc7 | |||
| 25fe211a22 | |||
| 4b675494c4 | |||
| a196b9a5dd | |||
| 5ba56c2bb3 | |||
| 36fbdda492 | |||
| 273d2729ee | |||
| 3036bc9e52 | |||
| b9c74eacc8 | |||
| 805ccfae34 | |||
| fddc018394 | |||
| 2477100062 | |||
| 983b979fda | |||
| 3edb45bac7 | |||
| 29d626fa1f | |||
| 1f22fc7321 | |||
| 8631f60f8d | |||
| 65873b6112 | |||
| 56e44bd45c | |||
| 87e65fd3e7 | |||
| ffbc3b122d | |||
| 9d71603dad | |||
| cceb41af98 | |||
| 60ffa898fd | |||
| a4a211b5a6 | |||
| c9766a48b0 | |||
| 6975685b89 | |||
| e920244a1b | |||
| ea63779baf | |||
| d826f6d0ae | |||
| f6fa3f2abc | |||
| eed6df45e0 | |||
| cb4c67767a | |||
| 1e21fee70e | |||
| 420dd36ab8 | |||
| 9a05770899 | |||
| 8e055f1c57 | |||
| 239e33aaed | |||
| bf4fdea187 | |||
| 03086052ed | |||
| 2128473938 | |||
| 74c64d66a9 | |||
| a4e77f22c4 | |||
| 2e03046a07 | |||
| 06a7b4cd4e | |||
| 6a70b84166 | |||
| 77d9fe8b41 | |||
| 64cb547e0a |
+10
@@ -0,0 +1,10 @@
|
||||
# .coveragerc to control coverage.py
|
||||
|
||||
[report]
|
||||
# Regexes for lines to exclude from consideration
|
||||
exclude_lines =
|
||||
# Have to re-enable the standard pragma:
|
||||
pragma: no cover
|
||||
|
||||
# Don't complain if non-runnable code isn't run:
|
||||
if __name__ == .__main__.:
|
||||
@@ -0,0 +1,10 @@
|
||||
[](https://jazzband.co/)
|
||||
|
||||
This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to abide
|
||||
by the [Contributor Code of Conduct](https://jazzband.co/about/conduct) and follow the
|
||||
[guidelines](https://jazzband.co/about/guidelines).
|
||||
|
||||
If you'd like to contribute, simply fork
|
||||
[the repository](https://github.com/jazzband/tablib), commit your changes to a feature
|
||||
branch, and send a pull request to `master`. Make sure you add yourself to
|
||||
[AUTHORS](https://github.com/jazzband/tablib/blob/master/AUTHORS).
|
||||
@@ -0,0 +1,30 @@
|
||||
name: Docs and lint
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
env:
|
||||
- TOXENV: docs
|
||||
- TOXENV: lint
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --upgrade tox
|
||||
|
||||
- name: Tox
|
||||
run: tox
|
||||
env: ${{ matrix.env }}
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: [3.5, 3.6, 3.7]
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --upgrade tox
|
||||
python -m pip install -e .
|
||||
|
||||
- name: Tox tests
|
||||
shell: bash
|
||||
# Drop the dot: py3.7-tests -> py37-tests
|
||||
run: |
|
||||
tox -e py`echo ${{ matrix.python-version }} | tr -d .`-tests
|
||||
+16
-1
@@ -22,4 +22,19 @@ coverage.xml
|
||||
nosetests.xml
|
||||
junit-py25.xml
|
||||
junit-py26.xml
|
||||
junit-py27.xml
|
||||
junit-py27.xml
|
||||
|
||||
# tox noise
|
||||
.tox
|
||||
|
||||
# pyenv noise
|
||||
.python-version
|
||||
tablib.egg-info/*
|
||||
|
||||
# Coverage
|
||||
.coverage
|
||||
htmlcov
|
||||
|
||||
# setuptools noise
|
||||
.eggs
|
||||
*.egg-info
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
repos:
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v1.25.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: ["--py3-plus"]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-isort
|
||||
rev: v4.3.21
|
||||
hooks:
|
||||
- id: isort
|
||||
additional_dependencies: [toml]
|
||||
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.4.2
|
||||
hooks:
|
||||
- id: python-check-blanket-noqa
|
||||
- id: rst-backticks
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.4.0
|
||||
hooks:
|
||||
- id: check-merge-conflict
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
language: python
|
||||
cache:
|
||||
pip: true
|
||||
directories:
|
||||
- $HOME/.cache/pre-commit
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- python: 3.8
|
||||
env: TOXENV=docs
|
||||
- python: 3.8
|
||||
env: TOXENV=lint
|
||||
- python: 3.8
|
||||
- python: 3.7
|
||||
- python: 3.6
|
||||
|
||||
install: travis_retry pip install tox-travis
|
||||
|
||||
script: tox
|
||||
|
||||
after_success:
|
||||
- |
|
||||
if [[ "$TOXENV" != "docs" && "$TOXENV" != "lint" ]]; then
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
fi
|
||||
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: jazzband
|
||||
server: https://jazzband.co/projects/tablib/upload
|
||||
distributions: sdist bdist_wheel
|
||||
password:
|
||||
secure: svV4fYtodwW+iTyFOm5ISEfhVwcA+6vTskD3x6peznc40TdMV9Ek8nT3Q/NB4lCbXoUw2qR4H6uhLCjesnv/VvVk/qbitCyD8ySlgwOV5n7NzJs8lC8EYaHSjGQjatTwJAokfGVYkPawkI7HXDqtDggLUQBK+Ag8HDW+XBSbQIU=
|
||||
on:
|
||||
tags: true
|
||||
repo: jazzband/tablib
|
||||
python: 3.7
|
||||
@@ -1,18 +1,32 @@
|
||||
Tablib is written and maintained by Kenneth Reitz and
|
||||
various contributors:
|
||||
Tablib was originally written by Kenneth Reitz and is now maintained
|
||||
by the Jazzband GitHub team.
|
||||
|
||||
Development Lead
|
||||
````````````````
|
||||
Here is a list of passed and present much-appreciated contributors:
|
||||
|
||||
- Kenneth Reitz <_@kennethreitz.com>
|
||||
|
||||
|
||||
Patches and Suggestions
|
||||
```````````````````````
|
||||
|
||||
- Luke Lee
|
||||
- Josh Ourisman
|
||||
- Luca Beltrame
|
||||
- Benjamin Wohlwend
|
||||
- Erik Youngren
|
||||
- Mark Rogers
|
||||
Alex Gaynor
|
||||
Andrii Soldatenko
|
||||
Benjamin Wohlwend
|
||||
Bruno Soares
|
||||
Claude Paroz
|
||||
Daniel Santos
|
||||
Erik Youngren
|
||||
Hugo van Kemenade
|
||||
Iuri de Silvio
|
||||
Jakub Janoszek
|
||||
James Douglass
|
||||
Joel Friedly
|
||||
Josh Ourisman
|
||||
Kenneth Reitz
|
||||
Luca Beltrame
|
||||
Luke Lee
|
||||
Marc Abramowitz
|
||||
Marco Dallagiacoma
|
||||
Mark Rogers
|
||||
Mark Walling
|
||||
Mathias Loesch
|
||||
Mike Waldner
|
||||
Peyman Salehi
|
||||
Rabin Nankhwa
|
||||
Tommy Anthony
|
||||
Tsuyoshi Hombashi
|
||||
Tushar Makkar
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
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 :)
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
# History
|
||||
|
||||
## 1.0.0 (2020-01-13)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- Dropped Python 2 support
|
||||
- Dependencies are now all optional. To install `tablib` as before with all
|
||||
possible supported formats, run `pip install tablib[all]`
|
||||
|
||||
### Improvements
|
||||
|
||||
- Formats can now be dynamically registered through the
|
||||
`tablib.formats.registry.refister` API (#256).
|
||||
- Tablib methods expecting data input (`detect_format`, `import_set`,
|
||||
`Dataset.load`, `Databook.load`) now accepts file-like objects in addition
|
||||
to raw strings and bytestrings (#440).
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Fixed a crash when exporting an empty string with the ReST format (#368)
|
||||
- Error cells from imported .xls files contain now the error string (#202)
|
||||
|
||||
## 0.14.0 (2019-10-19)
|
||||
|
||||
### Deprecations
|
||||
|
||||
- The 0.14.x series will be the last to support Python 2
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- Dropped Python 3.4 support
|
||||
|
||||
### Improvements
|
||||
|
||||
- Added Python 3.7 and 3.8 support
|
||||
- The project is now maintained by the Jazzband team, https://jazzband.co
|
||||
- Improved format autodetection and added autodetection for the odf format.
|
||||
- Added search to all documentation pages
|
||||
- Open xlsx workbooks in read-only mode (#316)
|
||||
- Unpin requirements
|
||||
- Only install backports.csv on Python 2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Fixed `DataBook().load` parameter ordering (first stream, then format).
|
||||
- Fixed a regression for xlsx exports where non-string values were forced to
|
||||
strings (#314)
|
||||
- Fixed xlsx format detection (which was often detected as `xls` format)
|
||||
|
||||
## 0.13.0 (2019-03-08)
|
||||
|
||||
- Added reStructuredText output capability (#336)
|
||||
- Added Jira output capability
|
||||
- Stopped calling openpyxl deprecated methods (accessing cells, removing sheets)
|
||||
(openpyxl minimal version is now 2.4.0)
|
||||
- Fixed a circular dependency issue in JSON output (#332)
|
||||
- Fixed Unicode error for the CSV export on Python 2 (#215)
|
||||
- Removed usage of optional `ujson` (#311)
|
||||
- Dropped Python 3.3 support
|
||||
|
||||
## 0.12.1 (2017-09-01)
|
||||
|
||||
- Favor `Dataset.export(<format>)` over `Dataset.<format>` syntax in docs
|
||||
- Make Panda dependency optional
|
||||
|
||||
## 0.12.0 (2017-08-27)
|
||||
|
||||
- Add initial Panda DataFrame support
|
||||
- Dropped Python 2.6 support
|
||||
|
||||
## 0.11.5 (2017-06-13)
|
||||
|
||||
- Use `yaml.safe_load` for importing yaml.
|
||||
|
||||
## 0.11.4 (2017-01-23)
|
||||
|
||||
- Use built-in `json` package if available
|
||||
- Support Python 3.5+ in classifiers
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Fixed textual representation for Dataset with no headers
|
||||
- Handle decimal types
|
||||
|
||||
## 0.11.3 (2016-02-16)
|
||||
|
||||
- Release fix.
|
||||
|
||||
## 0.11.2 (2016-02-16)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Fix export only formats.
|
||||
- Fix for xlsx output.
|
||||
|
||||
## 0.11.1 (2016-02-07)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Fixed packaging error on Python 3.
|
||||
|
||||
|
||||
## 0.11.0 (2016-02-07)
|
||||
|
||||
### New Formats!
|
||||
|
||||
- Added LaTeX table export format (`Dataset.latex`).
|
||||
- Support for dBase (DBF) files (`Dataset.dbf`).
|
||||
|
||||
### Improvements
|
||||
|
||||
- New import/export interface (`Dataset.export()`, `Dataset.load()`).
|
||||
- CSV custom delimiter support (`Dataset.export('csv', delimiter='$')`).
|
||||
- Adding ability to remove duplicates to all rows in a dataset (`Dataset.remove_duplicates()`).
|
||||
- Added a mechanism to avoid `datetime.datetime` issues when serializing data.
|
||||
- New `detect_format()` function (mostly for internal use).
|
||||
- Update the vendored unicodecsv to fix `None` handling.
|
||||
- Only freeze the headers row, not the headers columns (xls).
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- `detect()` function removed.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Fix XLSX import.
|
||||
- Bugfix for `Dataset.transpose().transpose()`.
|
||||
|
||||
|
||||
## 0.10.0 (2014-05-27)
|
||||
|
||||
* Unicode Column Headers
|
||||
* ALL the bugfixes!
|
||||
|
||||
## 0.9.11 (2011-06-30)
|
||||
|
||||
* Bugfixes
|
||||
|
||||
## 0.9.10 (2011-06-22)
|
||||
|
||||
* Bugfixes
|
||||
|
||||
## 0.9.9 (2011-06-21)
|
||||
|
||||
* Dataset API Changes
|
||||
* `stack_rows` => `stack`, `stack_columns` => `stack_cols`
|
||||
* column operations have their own methods now (`append_col`, `insert_col`)
|
||||
* List-style `pop()`
|
||||
* Redis-style `rpush`, `lpush`, `rpop`, `lpop`, `rpush_col`, and `lpush_col`
|
||||
|
||||
## 0.9.8 (2011-05-22)
|
||||
|
||||
* OpenDocument Spreadsheet support (.ods)
|
||||
* Full Unicode TSV support
|
||||
|
||||
|
||||
## 0.9.7 (2011-05-12)
|
||||
|
||||
* Full XLSX Support!
|
||||
* Pickling Bugfix
|
||||
* Compat Module
|
||||
|
||||
|
||||
## 0.9.6 (2011-05-12)
|
||||
|
||||
* `seperators` renamed to `separators`
|
||||
* Full unicode CSV support
|
||||
|
||||
|
||||
## 0.9.5 (2011-03-24)
|
||||
|
||||
* Python 3.1, Python 3.2 Support (same code base!)
|
||||
* Formatter callback support
|
||||
* Various bug fixes
|
||||
|
||||
|
||||
|
||||
## 0.9.4 (2011-02-18)
|
||||
|
||||
* Python 2.5 Support!
|
||||
* Tox Testing for 2.5, 2.6, 2.7
|
||||
* AnyJSON Integrated
|
||||
* OrderedDict support
|
||||
* Caved to community pressure (spaces)
|
||||
|
||||
|
||||
## 0.9.3 (2011-01-31)
|
||||
|
||||
* Databook duplication leak fix.
|
||||
* HTML Table output.
|
||||
* Added column sorting.
|
||||
|
||||
|
||||
## 0.9.2 (2010-11-17)
|
||||
|
||||
* Transpose method added to Datasets.
|
||||
* New frozen top row in Excel output.
|
||||
* Pickling support for Datasets and Rows.
|
||||
* Support for row/column stacking.
|
||||
|
||||
|
||||
## 0.9.1 (2010-11-04)
|
||||
|
||||
* Minor reference shadowing bugfix.
|
||||
|
||||
|
||||
## 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.
|
||||
-160
@@ -1,160 +0,0 @@
|
||||
History
|
||||
-------
|
||||
|
||||
0.9.7 (2011-05-12)
|
||||
++++++++++++++++++
|
||||
|
||||
* Full XLSX Support!
|
||||
* Pickling Bugfix
|
||||
* Compat Module
|
||||
|
||||
|
||||
0.9.6 (2011-05-12)
|
||||
++++++++++++++++++
|
||||
|
||||
* ``seperators`` renamed to ``separators``
|
||||
* Full unicode CSV support
|
||||
|
||||
|
||||
0.9.5 (2011-03-24)
|
||||
++++++++++++++++++
|
||||
|
||||
* Python 3.1, Python 3.2 Support (same code base!)
|
||||
* Formatter callback support
|
||||
* Various bug fixes
|
||||
|
||||
|
||||
|
||||
0.9.4 (2011-02-18)
|
||||
++++++++++++++++++
|
||||
|
||||
* Python 2.5 Support!
|
||||
* Tox Testing for 2.5, 2.6, 2.7
|
||||
* AnyJSON Integrated
|
||||
* OrderedDict support
|
||||
* Caved to community pressure (spaces)
|
||||
|
||||
|
||||
0.9.3 (2011-01-31)
|
||||
++++++++++++++++++
|
||||
|
||||
* Databook duplication leak fix.
|
||||
* HTML Table output.
|
||||
* Added column sorting.
|
||||
|
||||
|
||||
0.9.2 (2010-11-17)
|
||||
++++++++++++++++++
|
||||
|
||||
* Transpose method added to Datasets.
|
||||
* New frozen top row in Excel output.
|
||||
* Pickling support for Datasets and Rows.
|
||||
* Support for row/column stacking.
|
||||
|
||||
|
||||
0.9.1 (2010-11-04)
|
||||
++++++++++++++++++
|
||||
|
||||
* Minor reference shadowing bugfix.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
Copyright (c) 2011 Kenneth Reitz.
|
||||
Copyright 2016 Kenneth Reitz
|
||||
Copyright 2019 Jazzband
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -16,4 +17,4 @@ 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.
|
||||
THE SOFTWARE.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
include HISTORY.rst README.rst LICENSE AUTHORS
|
||||
@@ -1,209 +0,0 @@
|
||||
Tablib includes some vendorized python libraries: ordereddict, pyyaml,
|
||||
simplejson, unicodecsv, and xlwt.
|
||||
|
||||
Markup License
|
||||
==============
|
||||
|
||||
Markup is in the public domain.
|
||||
|
||||
|
||||
|
||||
OrderedDict License
|
||||
===================
|
||||
|
||||
Copyright (c) 2009 Raymond Hettinger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
(the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
UnicodeCSV License
|
||||
==================
|
||||
|
||||
Copyright 2010 Jeremy Dunck. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are
|
||||
permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
of conditions and the following disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY JEREMY DUNCK ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JEREMY DUNCK OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those of the
|
||||
authors and should not be interpreted as representing official policies, either expressed
|
||||
or implied, of Jeremy Dunck.
|
||||
|
||||
|
||||
|
||||
|
||||
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
|
||||
@@ -0,0 +1,45 @@
|
||||
# Tablib: format-agnostic tabular dataset library
|
||||
|
||||
[](https://jazzband.co/)
|
||||
[](https://pypi.org/project/tablib/)
|
||||
[](https://pypi.org/project/tablib/)
|
||||
[](https://pypistats.org/packages/tablib)
|
||||
[](https://travis-ci.org/jazzband/tablib)
|
||||
[](https://github.com/jazzband/tablib/actions)
|
||||
[](https://codecov.io/gh/jazzband/tablib)
|
||||
[](LICENSE)
|
||||
|
||||
_____ ______ ___________ ______
|
||||
__ /_______ ____ /_ ___ /___(_)___ /_
|
||||
_ __/_ __ `/__ __ \__ / __ / __ __ \
|
||||
/ /_ / /_/ / _ /_/ /_ / _ / _ /_/ /
|
||||
\__/ \__,_/ /_.___/ /_/ /_/ /_.___/
|
||||
|
||||
|
||||
Tablib is a format-agnostic tabular dataset library, written in Python.
|
||||
|
||||
Output formats supported:
|
||||
|
||||
- Excel (Sets + Books)
|
||||
- JSON (Sets + Books)
|
||||
- YAML (Sets + Books)
|
||||
- Pandas DataFrames (Sets)
|
||||
- HTML (Sets)
|
||||
- Jira (Sets)
|
||||
- TSV (Sets)
|
||||
- ODS (Sets)
|
||||
- CSV (Sets)
|
||||
- DBF (Sets)
|
||||
|
||||
Note that tablib *purposefully* excludes XML support. It always will. (Note: This is a
|
||||
joke. Pull requests are welcome.)
|
||||
|
||||
Tablib documentation is graciously hosted on https://tablib.readthedocs.io
|
||||
|
||||
It is also available in the ``docs`` directory of the source distribution.
|
||||
|
||||
Make sure to check out [Tablib on PyPI](https://pypi.org/project/tablib/)!
|
||||
|
||||
## Contribute
|
||||
|
||||
Please see the [contributing guide](https://github.com/jazzband/tablib/blob/master/.github/CONTRIBUTING.md).
|
||||
-155
@@ -1,155 +0,0 @@
|
||||
Tablib: format-agnostic tabular dataset library
|
||||
===============================================
|
||||
|
||||
::
|
||||
|
||||
_____ ______ ___________ ______
|
||||
__ /_______ ____ /_ ___ /___(_)___ /_
|
||||
_ __/_ __ `/__ __ \__ / __ / __ __ \
|
||||
/ /_ / /_/ / _ /_/ /_ / _ / _ /_/ /
|
||||
\__/ \__,_/ /_.___/ /_/ /_/ /_.___/
|
||||
|
||||
|
||||
|
||||
Tablib is a format-agnostic tabular dataset library, written in Python.
|
||||
|
||||
Output formats supported:
|
||||
|
||||
- Excel (Sets + Books)
|
||||
- JSON (Sets + Books)
|
||||
- YAML (Sets + Books)
|
||||
- HTML (Sets)
|
||||
- TSV (Sets)
|
||||
- CSV (Sets)
|
||||
|
||||
Note that tablib *purposefully* excludes XML support. It always will. (Note: This is a joke. Pull requests are welcome.)
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
`tablib.Dataset()`
|
||||
A Dataset is a table of tabular data. It may or may not have a header row. They can be build and manipulated as raw Python datatypes (Lists of tuples|dictionaries). Datasets can be imported from JSON, YAML, and CSV; they can be exported to Excel (XLS), JSON, YAML, and CSV.
|
||||
|
||||
`tablib.Databook()`
|
||||
A Databook is a set of Datasets. The most common form of a Databook is an Excel file with multiple spreadsheets. Databooks can be imported from JSON and YAML; they can be exported to Excel (XLS), JSON, and YAML.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
|
||||
Populate fresh data files: ::
|
||||
|
||||
headers = ('first_name', 'last_name')
|
||||
|
||||
data = [
|
||||
('John', 'Adams'),
|
||||
('George', 'Washington')
|
||||
]
|
||||
|
||||
data = tablib.Dataset(*data, headers=headers)
|
||||
|
||||
|
||||
Intelligently add new rows: ::
|
||||
|
||||
>>> data.append(('Henry', 'Ford'))
|
||||
|
||||
Intelligently add new columns: ::
|
||||
|
||||
>>> data.append(col=(90, 67, 83), header='age')
|
||||
|
||||
Slice rows: ::
|
||||
|
||||
>>> print data[:2]
|
||||
[('John', 'Adams', 90), ('George', 'Washington', 67)]
|
||||
|
||||
|
||||
Slice columns by header: ::
|
||||
|
||||
>>> print data['first_name']
|
||||
['John', 'George', 'Henry']
|
||||
|
||||
Easily delete rows: ::
|
||||
|
||||
>>> 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
|
||||
-------
|
||||
|
||||
v1.0.0:
|
||||
- Add hooks system
|
||||
- Tablib.ext namespace
|
||||
- Better 2.x/3.x handling (currently internal codebase fork)
|
||||
- Width detection on XLS out
|
||||
|
||||
|
||||
.. _`the repository`: http://github.com/kennethreitz/tablib
|
||||
.. _AUTHORS: http://github.com/kennethreitz/tablib/blob/master/AUTHORS
|
||||
@@ -0,0 +1,29 @@
|
||||
# Release checklist
|
||||
|
||||
Jazzband guidelines: https://jazzband.co/about/releases
|
||||
|
||||
* [ ] Get master to the appropriate code release state.
|
||||
[Travis CI](https://travis-ci.org/jazzband/tablib)
|
||||
should pass on master.
|
||||
[](https://travis-ci.org/jazzband/tablib)
|
||||
|
||||
* [ ] Check [HISTORY.md](https://github.com/jazzband/tablib/blob/master/HISTORY.md),
|
||||
update version number and release date
|
||||
|
||||
* [ ] Tag with version number and push tag, for example:
|
||||
```bash
|
||||
git tag -a v0.14.0 -m v0.14.0
|
||||
git push --tags
|
||||
```
|
||||
|
||||
* [ ] Once Travis CI has built and uploaded distributions, check files at
|
||||
[Jazzband](https://jazzband.co/projects/tablib) and release to
|
||||
[PyPI](https://pypi.org/pypi/tablib)
|
||||
|
||||
* [ ] Check installation:
|
||||
```bash
|
||||
pip uninstall -y tablib && pip install -U tablib
|
||||
```
|
||||
|
||||
* [ ] Create new GitHub release: https://github.com/jazzband/tablib/releases/new
|
||||
* Tag: Pick existing tag "v0.14.0"
|
||||
@@ -1,9 +0,0 @@
|
||||
* Hooks System
|
||||
- pre/post-append
|
||||
- pre/post-import
|
||||
- pre/post-export
|
||||
* Add Tablib.ext namespace
|
||||
* Fix 2.x/3.x handling (currently internal codebase fork)
|
||||
* Make CSV write more customizable.
|
||||
* Width detection for XLS output
|
||||
* Documentation Improvements
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
<h3><a href="https://tablib.readthedocs.io">About Tablib</a></h3>
|
||||
<p>
|
||||
Tablib is an MIT Licensed 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.
|
||||
</p>
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://tablib.readthedocs.io">The Tablib Website</a></li>
|
||||
<li><a href="https://pypi.org/project/tablib">Tablib @ PyPI</a></li>
|
||||
<li><a href="https://github.com/jazzband/tablib">Tablib @ GitHub</a></li>
|
||||
<li><a href="https://github.com/jazzband/tablib/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
<h3><a href="https://tablib.readthedocs.io">About Tablib</a></h3>
|
||||
<p>
|
||||
Tablib is an MIT Licensed 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.
|
||||
</p>
|
||||
@@ -1,3 +0,0 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
.DS_Store
|
||||
Vendored
-45
@@ -1,45 +0,0 @@
|
||||
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
@@ -1,25 +0,0 @@
|
||||
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
@@ -1,86 +0,0 @@
|
||||
# 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
-31
@@ -1,31 +0,0 @@
|
||||
{%- 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>
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-8742933-9']);
|
||||
_gaq.push(['_setDomainName', 'none']);
|
||||
_gaq.push(['_setAllowLinker', true]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
{%- endblock %}
|
||||
Vendored
-19
@@ -1,19 +0,0 @@
|
||||
<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
-430
@@ -1,430 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
|
||||
div.sphinxsidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
margin-left: 0;
|
||||
margin-top: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin-top: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.document {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.bodywrapper {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* 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
@@ -1,7 +0,0 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = flasky.css
|
||||
pygments_style = flask_theme_support.FlaskyStyle
|
||||
|
||||
[options]
|
||||
touch_icon =
|
||||
Vendored
-22
@@ -1,22 +0,0 @@
|
||||
{% 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
@@ -1,287 +0,0 @@
|
||||
/*
|
||||
* 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
@@ -1,10 +0,0 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = flasky.css
|
||||
nosidebar = true
|
||||
pygments_style = flask_theme_support.FlaskyStyle
|
||||
|
||||
[options]
|
||||
index_logo = ''
|
||||
index_logo_height = 120px
|
||||
github_fork = ''
|
||||
+2
-2
@@ -36,7 +36,7 @@ Functions
|
||||
---------
|
||||
|
||||
|
||||
.. autofunction:: detect
|
||||
.. autofunction:: detect_format
|
||||
|
||||
.. autofunction:: import_set
|
||||
|
||||
@@ -61,4 +61,4 @@ Exceptions
|
||||
You're trying to add something that doesn't quite taste right.
|
||||
|
||||
|
||||
Now, go start some :ref:`Tablib Development <development>`.
|
||||
Now, go start some :ref:`Tablib Development <development>`.
|
||||
|
||||
+26
-30
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Tablib documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue Oct 5 15:25:21 2010.
|
||||
@@ -10,22 +9,24 @@
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
from pkg_resources import get_distribution
|
||||
|
||||
# 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
|
||||
# sys.path.insert(0, os.path.abspath('..'))
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
# 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']
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage',
|
||||
'sphinx.ext.viewcode', 'sphinx.ext.intersphinx'
|
||||
]
|
||||
intersphinx_mapping = {'python': ('https://docs.python.org/3', None)}
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
@@ -40,17 +41,18 @@ source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Tablib'
|
||||
copyright = u'2011, Kenneth Reitz. Styles (modified) © Armin Ronacher'
|
||||
project = 'Tablib'
|
||||
copyright = '2019 Jazzband'
|
||||
|
||||
# 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.7'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
release = get_distribution('tablib').version
|
||||
# The short X.Y version.
|
||||
version = '.'.join(release.split('.')[:2])
|
||||
# for example take major/minor
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
@@ -81,7 +83,7 @@ add_function_parentheses = True
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'flask_theme_support.FlaskyStyle'
|
||||
# pygments_style = ''
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
@@ -91,7 +93,7 @@ pygments_style = 'flask_theme_support.FlaskyStyle'
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
html_theme = 'alabaster'
|
||||
|
||||
# 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
|
||||
@@ -120,7 +122,7 @@ html_theme = 'default'
|
||||
# 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']
|
||||
# html_static_path = ['static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
@@ -131,7 +133,11 @@ html_static_path = ['static']
|
||||
html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
html_sidebars = {
|
||||
'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
|
||||
'**': ['sidebarlogo.html', 'localtoc.html', 'relations.html',
|
||||
'sourcelink.html', 'searchbox.html']
|
||||
}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
@@ -178,24 +184,18 @@ htmlhelp_basename = 'Tablibdoc'
|
||||
# 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'),
|
||||
('index', 'Tablib.tex', 'Tablib Documentation',
|
||||
'Jazzband', '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
|
||||
@@ -225,10 +225,6 @@ latex_additional_files = ['krstyle.sty']
|
||||
# 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)
|
||||
('index', 'tablib', 'Tablib Documentation',
|
||||
['Jazzband'], 1)
|
||||
]
|
||||
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
html_theme_path = ['_themes']
|
||||
html_theme = 'kr'
|
||||
+82
-114
@@ -5,14 +5,10 @@ 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 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/
|
||||
.. _GitHub: https://github.com/jazzband/tablib/
|
||||
|
||||
|
||||
|
||||
@@ -42,41 +38,38 @@ Source Control
|
||||
--------------
|
||||
|
||||
|
||||
Tablib source is controlled with Git_, the lean, mean, distributed source control machine.
|
||||
Tablib source is controlled with Git_, the lean, mean, distributed source
|
||||
control machine.
|
||||
|
||||
The repository is publicly accessable.
|
||||
The repository is publicly accessible.
|
||||
|
||||
``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
|
||||
.. code-block:: console
|
||||
|
||||
git clone git://github.com/jazzband/tablib.git
|
||||
|
||||
The project is hosted on **GitHub**.
|
||||
|
||||
GitHub:
|
||||
https://github.com/jazzband/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.
|
||||
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_.
|
||||
When submitting patches, please place your feature/change in its own branch prior to opening a pull request 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
|
||||
.. _Git: https://git-scm.org
|
||||
.. _`Successful Git Branching Model`: https://nvie.com/posts/a-successful-git-branching-model/
|
||||
.. _git-flow: https://github.com/nvie/gitflow
|
||||
|
||||
|
||||
.. _newformats:
|
||||
@@ -87,57 +80,66 @@ 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*.
|
||||
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.
|
||||
From version 1.0, Tablib formats are class-based and can be dynamically
|
||||
registered.
|
||||
|
||||
:class:`tablib.core` follows a simple pattern for automatically utilizing your format throughout Tablib. Function names are crucial.
|
||||
|
||||
Example **tablib/formats/_xxx.py**: ::
|
||||
1. Write your custom format class::
|
||||
|
||||
class MyXXXFormatClass:
|
||||
title = 'xxx'
|
||||
|
||||
def export_set(dset):
|
||||
|
||||
@classmethod
|
||||
def export_set(cls, dset):
|
||||
....
|
||||
# returns string representation of given dataset
|
||||
|
||||
def export_book(dbook):
|
||||
|
||||
@classmethod
|
||||
def export_book(cls, dbook):
|
||||
....
|
||||
# returns string representation of given databook
|
||||
|
||||
def import_set(dset, in_stream):
|
||||
|
||||
@classmethod
|
||||
def import_set(cls, dset, in_stream):
|
||||
...
|
||||
# populates given Dataset with given datastream
|
||||
|
||||
def import_book(dbook, in_stream):
|
||||
|
||||
@classmethod
|
||||
def import_book(cls, dbook, in_stream):
|
||||
...
|
||||
# returns Databook instance
|
||||
|
||||
def detect(stream):
|
||||
|
||||
@classmethod
|
||||
def detect(cls, stream):
|
||||
...
|
||||
# returns True if given stream is parsable as xxx
|
||||
|
||||
.. admonition:: Excluding Support
|
||||
.. admonition:: Excluding Support
|
||||
|
||||
If the format excludes support for an import/export mechanism (*e.g.*
|
||||
:class:`csv <tablib.Dataset.csv>` excludes
|
||||
:class:`Databook <tablib.Databook>` support),
|
||||
simply don't define the respective class methods.
|
||||
Appropriate errors will be raised.
|
||||
|
||||
If the format excludes support for an import/export mechanism (*eg.* :class:`csv <tablib.Dataset.csv>` excludes :class:`Databook <tablib.Databook>` support), simply don't define the respective functions. Appropriate errors will be raised.
|
||||
2. Register your class::
|
||||
|
||||
2.
|
||||
from tablib.formats import registry
|
||||
|
||||
Add your new format module to the :class:`tablib.formats.avalable` tuple.
|
||||
registry.register('xxx', MyXXXFormatClass())
|
||||
|
||||
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>`.
|
||||
3. From then on, you should be able to use your new custom format as if it were
|
||||
a built-in Tablib format, e.g. using ``dataset.export('xxx')`` will use the
|
||||
``MyXXXFormatClass.export_set`` method.
|
||||
|
||||
.. _testing:
|
||||
|
||||
@@ -145,49 +147,32 @@ Tablib features a micro-framework for adding format support. The easiest way to
|
||||
Testing Tablib
|
||||
--------------
|
||||
|
||||
Testing is crucial to Tablib's stability. This stable project is used in production by many companies and developers, so it is important to be 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.
|
||||
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. ::
|
||||
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
|
||||
.. code-block:: console
|
||||
|
||||
|
||||
`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:
|
||||
$ tox
|
||||
|
||||
----------------------
|
||||
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.
|
||||
Every pull request is automatically tested and inspected upon receipt with `Travis CI`_.
|
||||
If you 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.
|
||||
|
||||
https://travis-ci.org/jazzband/tablib
|
||||
|
||||
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/
|
||||
.. _`Travis CI`: https://travis-ci.org/
|
||||
|
||||
|
||||
.. _docs:
|
||||
@@ -196,51 +181,34 @@ Additional reports will also be included here in the future, including :pep:`8`
|
||||
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.
|
||||
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. ::
|
||||
The Docs live in ``tablib/docs``.
|
||||
In order to build them, you will first need to install Sphinx.
|
||||
|
||||
$ pip install sphinx
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
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**).
|
||||
|
||||
$ pip install sphinx
|
||||
|
||||
|
||||
Then, to build an HTML version of the docs, simply run the following from the ``docs`` directory:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ make html
|
||||
|
||||
and `sphinx-to-github`_. ::
|
||||
Your ``docs/_build/html`` directory will then contain an HTML representation of the documentation,
|
||||
ready for publication on most web servers.
|
||||
|
||||
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.
|
||||
You can also generate the documentation in **epub**, **latex**, **json**, *&c* similarly.
|
||||
|
||||
.. _`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
|
||||
.. _`GitHub Pages`: https://pages.github.com
|
||||
|
||||
----------
|
||||
|
||||
Make sure to check out the :ref:`API Documentation <api>`.
|
||||
Make sure to check out the :ref:`API Documentation <api>`.
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
.. _formats:
|
||||
|
||||
=======
|
||||
Formats
|
||||
=======
|
||||
|
||||
Tablib supports a wide variety of different tabular formats, both for input and
|
||||
output. Moreover, you can :ref:`register your own formats <newformats>`.
|
||||
|
||||
cli
|
||||
===
|
||||
|
||||
The ``cli`` format is currently export-only. The exports produce a representation
|
||||
table suited to a terminal.
|
||||
|
||||
When exporting to a CLI you can pass the table format with the ``tablefmt``
|
||||
parameter, the supported formats are::
|
||||
|
||||
>>> import tabulate
|
||||
>>> list(tabulate._table_formats)
|
||||
['simple', 'plain', 'grid', 'fancy_grid', 'github', 'pipe', 'orgtbl',
|
||||
'jira', 'presto', 'psql', 'rst', 'mediawiki', 'moinmoin', 'youtrack',
|
||||
'html', 'latex', 'latex_raw', 'latex_booktabs', 'tsv', 'textile']
|
||||
|
||||
For example::
|
||||
|
||||
dataset.export("cli", tablefmt="github")
|
||||
dataset.export("cli", tablefmt="grid")
|
||||
|
||||
This format is optional, install Tablib with ``pip install tablib[cli]`` to
|
||||
make the format available.
|
||||
|
||||
csv
|
||||
===
|
||||
|
||||
When you import CSV data, you can specify if the first line of your data source
|
||||
is headers with the ``headers`` boolean parameter (defaults to ``True``)::
|
||||
|
||||
import tablib
|
||||
|
||||
tablib.import_set(your_data_stream, format='csv', headers=False)
|
||||
|
||||
When exporting with the ``csv`` format, the top row will contain headers, if
|
||||
they have been set. Otherwise, the top row will contain the first row of the
|
||||
dataset.
|
||||
|
||||
When importing a CSV data source or exporting a dataset as CSV, you can pass any
|
||||
parameter supported by the :py:func:`csv.reader` and :py:func:`csv.writer`
|
||||
functions. For example::
|
||||
|
||||
tablib.import_set(your_data_stream, format='csv', dialect='unix')
|
||||
|
||||
dataset.export('csv', delimiter=' ', quotechar='|')
|
||||
|
||||
.. admonition:: Line endings
|
||||
|
||||
Exporting uses \\r\\n line endings by default so, make sure to include
|
||||
``newline=''`` otherwise you will get a blank line between each row
|
||||
when you open the file in Excel::
|
||||
|
||||
with open('output.csv', 'w', newline='') as f:
|
||||
f.write(dataset.export('csv'))
|
||||
|
||||
If you do not do this, and you export the file on Windows, your
|
||||
CSV file will open in Excel with a blank line between each row.
|
||||
|
||||
dbf
|
||||
===
|
||||
|
||||
Import/export using the dBASE_ format.
|
||||
|
||||
.. admonition:: Binary Warning
|
||||
|
||||
The ``dbf`` format contains binary data, so make sure to write in binary
|
||||
mode::
|
||||
|
||||
with open('output.dbf', 'wb') as f:
|
||||
f.write(dataset.export('dbf')
|
||||
|
||||
.. _dBASE: https://en.wikipedia.org/wiki/DBase
|
||||
|
||||
df (DataFrame)
|
||||
==============
|
||||
|
||||
Import/export using the pandas_ DataFrame format. This format is optional,
|
||||
install Tablib with ``pip install tablib[pandas]`` to make the format available.
|
||||
|
||||
.. _pandas: https://pandas.pydata.org/
|
||||
|
||||
html
|
||||
====
|
||||
|
||||
The ``html`` format is currently export-only. The exports produce an HTML page
|
||||
with the data in a ``<table>``. If headers have been set, they will be used as
|
||||
table headers.
|
||||
|
||||
This format is optional, install Tablib with ``pip install tablib[html]`` to
|
||||
make the format available.
|
||||
|
||||
jira
|
||||
====
|
||||
|
||||
The ``jira`` format is currently export-only. Exports format the dataset
|
||||
according to the Jira table syntax::
|
||||
|
||||
||heading 1||heading 2||heading 3||
|
||||
|col A1|col A2|col A3|
|
||||
|col B1|col B2|col B3|
|
||||
|
||||
json
|
||||
====
|
||||
|
||||
Import/export using the JSON_ format. 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.
|
||||
|
||||
Import assumes (for now) that headers exist.
|
||||
|
||||
.. _JSON: http://json.org/
|
||||
|
||||
latex
|
||||
=====
|
||||
|
||||
Import/export using the LaTeX_ format. This format is export-only.
|
||||
If a title has been set, it will be exported as the table caption.
|
||||
|
||||
.. _LaTeX: https://www.latex-project.org/
|
||||
|
||||
ods
|
||||
===
|
||||
|
||||
Export data in OpenDocument Spreadsheet format. The ``ods`` format is currently
|
||||
export-only.
|
||||
|
||||
This format is optional, install Tablib with ``pip install tablib[ods]`` to
|
||||
make the format available.
|
||||
|
||||
.. admonition:: Binary Warning
|
||||
|
||||
:class:`Dataset.ods` contains binary data, so make sure to write in binary mode::
|
||||
|
||||
with open('output.ods', 'wb') as f:
|
||||
f.write(data.ods)
|
||||
|
||||
rst
|
||||
===
|
||||
|
||||
Export data as a reStructuredText_ table representation of a dataset. The
|
||||
``rst`` format is export-only.
|
||||
|
||||
Exporting returns a simple table if the text in the first column is never
|
||||
wrapped, otherwise returns a grid table::
|
||||
|
||||
>>> from tablib import Dataset
|
||||
>>> bits = ((0, 0), (1, 0), (0, 1), (1, 1))
|
||||
>>> data = Dataset()
|
||||
>>> data.headers = ['A', 'B', 'A and B']
|
||||
>>> for a, b in bits:
|
||||
... data.append([bool(a), bool(b), bool(a * b)])
|
||||
>>> table = data.export('rst')
|
||||
>>> table.split('\\n') == [
|
||||
... '===== ===== =====',
|
||||
... ' A B A and',
|
||||
... ' B ',
|
||||
... '===== ===== =====',
|
||||
... 'False False False',
|
||||
... 'True False False',
|
||||
... 'False True False',
|
||||
... 'True True True ',
|
||||
... '===== ===== =====',
|
||||
... ]
|
||||
True
|
||||
|
||||
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
|
||||
|
||||
tsv
|
||||
===
|
||||
|
||||
A variant of the csv_ format with tabulators as fields separators.
|
||||
|
||||
xls
|
||||
===
|
||||
|
||||
Import/export data in Legacy Excel Spreadsheet representation.
|
||||
|
||||
This format is optional, install Tablib with ``pip install tablib[xls]`` to
|
||||
make the format available.
|
||||
|
||||
.. note::
|
||||
|
||||
XLS files are limited to a maximum of 65,000 rows. Use xlsx_ to avoid this
|
||||
limitation.
|
||||
|
||||
.. admonition:: Binary Warning
|
||||
|
||||
The ``xls`` file format is binary, so make sure to write in binary mode::
|
||||
|
||||
with open('output.xls', 'wb') as f:
|
||||
f.write(data.export('xls'))
|
||||
|
||||
xlsx
|
||||
====
|
||||
|
||||
Import/export data in Excel 07+ Spreadsheet representation.
|
||||
|
||||
This format is optional, install Tablib with ``pip install tablib[xlsx]`` to
|
||||
make the format available.
|
||||
|
||||
.. admonition:: Binary Warning
|
||||
|
||||
The ``xlsx`` file format is binary, so make sure to write in binary mode::
|
||||
|
||||
with open('output.xlsx', 'wb') as f:
|
||||
f.write(data.export('xlsx'))
|
||||
|
||||
yaml
|
||||
====
|
||||
|
||||
Import/export data in the YAML_ format.
|
||||
When exporting, 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.
|
||||
|
||||
Import assumes (for now) that headers exist.
|
||||
|
||||
This format is optional, install Tablib with ``pip install tablib[yaml]`` to
|
||||
make the format available.
|
||||
|
||||
.. _YAML: https://yaml.org
|
||||
+66
-9
@@ -1,30 +1,82 @@
|
||||
.. 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.
|
||||
contain the root ``toctree`` directive.
|
||||
|
||||
Tablib: Pythonic Tabular Datasets
|
||||
Tablib: Pythonic Tabular Datasets
|
||||
=================================
|
||||
|
||||
Release |version|.
|
||||
Release v\ |version|. (:ref:`Installation <install>`)
|
||||
|
||||
.. 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.
|
||||
Tablib is an `MIT Licensed <https://mit-license.org/>`_ 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.
|
||||
|
||||
::
|
||||
|
||||
>>> data = tablib.Dataset(headers=['First Name', 'Last Name', 'Age'])
|
||||
>>> for i in [('Kenneth', 'Reitz', 22), ('Bessie', 'Monke', 21)]:
|
||||
... data.append(i)
|
||||
|
||||
|
||||
>>> print(data.export('json'))
|
||||
[{"Last Name": "Reitz", "First Name": "Kenneth", "Age": 22}, {"Last Name": "Monke", "First Name": "Bessie", "Age": 21}]
|
||||
|
||||
>>> print(data.export('yaml'))
|
||||
- {Age: 22, First Name: Kenneth, Last Name: Reitz}
|
||||
- {Age: 21, First Name: Bessie, Last Name: Monke}
|
||||
|
||||
>>> data.export('xlsx')
|
||||
<redacted binary data>
|
||||
|
||||
>>> data.export('df')
|
||||
First Name Last Name Age
|
||||
0 Kenneth Reitz 22
|
||||
1 Bessie Monke 21
|
||||
|
||||
|
||||
Testimonials
|
||||
------------
|
||||
|
||||
`National Geographic <https://www.nationalgeographic.com/>`_,
|
||||
`Digg, Inc <https://digg.com/>`_,
|
||||
`Northrop Grumman <https://www.northropgrumman.com/>`_,
|
||||
`Discovery Channel <https://dsc.discovery.com/>`_,
|
||||
and `The Sunlight Foundation <https://sunlightfoundation.com/>`_ use Tablib internally.
|
||||
|
||||
|
||||
|
||||
**Greg Thorton**
|
||||
Tablib by @kennethreitz saved my life.
|
||||
I had to consolidate like 5 huge poorly maintained lists of domains and data.
|
||||
It was a breeze!
|
||||
|
||||
**Dave Coutts**
|
||||
It's turning into one of my most used modules of 2010.
|
||||
You really hit a sweet spot for managing tabular data with a minimal amount of code and effort.
|
||||
|
||||
**Joshua Ourisman**
|
||||
Tablib has made it so much easier to deal with the inevitable 'I want an Excel file!' requests from clients...
|
||||
|
||||
**Brad Montgomery**
|
||||
I think you nailed the "Python Zen" with tablib.
|
||||
Thanks again for an awesome lib!
|
||||
|
||||
I recommend you start with :ref:`Installation <install>`.
|
||||
|
||||
User's Guide
|
||||
------------
|
||||
@@ -46,6 +98,11 @@ This part of the documentation, which is mostly prose, begins with some backgrou
|
||||
|
||||
tutorial
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
formats
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
@@ -61,4 +118,4 @@ method, this part of the documentation is for you.
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api
|
||||
api
|
||||
|
||||
+42
-35
@@ -1,8 +1,9 @@
|
||||
.. _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>`.
|
||||
This part of the documentation covers the installation of Tablib. The first step to using any software package is getting it properly installed.
|
||||
|
||||
|
||||
.. _installing:
|
||||
@@ -11,68 +12,74 @@ This part of the documentation covers the installation of Tablib. The first step
|
||||
Installing Tablib
|
||||
-----------------
|
||||
|
||||
To install Tablib, it only takes one simple command. ::
|
||||
Distribute & Pip
|
||||
----------------
|
||||
|
||||
$ pip install tablib
|
||||
Of course, the recommended way to install Tablib is with `pip <https://pip.pypa.io>`_:
|
||||
|
||||
Or, if you must: ::
|
||||
.. code-block:: console
|
||||
|
||||
$ easy_install tablib
|
||||
|
||||
But, you really shouldn't do that.
|
||||
$ pip install tablib
|
||||
|
||||
You can also choose to install more dependencies to have more import/export
|
||||
formats available:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install tablib[xlsx]
|
||||
|
||||
Or all possible formats:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install tablib[all]
|
||||
|
||||
which is equivalent to:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install tablib[html, pandas, ods, xls, xlsx, yaml]
|
||||
|
||||
-------------------
|
||||
Download the Source
|
||||
-------------------
|
||||
|
||||
You can also install tablib from source. The latest release (|version|) is available from GitHub.
|
||||
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. ::
|
||||
|
||||
Once you have a copy of the source,
|
||||
you can embed it in your Python package,
|
||||
or install it into your site-packages easily.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ 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
|
||||
|
||||
.. _tarball: https://github.com/jazzband/tablib/tarball/master
|
||||
.. _zipball: https://github.com/jazzband/tablib/zipball/master
|
||||
|
||||
|
||||
.. _updates:
|
||||
|
||||
Staying Updated
|
||||
---------------
|
||||
|
||||
The latest version of Tablib will always be available here:
|
||||
The latest version of Tablib will always be available here:
|
||||
|
||||
* PyPi: http://pypi.python.org/pypi/tablib/
|
||||
* GitHub: http://github.com/kennethreitz/tablib/
|
||||
* PyPI: https://pypi.org/project/tablib/
|
||||
* GitHub: https://github.com/jazzband/tablib/
|
||||
|
||||
When a new version is available, upgrading is simple. ::
|
||||
When a new version is available, upgrading is simple::
|
||||
|
||||
$ pip install tablib --upgrade
|
||||
$ pip install tablib --upgrade
|
||||
|
||||
|
||||
Now, go get a :ref:`Quick Start <quickstart>`.
|
||||
Now, go get a :ref:`Quick Start <quickstart>`.
|
||||
|
||||
+13
-34
@@ -3,16 +3,18 @@
|
||||
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.
|
||||
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
|
||||
---------
|
||||
Philosophy
|
||||
----------
|
||||
|
||||
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.
|
||||
@@ -21,27 +23,14 @@ Tablib was developed with a few :pep:`20` idioms in mind.
|
||||
|
||||
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.
|
||||
Tablib is released under terms of `The MIT License`_.
|
||||
|
||||
Copyright 2017 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
|
||||
@@ -61,24 +50,14 @@ 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.
|
||||
|
||||
.. _`The MIT License`: https://opensource.org/licenses/mit-license.php
|
||||
|
||||
.. _pythonsupport:
|
||||
|
||||
Pythons Supported
|
||||
-----------------
|
||||
|
||||
At this time, the following Python platforms are officially supported:
|
||||
Python 3.5+ is officially supported.
|
||||
|
||||
* cPython 2.5
|
||||
* cPython 2.6
|
||||
* cPython 2.7
|
||||
* cPython 3.1
|
||||
* cPython 3.2
|
||||
* PyPy-c 1.4
|
||||
Now, go :ref:`install Tablib <install>`.
|
||||
|
||||
Support for other Pythons will be rolled out soon.
|
||||
|
||||
|
||||
|
||||
|
||||
Now, go :ref:`Install Tablib <install>`.
|
||||
+107
-45
@@ -5,10 +5,10 @@ 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.
|
||||
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:
|
||||
|
||||
@@ -16,7 +16,7 @@ First, make sure that:
|
||||
* Tablib is :ref:`up-to-date <updates>`
|
||||
|
||||
|
||||
Lets gets started with some simple use cases and examples.
|
||||
Let's get started with some simple use cases and examples.
|
||||
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ You can now start filling this :class:`Dataset <tablib.Dataset>` object with dat
|
||||
|
||||
.. admonition:: Example Context
|
||||
|
||||
From here on out, if you see ``data``, assume that it's a fresh :class:`Dataset <tablib.Dataset>` object.
|
||||
From here on out, if you see ``data``, assume that it's a fresh
|
||||
:class:`Dataset <tablib.Dataset>` object.
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +57,7 @@ Let's say you want to collect a simple list of names. ::
|
||||
# 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`.
|
||||
You can get a nice, Pythonic view of the dataset at any time with :class:`Dataset.dict`::
|
||||
|
||||
>>> data.dict
|
||||
[('Kenneth', 'Reitz'), ('Bessie', 'Monke')]
|
||||
@@ -68,14 +69,16 @@ Adding Headers
|
||||
--------------
|
||||
|
||||
|
||||
It's time enhance our :class:`Dataset` by giving our columns some titles. To do so, set :class:`Dataset.headers`. ::
|
||||
It's time to 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'}]
|
||||
[{'Last Name': 'Reitz', 'First Name': 'Kenneth'},
|
||||
{'Last Name': 'Monke', 'First Name': 'Bessie'}]
|
||||
|
||||
|
||||
|
||||
@@ -87,16 +90,34 @@ 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')
|
||||
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}]
|
||||
[{'Last Name': 'Reitz', 'First Name': 'Kenneth', 'Age': 22},
|
||||
{'Last Name': 'Monke', 'First Name': 'Bessie', 'Age': 20}]
|
||||
|
||||
It's that easy.
|
||||
|
||||
|
||||
--------------
|
||||
Importing Data
|
||||
--------------
|
||||
Creating a :class:`tablib.Dataset` object by importing a pre-existing file is simple. ::
|
||||
|
||||
with open('data.csv', 'r') as fh:
|
||||
imported_data = Dataset().load(fh)
|
||||
|
||||
This detects what sort of data is being passed in, and uses an appropriate formatter to do the import. So you can import from a variety of different file types.
|
||||
|
||||
.. admonition:: Source without headers
|
||||
|
||||
When the format is :class:`csv <Dataset.csv>`, :class:`tsv <Dataset.tsv>`, :class:`dbf <Dataset.dbf>`, :class:`xls <Dataset.xls>` or :class:`xlsx <Dataset.xlsx>`, and the data source does not have headers, the import should be done as follows ::
|
||||
|
||||
with open('data.csv', 'r') as fh:
|
||||
imported_data = Dataset().load(fh, headers=False)
|
||||
|
||||
--------------
|
||||
Exporting Data
|
||||
--------------
|
||||
@@ -105,28 +126,36 @@ Tablib's killer feature is the ability to export your :class:`Dataset` objects i
|
||||
|
||||
**Comma-Separated Values** ::
|
||||
|
||||
>>> data.csv
|
||||
>>> data.export('csv')
|
||||
Last Name,First Name,Age
|
||||
Reitz,Kenneth,22
|
||||
Monke,Bessie,20
|
||||
|
||||
**JavaScript Object Notation** ::
|
||||
|
||||
>>> data.json
|
||||
>>> data.export('json')
|
||||
[{"Last Name": "Reitz", "First Name": "Kenneth", "Age": 22}, {"Last Name": "Monke", "First Name": "Bessie", "Age": 20}]
|
||||
|
||||
|
||||
**YAML Ain't Markup Language** ::
|
||||
|
||||
>>> data.yaml
|
||||
>>> data.export('yaml')
|
||||
- {Age: 22, First Name: Kenneth, Last Name: Reitz}
|
||||
- {Age: 20, First Name: Bessie, Last Name: Monke}
|
||||
|
||||
|
||||
**Microsoft Excel** ::
|
||||
|
||||
>>> data.xls
|
||||
<censored binary data>
|
||||
>>> data.export('xls')
|
||||
<redacted binary data>
|
||||
|
||||
|
||||
**Pandas DataFrame** ::
|
||||
|
||||
>>> data.export('df')
|
||||
First Name Last Name Age
|
||||
0 Kenneth Reitz 22
|
||||
1 Bessie Monke 21
|
||||
|
||||
|
||||
------------------------
|
||||
@@ -140,12 +169,20 @@ You can slice and dice your data, just like a standard Python list. ::
|
||||
('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.
|
||||
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']
|
||||
|
||||
You can also access the column using its index. ::
|
||||
|
||||
>>> data.headers
|
||||
['Last Name', 'First Name', 'Age']
|
||||
>>> data.get_col(1)
|
||||
['Kenneth', 'Bessie']
|
||||
|
||||
Let's find the average age. ::
|
||||
|
||||
>>> ages = data['Age']
|
||||
@@ -158,11 +195,11 @@ Let's find the average age. ::
|
||||
Removing Rows & Columns
|
||||
-----------------------
|
||||
|
||||
It's easier than you could imagine. ::
|
||||
It's easier than you could imagine. Delete a column::
|
||||
|
||||
>>> del data['Col Name']
|
||||
|
||||
::
|
||||
Delete a range of rows::
|
||||
|
||||
>>> del data[0:12]
|
||||
|
||||
@@ -171,8 +208,7 @@ It's easier than you could imagine. ::
|
||||
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>`
|
||||
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.
|
||||
|
||||
@@ -185,9 +221,11 @@ 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).
|
||||
Thanks to Josh Ourisman, Tablib now supports adding dynamic columns.
|
||||
A dynamic column is a single callable object (*e.g.* 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. ::
|
||||
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
|
||||
|
||||
@@ -195,11 +233,11 @@ Let's add a dynamic column to our :class:`Dataset` object. In this example, we h
|
||||
"""Returns a random integer for entry."""
|
||||
return (random.randint(60,100)/100.0)
|
||||
|
||||
data.append(col=[random_grade], header='Grade')
|
||||
data.append_col(random_grade, header='Grade')
|
||||
|
||||
Let's have a look at our data. ::
|
||||
|
||||
>>> data.yaml
|
||||
>>> data.export('yaml')
|
||||
- {Age: 22, First Name: Kenneth, Grade: 0.6, Last Name: Reitz}
|
||||
- {Age: 20, First Name: Bessie, Grade: 0.75, Last Name: Monke}
|
||||
|
||||
@@ -209,7 +247,8 @@ Let's remove that column. ::
|
||||
>>> del data['Grade']
|
||||
|
||||
|
||||
When you add a dynamic column, the first argument that is passed in to the given callable is the current data row. You can use this to perform calculations against your data row.
|
||||
When you add a dynamic column, the first argument that is passed in to the given callable is the current data row.
|
||||
You can use this to perform calculations against your data row.
|
||||
|
||||
For example, we can use the data available in the row to guess the gender of a student. ::
|
||||
|
||||
@@ -229,7 +268,7 @@ For example, we can use the data available in the row to guess the gender of a s
|
||||
|
||||
Adding this function to our dataset as a dynamic column would result in: ::
|
||||
|
||||
>>> data.yaml
|
||||
>>> data.export('yaml')
|
||||
- {Age: 22, First Name: Kenneth, Gender: Male, Last Name: Reitz}
|
||||
- {Age: 20, First Name: Bessie, Gender: Female, Last Name: Monke}
|
||||
|
||||
@@ -243,9 +282,11 @@ 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`.
|
||||
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 to 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. ::
|
||||
|
||||
@@ -253,34 +294,56 @@ Let's tag some students. ::
|
||||
|
||||
students.headers = ['first', 'last']
|
||||
|
||||
students.append(['Kenneth', 'Reitz'], tags=['male', 'technical'])
|
||||
students.append(['Bessie', 'Monke'], tags=['female', 'creative'])
|
||||
students.rpush(['Kenneth', 'Reitz'], tags=['male', 'technical'])
|
||||
students.rpush(['Daniel', 'Dupont'], tags=['male', 'creative' ])
|
||||
students.rpush(['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. ::
|
||||
Now that we have extra meta-data on our rows, we can easily filter our :class:`Dataset`. Let's just see Female students. ::
|
||||
|
||||
>>> students.filter(['female']).yaml
|
||||
- {first: Bessie, Last: Monke}
|
||||
|
||||
>>> data.filter(['male']).yaml
|
||||
- {first: Kenneth, Last: Reitz}
|
||||
By default, when you pass a list of tags you get filter type or. ::
|
||||
|
||||
>>> students.filter(['female', 'creative']).yaml
|
||||
- {first: Daniel, Last: Dupont}
|
||||
- {first: Bessie, Last: Monke}
|
||||
|
||||
Using chaining you can get a filter type and. ::
|
||||
|
||||
>>> students.filter(['female']).filter(['creative']).yaml
|
||||
- {first: Bessie, Last: Monke}
|
||||
|
||||
It's that simple. The original :class:`Dataset` is untouched.
|
||||
|
||||
Open an Excel Workbook and read first sheet
|
||||
-------------------------------------------
|
||||
|
||||
Open an Excel 2007 and later workbook with a single sheet (or a workbook with multiple sheets but you just want the first sheet). ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
with open('my_excel_file.xlsx', 'rb') as fh:
|
||||
data.load(fh, 'xlsx')
|
||||
print(data)
|
||||
|
||||
Excel Workbook With Multiple Sheets
|
||||
------------------------------------
|
||||
|
||||
When dealing with a large number of :class:`Datasets <Dataset>` in spreadsheet format, it's quite common to group multiple spreadsheets into a single Excel file, known as a Workbook. Tablib makes it extremely easy to build workbooks with the handy, :class:`Databook` class.
|
||||
When 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 them to a :class:`Databook` object... ::
|
||||
|
||||
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])
|
||||
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)
|
||||
f.write(book.export('xls'))
|
||||
|
||||
The resulting **students.xls** file will contain a separate spreadsheet for each :class:`Dataset` object in the :class:`Databook`.
|
||||
The resulting ``students.xls`` file will contain a separate spreadsheet for each :class:`Dataset` object in the :class:`Databook`.
|
||||
|
||||
.. admonition:: Binary Warning
|
||||
|
||||
@@ -295,9 +358,8 @@ Separators
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
When, it's often useful to create a blank row containing information on the upcoming data. So,
|
||||
|
||||
|
||||
When constructing a spreadsheet,
|
||||
it's often useful to create a blank row containing information on the upcoming data. So,
|
||||
|
||||
::
|
||||
|
||||
@@ -329,7 +391,7 @@ When, it's often useful to create a blank row containing information on the upco
|
||||
|
||||
# Write spreadsheet to disk
|
||||
with open('grades.xls', 'wb') as f:
|
||||
f.write(tests.xls)
|
||||
f.write(tests.export('xls'))
|
||||
|
||||
The resulting **tests.xls** will have the following layout:
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
[tool.isort]
|
||||
force_grid_wrap = 0
|
||||
include_trailing_comma = true
|
||||
known_third_party = ["MarkupPy", "odf", "openpyxl", "pkg_resources", "setuptools", "tablib", "xlrd", "xlwt", "yaml"]
|
||||
line_length = 88
|
||||
multi_line_output = 3
|
||||
use_parentheses = true
|
||||
@@ -0,0 +1,4 @@
|
||||
[pytest]
|
||||
norecursedirs = .git .*
|
||||
addopts = -rsxX --showlocals --tb=native --cov=tablib --cov=tests --cov-report xml --cov-report term --cov-report html
|
||||
python_paths = .
|
||||
@@ -1,55 +1,50 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from distutils.core import setup
|
||||
|
||||
|
||||
def publish():
|
||||
"""Publish to PyPi"""
|
||||
os.system("python setup.py sdist upload")
|
||||
|
||||
if sys.argv[-1] == "publish":
|
||||
publish()
|
||||
sys.exit()
|
||||
|
||||
required = []
|
||||
|
||||
if sys.version_info[:2] < (2,6):
|
||||
required.append('simplejson')
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
setup(
|
||||
name='tablib',
|
||||
version='0.9.7',
|
||||
use_scm_version=True,
|
||||
setup_requires=['setuptools_scm'],
|
||||
description='Format agnostic tabular data library (XLS, JSON, YAML, CSV)',
|
||||
long_description=open('README.rst').read() + '\n\n' +
|
||||
open('HISTORY.rst').read(),
|
||||
long_description=(
|
||||
open('README.md').read() + '\n\n' + open('HISTORY.md').read()
|
||||
),
|
||||
long_description_content_type="text/markdown",
|
||||
author='Kenneth Reitz',
|
||||
author_email='me@kennethreitz.com',
|
||||
url='http://tablib.org',
|
||||
packages= [
|
||||
'tablib', 'tablib.formats',
|
||||
'tablib.packages',
|
||||
'tablib.packages.xlwt',
|
||||
'tablib.packages.openpyxl',
|
||||
'tablib.packages.yaml',
|
||||
'tablib.packages.unicodecsv'
|
||||
],
|
||||
install_requires=required,
|
||||
author_email='me@kennethreitz.org',
|
||||
maintainer='Jazzband',
|
||||
maintainer_email='roadies@jazzband.co',
|
||||
url='https://tablib.readthedocs.io',
|
||||
project_urls={
|
||||
"Documentation": "https://tablib.readthedocs.io",
|
||||
"Source": "https://github.com/jazzband/tablib",
|
||||
},
|
||||
packages=find_packages(where="src"),
|
||||
package_dir={"": "src"},
|
||||
license='MIT',
|
||||
classifiers=(
|
||||
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',
|
||||
),
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
],
|
||||
python_requires='>=3.5',
|
||||
extras_require={
|
||||
'all': ['markuppy', 'odfpy', 'openpyxl>=2.4.0', 'pandas', 'pyyaml', 'tabulate', 'xlrd', 'xlwt'],
|
||||
'cli': ['tabulate'],
|
||||
'html': ['markuppy'],
|
||||
'ods': ['odfpy'],
|
||||
'pandas': ['pandas'],
|
||||
'xls': ['xlrd', 'xlwt'],
|
||||
'xlsx': ['openpyxl>=2.4.0'],
|
||||
'yaml': ['pyyaml'],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
""" Tablib. """
|
||||
from pkg_resources import DistributionNotFound, get_distribution
|
||||
from tablib.core import ( # noqa: F401
|
||||
Databook,
|
||||
Dataset,
|
||||
InvalidDatasetType,
|
||||
InvalidDimensions,
|
||||
UnsupportedFormat,
|
||||
detect_format,
|
||||
import_book,
|
||||
import_set,
|
||||
)
|
||||
|
||||
try:
|
||||
__version__ = get_distribution(__name__).version
|
||||
except DistributionNotFound:
|
||||
# package is not installed
|
||||
__version__ = None
|
||||
+441
-343
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
class InvalidDatasetType(Exception):
|
||||
"Only Datasets can be added to a DataBook"
|
||||
|
||||
|
||||
class InvalidDimensions(Exception):
|
||||
"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"
|
||||
@@ -0,0 +1,135 @@
|
||||
""" Tablib - formats
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
from functools import partialmethod
|
||||
from importlib import import_module
|
||||
from importlib.util import find_spec
|
||||
|
||||
from tablib.exceptions import UnsupportedFormat
|
||||
from tablib.utils import normalize_input
|
||||
|
||||
from ._csv import CSVFormat
|
||||
from ._json import JSONFormat
|
||||
from ._tsv import TSVFormat
|
||||
|
||||
uninstalled_format_messages = {
|
||||
"cli": {"package_name": "tabulate package", "extras_name": "cli"},
|
||||
"df": {"package_name": "pandas package", "extras_name": "pandas"},
|
||||
"html": {"package_name": "MarkupPy package", "extras_name": "html"},
|
||||
"ods": {"package_name": "odfpy package", "extras_name": "ods"},
|
||||
"xls": {"package_name": "odfpy and xlwt packages", "extras_name": "ods"},
|
||||
"xlsx": {"package_name": "openpyxl package", "extras_name": "xlsx"},
|
||||
"yaml": {"package_name": "pyyaml package", "extras_name": "yaml"},
|
||||
}
|
||||
|
||||
|
||||
def load_format_class(dotted_path):
|
||||
try:
|
||||
module_path, class_name = dotted_path.rsplit('.', 1)
|
||||
return getattr(import_module(module_path), class_name)
|
||||
except (ValueError, AttributeError) as err:
|
||||
raise ImportError("Unable to load format class '{}' ({})".format(dotted_path, err))
|
||||
|
||||
|
||||
class FormatDescriptorBase:
|
||||
def __init__(self, key, format_or_path):
|
||||
self.key = key
|
||||
self._format_path = None
|
||||
if isinstance(format_or_path, str):
|
||||
self._format = None
|
||||
self._format_path = format_or_path
|
||||
else:
|
||||
self._format = format_or_path
|
||||
|
||||
def ensure_format_loaded(self):
|
||||
if self._format is None:
|
||||
self._format = load_format_class(self._format_path)
|
||||
|
||||
|
||||
class ImportExportBookDescriptor(FormatDescriptorBase):
|
||||
def __get__(self, obj, cls, **kwargs):
|
||||
self.ensure_format_loaded()
|
||||
return self._format.export_book(obj, **kwargs)
|
||||
|
||||
def __set__(self, obj, val):
|
||||
self.ensure_format_loaded()
|
||||
return self._format.import_book(obj, normalize_input(val))
|
||||
|
||||
|
||||
class ImportExportSetDescriptor(FormatDescriptorBase):
|
||||
def __get__(self, obj, cls, **kwargs):
|
||||
self.ensure_format_loaded()
|
||||
return self._format.export_set(obj, **kwargs)
|
||||
|
||||
def __set__(self, obj, val):
|
||||
self.ensure_format_loaded()
|
||||
return self._format.import_set(obj, normalize_input(val))
|
||||
|
||||
|
||||
class Registry:
|
||||
_formats = OrderedDict()
|
||||
|
||||
def register(self, key, format_or_path):
|
||||
from tablib.core import Databook, Dataset
|
||||
|
||||
# Create Databook.<format> read or read/write properties
|
||||
setattr(Databook, key, ImportExportBookDescriptor(key, format_or_path))
|
||||
|
||||
# Create Dataset.<format> read or read/write properties,
|
||||
# and Dataset.get_<format>/set_<format> methods.
|
||||
setattr(Dataset, key, ImportExportSetDescriptor(key, format_or_path))
|
||||
try:
|
||||
setattr(Dataset, 'get_%s' % key, partialmethod(Dataset._get_in_format, key))
|
||||
setattr(Dataset, 'set_%s' % key, partialmethod(Dataset._set_in_format, key))
|
||||
except AttributeError:
|
||||
setattr(Dataset, 'get_%s' % key, partialmethod(Dataset._get_in_format, key))
|
||||
|
||||
self._formats[key] = format_or_path
|
||||
|
||||
def register_builtins(self):
|
||||
# Registration ordering matters for autodetection.
|
||||
self.register('json', JSONFormat())
|
||||
# xlsx before as xls (xlrd) can also read xlsx
|
||||
if find_spec('openpyxl'):
|
||||
self.register('xlsx', 'tablib.formats._xlsx.XLSXFormat')
|
||||
if find_spec('xlrd') and find_spec('xlwt'):
|
||||
self.register('xls', 'tablib.formats._xls.XLSFormat')
|
||||
if find_spec('yaml'):
|
||||
self.register('yaml', 'tablib.formats._yaml.YAMLFormat')
|
||||
self.register('csv', CSVFormat())
|
||||
self.register('tsv', TSVFormat())
|
||||
if find_spec('odf'):
|
||||
self.register('ods', 'tablib.formats._ods.ODSFormat')
|
||||
self.register('dbf', 'tablib.formats._dbf.DBFFormat')
|
||||
if find_spec('MarkupPy'):
|
||||
self.register('html', 'tablib.formats._html.HTMLFormat')
|
||||
self.register('jira', 'tablib.formats._jira.JIRAFormat')
|
||||
self.register('latex', 'tablib.formats._latex.LATEXFormat')
|
||||
if find_spec('pandas'):
|
||||
self.register('df', 'tablib.formats._df.DataFrameFormat')
|
||||
self.register('rst', 'tablib.formats._rst.ReSTFormat')
|
||||
if find_spec('tabulate'):
|
||||
self.register('cli', 'tablib.formats._cli.CLIFormat')
|
||||
|
||||
def formats(self):
|
||||
for key, frm in self._formats.items():
|
||||
if isinstance(frm, str):
|
||||
self._formats[key] = load_format_class(frm)
|
||||
yield self._formats[key]
|
||||
|
||||
def get_format(self, key):
|
||||
if key not in self._formats:
|
||||
if key in uninstalled_format_messages:
|
||||
raise UnsupportedFormat(
|
||||
"The '{key}' format is not available. You may want to install the "
|
||||
"{package_name} (or `pip install tablib[{extras_name}]`).".format(
|
||||
**uninstalled_format_messages[key], key=key
|
||||
)
|
||||
)
|
||||
raise UnsupportedFormat("Tablib has no format '%s' or it is not registered." % key)
|
||||
if isinstance(self._formats[key], str):
|
||||
self._formats[key] = load_format_class(self._formats[key])
|
||||
return self._formats[key]
|
||||
|
||||
|
||||
registry = Registry()
|
||||
@@ -0,0 +1,20 @@
|
||||
"""Tablib - Command-line Interface table export support.
|
||||
|
||||
Generates a representation for CLI from the dataset.
|
||||
Wrapper for tabulate library.
|
||||
"""
|
||||
from tabulate import tabulate as Tabulate
|
||||
|
||||
|
||||
class CLIFormat:
|
||||
""" Class responsible to export to CLI Format """
|
||||
title = 'cli'
|
||||
DEFAULT_FMT = 'plain'
|
||||
|
||||
@classmethod
|
||||
def export_set(cls, dataset, **kwargs):
|
||||
"""Returns CLI representation of a Dataset."""
|
||||
if dataset.headers:
|
||||
kwargs.setdefault('headers', dataset.headers)
|
||||
kwargs.setdefault('tablefmt', cls.DEFAULT_FMT)
|
||||
return Tabulate(dataset, **kwargs)
|
||||
@@ -0,0 +1,58 @@
|
||||
""" Tablib - *SV Support.
|
||||
"""
|
||||
|
||||
import csv
|
||||
from io import StringIO
|
||||
|
||||
|
||||
class CSVFormat:
|
||||
title = 'csv'
|
||||
extensions = ('csv',)
|
||||
|
||||
DEFAULT_DELIMITER = ','
|
||||
|
||||
@classmethod
|
||||
def export_stream_set(cls, dataset, **kwargs):
|
||||
"""Returns CSV representation of Dataset as file-like."""
|
||||
stream = StringIO()
|
||||
|
||||
kwargs.setdefault('delimiter', cls.DEFAULT_DELIMITER)
|
||||
|
||||
_csv = csv.writer(stream, **kwargs)
|
||||
|
||||
for row in dataset._package(dicts=False):
|
||||
_csv.writerow(row)
|
||||
|
||||
stream.seek(0)
|
||||
return stream
|
||||
|
||||
@classmethod
|
||||
def export_set(cls, dataset, **kwargs):
|
||||
"""Returns CSV representation of Dataset."""
|
||||
stream = cls.export_stream_set(dataset, **kwargs)
|
||||
return stream.getvalue()
|
||||
|
||||
@classmethod
|
||||
def import_set(cls, dset, in_stream, headers=True, **kwargs):
|
||||
"""Returns dataset from CSV stream."""
|
||||
|
||||
dset.wipe()
|
||||
|
||||
kwargs.setdefault('delimiter', cls.DEFAULT_DELIMITER)
|
||||
|
||||
rows = csv.reader(in_stream, **kwargs)
|
||||
for i, row in enumerate(rows):
|
||||
|
||||
if (i == 0) and (headers):
|
||||
dset.headers = row
|
||||
elif row:
|
||||
dset.append(row)
|
||||
|
||||
@classmethod
|
||||
def detect(cls, stream, delimiter=None):
|
||||
"""Returns True if given stream is valid CSV."""
|
||||
try:
|
||||
csv.Sniffer().sniff(stream.read(1024), delimiters=delimiter or cls.DEFAULT_DELIMITER)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
@@ -0,0 +1,66 @@
|
||||
""" Tablib - DBF Support.
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from tablib.packages.dbfpy import dbf, dbfnew
|
||||
from tablib.packages.dbfpy import record as dbfrecord
|
||||
|
||||
|
||||
class DBFFormat:
|
||||
title = 'dbf'
|
||||
extensions = ('csv',)
|
||||
|
||||
DEFAULT_ENCODING = 'utf-8'
|
||||
|
||||
@classmethod
|
||||
def export_set(cls, dataset):
|
||||
"""Returns DBF representation of a Dataset"""
|
||||
new_dbf = dbfnew.dbf_new()
|
||||
temp_file, temp_uri = tempfile.mkstemp()
|
||||
|
||||
# create the appropriate fields based on the contents of the first row
|
||||
first_row = dataset[0]
|
||||
for fieldname, field_value in zip(dataset.headers, first_row):
|
||||
if type(field_value) in [int, float]:
|
||||
new_dbf.add_field(fieldname, 'N', 10, 8)
|
||||
else:
|
||||
new_dbf.add_field(fieldname, 'C', 80)
|
||||
|
||||
new_dbf.write(temp_uri)
|
||||
|
||||
dbf_file = dbf.Dbf(temp_uri, readOnly=0)
|
||||
for row in dataset:
|
||||
record = dbfrecord.DbfRecord(dbf_file)
|
||||
for fieldname, field_value in zip(dataset.headers, row):
|
||||
record[fieldname] = field_value
|
||||
record.store()
|
||||
|
||||
dbf_file.close()
|
||||
dbf_stream = open(temp_uri, 'rb')
|
||||
stream = io.BytesIO(dbf_stream.read())
|
||||
dbf_stream.close()
|
||||
os.close(temp_file)
|
||||
os.remove(temp_uri)
|
||||
return stream.getvalue()
|
||||
|
||||
@classmethod
|
||||
def import_set(cls, dset, in_stream, headers=True):
|
||||
"""Returns a dataset from a DBF stream."""
|
||||
|
||||
dset.wipe()
|
||||
_dbf = dbf.Dbf(in_stream)
|
||||
dset.headers = _dbf.fieldNames
|
||||
for record in range(_dbf.recordCount):
|
||||
row = [_dbf[record][f] for f in _dbf.fieldNames]
|
||||
dset.append(row)
|
||||
|
||||
@classmethod
|
||||
def detect(cls, stream):
|
||||
"""Returns True if the given stream is valid DBF"""
|
||||
try:
|
||||
_dbf = dbf.Dbf(stream, readOnly=True)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
@@ -0,0 +1,41 @@
|
||||
""" Tablib - DataFrame Support.
|
||||
"""
|
||||
|
||||
try:
|
||||
from pandas import DataFrame
|
||||
except ImportError:
|
||||
DataFrame = None
|
||||
|
||||
|
||||
class DataFrameFormat:
|
||||
title = 'df'
|
||||
extensions = ('df',)
|
||||
|
||||
@classmethod
|
||||
def detect(cls, stream):
|
||||
"""Returns True if given stream is a DataFrame."""
|
||||
if DataFrame is None:
|
||||
return False
|
||||
elif isinstance(stream, DataFrame):
|
||||
return True
|
||||
try:
|
||||
DataFrame(stream.read())
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def export_set(cls, dset, index=None):
|
||||
"""Returns DataFrame representation of DataBook."""
|
||||
if DataFrame is None:
|
||||
raise NotImplementedError(
|
||||
'DataFrame Format requires `pandas` to be installed.'
|
||||
' Try `pip install tablib[pandas]`.')
|
||||
dataframe = DataFrame(dset.dict, columns=dset.headers)
|
||||
return dataframe
|
||||
|
||||
@classmethod
|
||||
def import_set(cls, dset, in_stream):
|
||||
"""Returns dataset from DataFrame."""
|
||||
dset.wipe()
|
||||
dset.dict = in_stream.to_dict(orient='records')
|
||||
@@ -0,0 +1,62 @@
|
||||
""" Tablib - HTML export support.
|
||||
"""
|
||||
|
||||
import codecs
|
||||
from io import BytesIO
|
||||
|
||||
from MarkupPy import markup
|
||||
|
||||
|
||||
class HTMLFormat:
|
||||
BOOK_ENDINGS = 'h3'
|
||||
|
||||
title = 'html'
|
||||
extensions = ('html', )
|
||||
|
||||
@classmethod
|
||||
def export_set(cls, dataset):
|
||||
"""HTML representation of a Dataset."""
|
||||
|
||||
stream = BytesIO()
|
||||
|
||||
page = markup.page()
|
||||
page.table.open()
|
||||
|
||||
if dataset.headers is not None:
|
||||
new_header = [item if item is not None else '' for item in dataset.headers]
|
||||
|
||||
page.thead.open()
|
||||
headers = markup.oneliner.th(new_header)
|
||||
page.tr(headers)
|
||||
page.thead.close()
|
||||
|
||||
for row in dataset:
|
||||
new_row = [item if item is not None else '' for item in row]
|
||||
|
||||
html_row = markup.oneliner.td(new_row)
|
||||
page.tr(html_row)
|
||||
|
||||
page.table.close()
|
||||
|
||||
# Allow unicode characters in output
|
||||
wrapper = codecs.getwriter("utf8")(stream)
|
||||
wrapper.writelines(str(page))
|
||||
|
||||
return stream.getvalue().decode('utf-8')
|
||||
|
||||
@classmethod
|
||||
def export_book(cls, databook):
|
||||
"""HTML representation of a Databook."""
|
||||
|
||||
stream = BytesIO()
|
||||
|
||||
# Allow unicode characters in output
|
||||
wrapper = codecs.getwriter("utf8")(stream)
|
||||
|
||||
for i, dset in enumerate(databook._datasets):
|
||||
title = (dset.title if dset.title else 'Set %s' % (i))
|
||||
wrapper.write('<{}>{}</{}>\n'.format(cls.BOOK_ENDINGS, title, cls.BOOK_ENDINGS))
|
||||
wrapper.write(dset.html)
|
||||
wrapper.write('\n')
|
||||
|
||||
return stream.getvalue().decode('utf-8')
|
||||
@@ -0,0 +1,40 @@
|
||||
"""Tablib - Jira table export support.
|
||||
|
||||
Generates a Jira table from the dataset.
|
||||
"""
|
||||
|
||||
|
||||
class JIRAFormat:
|
||||
title = 'jira'
|
||||
|
||||
@classmethod
|
||||
def export_set(cls, dataset):
|
||||
"""Formats the dataset according to the Jira table syntax:
|
||||
|
||||
||heading 1||heading 2||heading 3||
|
||||
|col A1|col A2|col A3|
|
||||
|col B1|col B2|col B3|
|
||||
|
||||
:param dataset: dataset to serialize
|
||||
:type dataset: tablib.core.Dataset
|
||||
"""
|
||||
|
||||
header = cls._get_header(dataset.headers) if dataset.headers else ''
|
||||
body = cls._get_body(dataset)
|
||||
return '{}\n{}'.format(header, body) if header else body
|
||||
|
||||
@classmethod
|
||||
def _get_body(cls, dataset):
|
||||
return '\n'.join([cls._serialize_row(row) for row in dataset])
|
||||
|
||||
@classmethod
|
||||
def _get_header(cls, headers):
|
||||
return cls._serialize_row(headers, delimiter='||')
|
||||
|
||||
@classmethod
|
||||
def _serialize_row(cls, row, delimiter='|'):
|
||||
return '{}{}{}'.format(
|
||||
delimiter,
|
||||
delimiter.join([str(item) if item else ' ' for item in row]),
|
||||
delimiter
|
||||
)
|
||||
@@ -0,0 +1,58 @@
|
||||
""" Tablib - JSON Support
|
||||
"""
|
||||
import decimal
|
||||
import json
|
||||
from uuid import UUID
|
||||
|
||||
import tablib
|
||||
|
||||
|
||||
def serialize_objects_handler(obj):
|
||||
if isinstance(obj, (decimal.Decimal, UUID)):
|
||||
return str(obj)
|
||||
elif hasattr(obj, 'isoformat'):
|
||||
return obj.isoformat()
|
||||
else:
|
||||
return obj
|
||||
|
||||
|
||||
class JSONFormat:
|
||||
title = 'json'
|
||||
extensions = ('json', 'jsn')
|
||||
|
||||
@classmethod
|
||||
def export_set(cls, dataset):
|
||||
"""Returns JSON representation of Dataset."""
|
||||
return json.dumps(dataset.dict, default=serialize_objects_handler)
|
||||
|
||||
@classmethod
|
||||
def export_book(cls, databook):
|
||||
"""Returns JSON representation of Databook."""
|
||||
return json.dumps(databook._package(), default=serialize_objects_handler)
|
||||
|
||||
@classmethod
|
||||
def import_set(cls, dset, in_stream):
|
||||
"""Returns dataset from JSON stream."""
|
||||
|
||||
dset.wipe()
|
||||
dset.dict = json.load(in_stream)
|
||||
|
||||
@classmethod
|
||||
def import_book(cls, dbook, in_stream):
|
||||
"""Returns databook from JSON stream."""
|
||||
|
||||
dbook.wipe()
|
||||
for sheet in json.load(in_stream):
|
||||
data = tablib.Dataset()
|
||||
data.title = sheet['title']
|
||||
data.dict = sheet['data']
|
||||
dbook.add_sheet(data)
|
||||
|
||||
@classmethod
|
||||
def detect(cls, stream):
|
||||
"""Returns True if given stream is valid JSON."""
|
||||
try:
|
||||
json.load(stream)
|
||||
return True
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
@@ -0,0 +1,132 @@
|
||||
"""Tablib - LaTeX table export support.
|
||||
|
||||
Generates a LaTeX booktabs-style table from the dataset.
|
||||
"""
|
||||
import re
|
||||
|
||||
|
||||
class LATEXFormat:
|
||||
title = 'latex'
|
||||
extensions = ('tex',)
|
||||
|
||||
TABLE_TEMPLATE = """\
|
||||
%% Note: add \\usepackage{booktabs} to your preamble
|
||||
%%
|
||||
\\begin{table}[!htbp]
|
||||
\\centering
|
||||
%(CAPTION)s
|
||||
\\begin{tabular}{%(COLSPEC)s}
|
||||
\\toprule
|
||||
%(HEADER)s
|
||||
%(MIDRULE)s
|
||||
%(BODY)s
|
||||
\\bottomrule
|
||||
\\end{tabular}
|
||||
\\end{table}
|
||||
"""
|
||||
|
||||
TEX_RESERVED_SYMBOLS_MAP = dict([
|
||||
('\\', '\\textbackslash{}'),
|
||||
('{', '\\{'),
|
||||
('}', '\\}'),
|
||||
('$', '\\$'),
|
||||
('&', '\\&'),
|
||||
('#', '\\#'),
|
||||
('^', '\\textasciicircum{}'),
|
||||
('_', '\\_'),
|
||||
('~', '\\textasciitilde{}'),
|
||||
('%', '\\%'),
|
||||
])
|
||||
|
||||
TEX_RESERVED_SYMBOLS_RE = re.compile(
|
||||
'(%s)' % '|'.join(map(re.escape, TEX_RESERVED_SYMBOLS_MAP.keys())))
|
||||
|
||||
@classmethod
|
||||
def export_set(cls, dataset):
|
||||
"""Returns LaTeX representation of dataset
|
||||
|
||||
:param dataset: dataset to serialize
|
||||
:type dataset: tablib.core.Dataset
|
||||
"""
|
||||
|
||||
caption = '\\caption{%s}' % dataset.title if dataset.title else '%'
|
||||
colspec = cls._colspec(dataset.width)
|
||||
header = cls._serialize_row(dataset.headers) if dataset.headers else ''
|
||||
midrule = cls._midrule(dataset.width)
|
||||
body = '\n'.join([cls._serialize_row(row) for row in dataset])
|
||||
return cls.TABLE_TEMPLATE % dict(CAPTION=caption, COLSPEC=colspec,
|
||||
HEADER=header, MIDRULE=midrule, BODY=body)
|
||||
|
||||
@classmethod
|
||||
def _colspec(cls, dataset_width):
|
||||
"""Generates the column specification for the LaTeX `tabular` environment
|
||||
based on the dataset width.
|
||||
|
||||
The first column is justified to the left, all further columns are aligned
|
||||
to the right.
|
||||
|
||||
.. note:: This is only a heuristic and most probably has to be fine-tuned
|
||||
post export. Column alignment should depend on the data type, e.g., textual
|
||||
content should usually be aligned to the left while numeric content almost
|
||||
always should be aligned to the right.
|
||||
|
||||
:param dataset_width: width of the dataset
|
||||
"""
|
||||
|
||||
spec = 'l'
|
||||
for _ in range(1, dataset_width):
|
||||
spec += 'r'
|
||||
return spec
|
||||
|
||||
@classmethod
|
||||
def _midrule(cls, dataset_width):
|
||||
"""Generates the table `midrule`, which may be composed of several
|
||||
`cmidrules`.
|
||||
|
||||
:param dataset_width: width of the dataset to serialize
|
||||
"""
|
||||
|
||||
if not dataset_width or dataset_width == 1:
|
||||
return '\\midrule'
|
||||
return ' '.join([cls._cmidrule(colindex, dataset_width) for colindex in
|
||||
range(1, dataset_width + 1)])
|
||||
|
||||
@classmethod
|
||||
def _cmidrule(cls, colindex, dataset_width):
|
||||
"""Generates the `cmidrule` for a single column with appropriate trimming
|
||||
based on the column position.
|
||||
|
||||
:param colindex: Column index
|
||||
:param dataset_width: width of the dataset
|
||||
"""
|
||||
|
||||
rule = '\\cmidrule(%s){%d-%d}'
|
||||
if colindex == 1:
|
||||
# Rule of first column is trimmed on the right
|
||||
return rule % ('r', colindex, colindex)
|
||||
if colindex == dataset_width:
|
||||
# Rule of last column is trimmed on the left
|
||||
return rule % ('l', colindex, colindex)
|
||||
# Inner columns are trimmed on the left and right
|
||||
return rule % ('lr', colindex, colindex)
|
||||
|
||||
@classmethod
|
||||
def _serialize_row(cls, row):
|
||||
"""Returns string representation of a single row.
|
||||
|
||||
:param row: single dataset row
|
||||
"""
|
||||
|
||||
new_row = [cls._escape_tex_reserved_symbols(str(item)) if item else ''
|
||||
for item in row]
|
||||
return 6 * ' ' + ' & '.join(new_row) + ' \\\\'
|
||||
|
||||
@classmethod
|
||||
def _escape_tex_reserved_symbols(cls, input):
|
||||
"""Escapes all TeX reserved symbols ('_', '~', etc.) in a string.
|
||||
|
||||
:param input: String to escape
|
||||
"""
|
||||
def replace(match):
|
||||
return cls.TEX_RESERVED_SYMBOLS_MAP[match.group()]
|
||||
return cls.TEX_RESERVED_SYMBOLS_RE.sub(replace, input)
|
||||
@@ -0,0 +1,105 @@
|
||||
""" Tablib - ODF Support.
|
||||
"""
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from odf import opendocument, style, table, text
|
||||
|
||||
bold = style.Style(name="bold", family="paragraph")
|
||||
bold.addElement(style.TextProperties(fontweight="bold", fontweightasian="bold", fontweightcomplex="bold"))
|
||||
|
||||
|
||||
class ODSFormat:
|
||||
title = 'ods'
|
||||
extensions = ('ods',)
|
||||
|
||||
@classmethod
|
||||
def export_set(cls, dataset):
|
||||
"""Returns ODF representation of Dataset."""
|
||||
|
||||
wb = opendocument.OpenDocumentSpreadsheet()
|
||||
wb.automaticstyles.addElement(bold)
|
||||
|
||||
ws = table.Table(name=dataset.title if dataset.title else 'Tablib Dataset')
|
||||
wb.spreadsheet.addElement(ws)
|
||||
cls.dset_sheet(dataset, ws)
|
||||
|
||||
stream = BytesIO()
|
||||
wb.save(stream)
|
||||
return stream.getvalue()
|
||||
|
||||
@classmethod
|
||||
def export_book(cls, databook):
|
||||
"""Returns ODF representation of DataBook."""
|
||||
|
||||
wb = opendocument.OpenDocumentSpreadsheet()
|
||||
wb.automaticstyles.addElement(bold)
|
||||
|
||||
for i, dset in enumerate(databook._datasets):
|
||||
ws = table.Table(name=dset.title if dset.title else 'Sheet%s' % (i))
|
||||
wb.spreadsheet.addElement(ws)
|
||||
cls.dset_sheet(dset, ws)
|
||||
|
||||
stream = BytesIO()
|
||||
wb.save(stream)
|
||||
return stream.getvalue()
|
||||
|
||||
@classmethod
|
||||
def dset_sheet(cls, dataset, ws):
|
||||
"""Completes given worksheet from given Dataset."""
|
||||
_package = dataset._package(dicts=False)
|
||||
|
||||
for i, sep in enumerate(dataset._separators):
|
||||
_offset = i
|
||||
_package.insert((sep[0] + _offset), (sep[1],))
|
||||
|
||||
for i, row in enumerate(_package):
|
||||
row_number = i + 1
|
||||
odf_row = table.TableRow(stylename=bold, defaultcellstylename='bold')
|
||||
for j, col in enumerate(row):
|
||||
try:
|
||||
col = str(col, errors='ignore')
|
||||
except TypeError:
|
||||
# col is already str
|
||||
pass
|
||||
ws.addElement(table.TableColumn())
|
||||
|
||||
# bold headers
|
||||
if (row_number == 1) and dataset.headers:
|
||||
odf_row.setAttribute('stylename', bold)
|
||||
ws.addElement(odf_row)
|
||||
cell = table.TableCell()
|
||||
p = text.P()
|
||||
p.addElement(text.Span(text=col, stylename=bold))
|
||||
cell.addElement(p)
|
||||
odf_row.addElement(cell)
|
||||
|
||||
# wrap the rest
|
||||
else:
|
||||
try:
|
||||
if '\n' in col:
|
||||
ws.addElement(odf_row)
|
||||
cell = table.TableCell()
|
||||
cell.addElement(text.P(text=col))
|
||||
odf_row.addElement(cell)
|
||||
else:
|
||||
ws.addElement(odf_row)
|
||||
cell = table.TableCell()
|
||||
cell.addElement(text.P(text=col))
|
||||
odf_row.addElement(cell)
|
||||
except TypeError:
|
||||
ws.addElement(odf_row)
|
||||
cell = table.TableCell()
|
||||
cell.addElement(text.P(text=col))
|
||||
odf_row.addElement(cell)
|
||||
|
||||
@classmethod
|
||||
def detect(cls, stream):
|
||||
if isinstance(stream, bytes):
|
||||
# load expects a file-like object.
|
||||
stream = BytesIO(stream)
|
||||
try:
|
||||
opendocument.load(stream)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
@@ -0,0 +1,265 @@
|
||||
""" Tablib - reStructuredText Support
|
||||
"""
|
||||
|
||||
from itertools import zip_longest
|
||||
from statistics import median
|
||||
from textwrap import TextWrapper
|
||||
|
||||
JUSTIFY_LEFT = 'left'
|
||||
JUSTIFY_CENTER = 'center'
|
||||
JUSTIFY_RIGHT = 'right'
|
||||
JUSTIFY_VALUES = (JUSTIFY_LEFT, JUSTIFY_CENTER, JUSTIFY_RIGHT)
|
||||
|
||||
|
||||
def to_str(value):
|
||||
if isinstance(value, bytes):
|
||||
return value.decode('utf-8')
|
||||
return str(value)
|
||||
|
||||
|
||||
def _max_word_len(text):
|
||||
"""
|
||||
Return the length of the longest word in `text`.
|
||||
|
||||
>>> _max_word_len('Python Module for Tabular Datasets')
|
||||
8
|
||||
"""
|
||||
return max(len(word) for word in text.split()) if text else 0
|
||||
|
||||
|
||||
class ReSTFormat:
|
||||
title = 'rst'
|
||||
extensions = ('rst',)
|
||||
|
||||
MAX_TABLE_WIDTH = 80 # Roughly. It may be wider to avoid breaking words.
|
||||
|
||||
@classmethod
|
||||
def _get_column_string_lengths(cls, dataset):
|
||||
"""
|
||||
Returns a list of string lengths of each column, and a list of
|
||||
maximum word lengths.
|
||||
"""
|
||||
if dataset.headers:
|
||||
column_lengths = [[len(h)] for h in dataset.headers]
|
||||
word_lens = [_max_word_len(h) for h in dataset.headers]
|
||||
else:
|
||||
column_lengths = [[] for _ in range(dataset.width)]
|
||||
word_lens = [0 for _ in range(dataset.width)]
|
||||
for row in dataset.dict:
|
||||
values = iter(row.values() if hasattr(row, 'values') else row)
|
||||
for i, val in enumerate(values):
|
||||
text = to_str(val)
|
||||
column_lengths[i].append(len(text))
|
||||
word_lens[i] = max(word_lens[i], _max_word_len(text))
|
||||
return column_lengths, word_lens
|
||||
|
||||
@classmethod
|
||||
def _row_to_lines(cls, values, widths, wrapper, sep='|', justify=JUSTIFY_LEFT):
|
||||
"""
|
||||
Returns a table row of wrapped values as a list of lines
|
||||
"""
|
||||
if justify not in JUSTIFY_VALUES:
|
||||
raise ValueError('Value of "justify" must be one of "{}"'.format(
|
||||
'", "'.join(JUSTIFY_VALUES)
|
||||
))
|
||||
if justify == JUSTIFY_LEFT:
|
||||
just = lambda text, width: text.ljust(width)
|
||||
elif justify == JUSTIFY_CENTER:
|
||||
just = lambda text, width: text.center(width)
|
||||
else:
|
||||
just = lambda text, width: text.rjust(width)
|
||||
lpad = sep + ' ' if sep else ''
|
||||
rpad = ' ' + sep if sep else ''
|
||||
pad = ' ' + sep + ' '
|
||||
cells = []
|
||||
for value, width in zip(values, widths):
|
||||
wrapper.width = width
|
||||
text = to_str(value)
|
||||
cell = wrapper.wrap(text)
|
||||
cells.append(cell)
|
||||
lines = zip_longest(*cells, fillvalue='')
|
||||
lines = (
|
||||
(just(cell_line, widths[i]) for i, cell_line in enumerate(line))
|
||||
for line in lines
|
||||
)
|
||||
lines = [''.join((lpad, pad.join(line), rpad)) for line in lines]
|
||||
return lines
|
||||
|
||||
@classmethod
|
||||
def _get_column_widths(cls, dataset, max_table_width=MAX_TABLE_WIDTH, pad_len=3):
|
||||
"""
|
||||
Returns a list of column widths proportional to the median length
|
||||
of the text in their cells.
|
||||
"""
|
||||
str_lens, word_lens = cls._get_column_string_lengths(dataset)
|
||||
median_lens = [int(median(lens)) for lens in str_lens]
|
||||
total = sum(median_lens)
|
||||
if total > max_table_width - (pad_len * len(median_lens)):
|
||||
column_widths = (max_table_width * l // total for l in median_lens)
|
||||
else:
|
||||
column_widths = (l for l in median_lens)
|
||||
# Allow for separator and padding:
|
||||
column_widths = (w - pad_len if w > pad_len else w for w in column_widths)
|
||||
# Rather widen table than break words:
|
||||
column_widths = [max(w, l) for w, l in zip(column_widths, word_lens)]
|
||||
return column_widths
|
||||
|
||||
@classmethod
|
||||
def export_set_as_simple_table(cls, dataset, column_widths=None):
|
||||
"""
|
||||
Returns reStructuredText grid table representation of dataset.
|
||||
"""
|
||||
lines = []
|
||||
wrapper = TextWrapper()
|
||||
if column_widths is None:
|
||||
column_widths = cls._get_column_widths(dataset, pad_len=2)
|
||||
border = ' '.join(['=' * w for w in column_widths])
|
||||
|
||||
lines.append(border)
|
||||
if dataset.headers:
|
||||
lines.extend(cls._row_to_lines(
|
||||
dataset.headers,
|
||||
column_widths,
|
||||
wrapper,
|
||||
sep='',
|
||||
justify=JUSTIFY_CENTER,
|
||||
))
|
||||
lines.append(border)
|
||||
for row in dataset.dict:
|
||||
values = iter(row.values() if hasattr(row, 'values') else row)
|
||||
lines.extend(cls._row_to_lines(values, column_widths, wrapper, ''))
|
||||
lines.append(border)
|
||||
return '\n'.join(lines)
|
||||
|
||||
@classmethod
|
||||
def export_set_as_grid_table(cls, dataset, column_widths=None):
|
||||
"""
|
||||
Returns reStructuredText grid table representation of dataset.
|
||||
|
||||
|
||||
>>> from tablib import Dataset
|
||||
>>> from tablib.formats import registry
|
||||
>>> bits = ((0, 0), (1, 0), (0, 1), (1, 1))
|
||||
>>> data = Dataset()
|
||||
>>> data.headers = ['A', 'B', 'A and B']
|
||||
>>> for a, b in bits:
|
||||
... data.append([bool(a), bool(b), bool(a * b)])
|
||||
>>> rst = registry.get_format('rst')
|
||||
>>> print(rst.export_set(data, force_grid=True))
|
||||
+-------+-------+-------+
|
||||
| A | B | A and |
|
||||
| | | B |
|
||||
+=======+=======+=======+
|
||||
| False | False | False |
|
||||
+-------+-------+-------+
|
||||
| True | False | False |
|
||||
+-------+-------+-------+
|
||||
| False | True | False |
|
||||
+-------+-------+-------+
|
||||
| True | True | True |
|
||||
+-------+-------+-------+
|
||||
|
||||
"""
|
||||
lines = []
|
||||
wrapper = TextWrapper()
|
||||
if column_widths is None:
|
||||
column_widths = cls._get_column_widths(dataset)
|
||||
header_sep = '+=' + '=+='.join(['=' * w for w in column_widths]) + '=+'
|
||||
row_sep = '+-' + '-+-'.join(['-' * w for w in column_widths]) + '-+'
|
||||
|
||||
lines.append(row_sep)
|
||||
|
||||
if dataset.headers:
|
||||
lines.extend(cls._row_to_lines(
|
||||
dataset.headers,
|
||||
column_widths,
|
||||
wrapper,
|
||||
justify=JUSTIFY_CENTER,
|
||||
))
|
||||
lines.append(header_sep)
|
||||
for row in dataset.dict:
|
||||
values = iter(row.values() if hasattr(row, 'values') else row)
|
||||
lines.extend(cls._row_to_lines(values, column_widths, wrapper))
|
||||
lines.append(row_sep)
|
||||
return '\n'.join(lines)
|
||||
|
||||
@classmethod
|
||||
def _use_simple_table(cls, head0, col0, width0):
|
||||
"""
|
||||
Use a simple table if the text in the first column is never wrapped
|
||||
|
||||
|
||||
>>> from tablib.formats import registry
|
||||
>>> rst = registry.get_format('rst')
|
||||
>>> rst._use_simple_table('menu', ['egg', 'bacon'], 10)
|
||||
True
|
||||
>>> rst._use_simple_table(None, ['lobster thermidor', 'spam'], 10)
|
||||
False
|
||||
|
||||
"""
|
||||
if head0 is not None:
|
||||
head0 = to_str(head0)
|
||||
if len(head0) > width0:
|
||||
return False
|
||||
for cell in col0:
|
||||
cell = to_str(cell)
|
||||
if len(cell) > width0:
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def export_set(cls, dataset, **kwargs):
|
||||
"""
|
||||
Returns reStructuredText table representation of dataset.
|
||||
|
||||
Returns a simple table if the text in the first column is never
|
||||
wrapped, otherwise returns a grid table.
|
||||
|
||||
|
||||
>>> from tablib import Dataset
|
||||
>>> bits = ((0, 0), (1, 0), (0, 1), (1, 1))
|
||||
>>> data = Dataset()
|
||||
>>> data.headers = ['A', 'B', 'A and B']
|
||||
>>> for a, b in bits:
|
||||
... data.append([bool(a), bool(b), bool(a * b)])
|
||||
>>> table = data.rst
|
||||
>>> table.split('\\n') == [
|
||||
... '===== ===== =====',
|
||||
... ' A B A and',
|
||||
... ' B ',
|
||||
... '===== ===== =====',
|
||||
... 'False False False',
|
||||
... 'True False False',
|
||||
... 'False True False',
|
||||
... 'True True True ',
|
||||
... '===== ===== =====',
|
||||
... ]
|
||||
True
|
||||
|
||||
"""
|
||||
if not dataset.dict:
|
||||
return ''
|
||||
force_grid = kwargs.get('force_grid', False)
|
||||
max_table_width = kwargs.get('max_table_width', cls.MAX_TABLE_WIDTH)
|
||||
column_widths = cls._get_column_widths(dataset, max_table_width)
|
||||
|
||||
use_simple_table = cls._use_simple_table(
|
||||
dataset.headers[0] if dataset.headers else None,
|
||||
dataset.get_col(0),
|
||||
column_widths[0],
|
||||
)
|
||||
if use_simple_table and not force_grid:
|
||||
return cls.export_set_as_simple_table(dataset, column_widths)
|
||||
else:
|
||||
return cls.export_set_as_grid_table(dataset, column_widths)
|
||||
|
||||
@classmethod
|
||||
def export_book(cls, databook):
|
||||
"""
|
||||
reStructuredText representation of a Databook.
|
||||
|
||||
Tables are separated by a blank line. All tables use the grid
|
||||
format.
|
||||
"""
|
||||
return '\n\n'.join(cls.export_set(dataset, force_grid=True)
|
||||
for dataset in databook._datasets)
|
||||
@@ -0,0 +1,11 @@
|
||||
""" Tablib - TSV (Tab Separated Values) Support.
|
||||
"""
|
||||
|
||||
from ._csv import CSVFormat
|
||||
|
||||
|
||||
class TSVFormat(CSVFormat):
|
||||
title = 'tsv'
|
||||
extensions = ('tsv',)
|
||||
|
||||
DEFAULT_DELIMITER = '\t'
|
||||
@@ -0,0 +1,138 @@
|
||||
""" Tablib - XLS Support.
|
||||
"""
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
import tablib
|
||||
import xlrd
|
||||
import xlwt
|
||||
|
||||
# special styles
|
||||
wrap = xlwt.easyxf("alignment: wrap on")
|
||||
bold = xlwt.easyxf("font: bold on")
|
||||
|
||||
|
||||
class XLSFormat:
|
||||
title = 'xls'
|
||||
extensions = ('xls',)
|
||||
|
||||
@classmethod
|
||||
def detect(cls, stream):
|
||||
"""Returns True if given stream is a readable excel file."""
|
||||
try:
|
||||
xlrd.open_workbook(file_contents=stream)
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
xlrd.open_workbook(file_contents=stream.read())
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
xlrd.open_workbook(filename=stream)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def export_set(cls, dataset):
|
||||
"""Returns XLS representation of Dataset."""
|
||||
|
||||
wb = xlwt.Workbook(encoding='utf8')
|
||||
ws = wb.add_sheet(dataset.title if dataset.title else 'Tablib Dataset')
|
||||
|
||||
cls.dset_sheet(dataset, ws)
|
||||
|
||||
stream = BytesIO()
|
||||
wb.save(stream)
|
||||
return stream.getvalue()
|
||||
|
||||
@classmethod
|
||||
def export_book(cls, 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))
|
||||
|
||||
cls.dset_sheet(dset, ws)
|
||||
|
||||
stream = BytesIO()
|
||||
wb.save(stream)
|
||||
return stream.getvalue()
|
||||
|
||||
@classmethod
|
||||
def import_set(cls, dset, in_stream, headers=True):
|
||||
"""Returns databook from XLS stream."""
|
||||
|
||||
dset.wipe()
|
||||
|
||||
xls_book = xlrd.open_workbook(file_contents=in_stream.read())
|
||||
sheet = xls_book.sheet_by_index(0)
|
||||
|
||||
dset.title = sheet.name
|
||||
|
||||
for i in range(sheet.nrows):
|
||||
if i == 0 and headers:
|
||||
dset.headers = sheet.row_values(0)
|
||||
else:
|
||||
dset.append([
|
||||
val if typ != xlrd.XL_CELL_ERROR else xlrd.error_text_from_code[val]
|
||||
for val, typ in zip(sheet.row_values(i), sheet.row_types(i))
|
||||
])
|
||||
|
||||
@classmethod
|
||||
def import_book(cls, dbook, in_stream, headers=True):
|
||||
"""Returns databook from XLS stream."""
|
||||
|
||||
dbook.wipe()
|
||||
|
||||
xls_book = xlrd.open_workbook(file_contents=in_stream)
|
||||
|
||||
for sheet in xls_book.sheets():
|
||||
data = tablib.Dataset()
|
||||
data.title = sheet.name
|
||||
|
||||
for i in range(sheet.nrows):
|
||||
if i == 0 and headers:
|
||||
data.headers = sheet.row_values(0)
|
||||
else:
|
||||
data.append(sheet.row_values(i))
|
||||
|
||||
dbook.add_sheet(data)
|
||||
|
||||
@classmethod
|
||||
def dset_sheet(cls, 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,137 @@
|
||||
""" Tablib - XLSX Support.
|
||||
"""
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
import openpyxl
|
||||
import tablib
|
||||
|
||||
Workbook = openpyxl.workbook.Workbook
|
||||
ExcelWriter = openpyxl.writer.excel.ExcelWriter
|
||||
get_column_letter = openpyxl.utils.get_column_letter
|
||||
|
||||
|
||||
class XLSXFormat:
|
||||
title = 'xlsx'
|
||||
extensions = ('xlsx',)
|
||||
|
||||
@classmethod
|
||||
def detect(cls, stream):
|
||||
"""Returns True if given stream is a readable excel file."""
|
||||
try:
|
||||
openpyxl.reader.excel.load_workbook(stream, read_only=True)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def export_set(cls, dataset, freeze_panes=True):
|
||||
"""Returns XLSX representation of Dataset."""
|
||||
wb = Workbook()
|
||||
ws = wb.worksheets[0]
|
||||
ws.title = dataset.title if dataset.title else 'Tablib Dataset'
|
||||
|
||||
cls.dset_sheet(dataset, ws, freeze_panes=freeze_panes)
|
||||
|
||||
stream = BytesIO()
|
||||
wb.save(stream)
|
||||
return stream.getvalue()
|
||||
|
||||
@classmethod
|
||||
def export_book(cls, databook, freeze_panes=True):
|
||||
"""Returns XLSX representation of DataBook."""
|
||||
|
||||
wb = Workbook()
|
||||
for sheet in wb.worksheets:
|
||||
wb.remove(sheet)
|
||||
for i, dset in enumerate(databook._datasets):
|
||||
ws = wb.create_sheet()
|
||||
ws.title = dset.title if dset.title else 'Sheet%s' % (i)
|
||||
|
||||
cls.dset_sheet(dset, ws, freeze_panes=freeze_panes)
|
||||
|
||||
stream = BytesIO()
|
||||
wb.save(stream)
|
||||
return stream.getvalue()
|
||||
|
||||
@classmethod
|
||||
def import_set(cls, dset, in_stream, headers=True):
|
||||
"""Returns databook from XLS stream."""
|
||||
|
||||
dset.wipe()
|
||||
|
||||
xls_book = openpyxl.reader.excel.load_workbook(in_stream, read_only=True)
|
||||
sheet = xls_book.active
|
||||
|
||||
dset.title = sheet.title
|
||||
|
||||
for i, row in enumerate(sheet.rows):
|
||||
row_vals = [c.value for c in row]
|
||||
if (i == 0) and (headers):
|
||||
dset.headers = row_vals
|
||||
else:
|
||||
dset.append(row_vals)
|
||||
|
||||
@classmethod
|
||||
def import_book(cls, dbook, in_stream, headers=True):
|
||||
"""Returns databook from XLS stream."""
|
||||
|
||||
dbook.wipe()
|
||||
|
||||
xls_book = openpyxl.reader.excel.load_workbook(in_stream, read_only=True)
|
||||
|
||||
for sheet in xls_book.worksheets:
|
||||
data = tablib.Dataset()
|
||||
data.title = sheet.title
|
||||
|
||||
for i, row in enumerate(sheet.rows):
|
||||
row_vals = [c.value for c in row]
|
||||
if (i == 0) and (headers):
|
||||
data.headers = row_vals
|
||||
else:
|
||||
data.append(row_vals)
|
||||
|
||||
dbook.add_sheet(data)
|
||||
|
||||
@classmethod
|
||||
def dset_sheet(cls, dataset, ws, freeze_panes=True):
|
||||
"""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],))
|
||||
|
||||
bold = openpyxl.styles.Font(bold=True)
|
||||
wrap_text = openpyxl.styles.Alignment(wrap_text=True)
|
||||
|
||||
for i, row in enumerate(_package):
|
||||
row_number = i + 1
|
||||
for j, col in enumerate(row):
|
||||
col_idx = get_column_letter(j + 1)
|
||||
cell = ws['{}{}'.format(col_idx, row_number)]
|
||||
|
||||
# bold headers
|
||||
if (row_number == 1) and dataset.headers:
|
||||
cell.font = bold
|
||||
if freeze_panes:
|
||||
# Export Freeze only after first Line
|
||||
ws.freeze_panes = 'A2'
|
||||
|
||||
# bold separators
|
||||
elif len(row) < dataset.width:
|
||||
cell.font = bold
|
||||
|
||||
# wrap the rest
|
||||
else:
|
||||
try:
|
||||
str_col_value = str(col)
|
||||
except TypeError:
|
||||
str_col_value = ''
|
||||
if '\n' in str_col_value:
|
||||
cell.alignment = wrap_text
|
||||
|
||||
try:
|
||||
cell.value = col
|
||||
except (ValueError, TypeError):
|
||||
cell.value = str(col)
|
||||
@@ -0,0 +1,53 @@
|
||||
""" Tablib - YAML Support.
|
||||
"""
|
||||
|
||||
import tablib
|
||||
import yaml
|
||||
|
||||
|
||||
class YAMLFormat:
|
||||
title = 'yaml'
|
||||
extensions = ('yaml', 'yml')
|
||||
|
||||
@classmethod
|
||||
def export_set(cls, dataset):
|
||||
"""Returns YAML representation of Dataset."""
|
||||
|
||||
return yaml.safe_dump(dataset._package(ordered=False), default_flow_style=None)
|
||||
|
||||
@classmethod
|
||||
def export_book(cls, databook):
|
||||
"""Returns YAML representation of Databook."""
|
||||
return yaml.safe_dump(databook._package(ordered=False), default_flow_style=None)
|
||||
|
||||
@classmethod
|
||||
def import_set(cls, dset, in_stream):
|
||||
"""Returns dataset from YAML stream."""
|
||||
|
||||
dset.wipe()
|
||||
dset.dict = yaml.safe_load(in_stream)
|
||||
|
||||
@classmethod
|
||||
def import_book(cls, dbook, in_stream):
|
||||
"""Returns databook from YAML stream."""
|
||||
|
||||
dbook.wipe()
|
||||
|
||||
for sheet in yaml.safe_load(in_stream):
|
||||
data = tablib.Dataset()
|
||||
data.title = sheet['title']
|
||||
data.dict = sheet['data']
|
||||
dbook.add_sheet(data)
|
||||
|
||||
@classmethod
|
||||
def detect(cls, stream):
|
||||
"""Returns True if given stream is valid YAML."""
|
||||
try:
|
||||
_yaml = yaml.safe_load(stream)
|
||||
if isinstance(_yaml, (list, tuple, dict)):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except (yaml.parser.ParserError, yaml.reader.ReaderError,
|
||||
yaml.scanner.ScannerError):
|
||||
return False
|
||||
@@ -0,0 +1,297 @@
|
||||
#! /usr/bin/env python
|
||||
"""DBF accessing helpers.
|
||||
|
||||
FIXME: more documentation needed
|
||||
|
||||
Examples:
|
||||
|
||||
Create new table, setup structure, add records:
|
||||
|
||||
dbf = Dbf(filename, new=True)
|
||||
dbf.addField(
|
||||
("NAME", "C", 15),
|
||||
("SURNAME", "C", 25),
|
||||
("INITIALS", "C", 10),
|
||||
("BIRTHDATE", "D"),
|
||||
)
|
||||
for (n, s, i, b) in (
|
||||
("John", "Miller", "YC", (1980, 10, 11)),
|
||||
("Andy", "Larkin", "", (1980, 4, 11)),
|
||||
):
|
||||
rec = dbf.newRecord()
|
||||
rec["NAME"] = n
|
||||
rec["SURNAME"] = s
|
||||
rec["INITIALS"] = i
|
||||
rec["BIRTHDATE"] = b
|
||||
rec.store()
|
||||
dbf.close()
|
||||
|
||||
Open existed dbf, read some data:
|
||||
|
||||
dbf = Dbf(filename, True)
|
||||
for rec in dbf:
|
||||
for fldName in dbf.fieldNames:
|
||||
print('%s:\t %s (%s)' % (fldName, rec[fldName],
|
||||
type(rec[fldName])))
|
||||
print()
|
||||
dbf.close()
|
||||
|
||||
"""
|
||||
"""History (most recent first):
|
||||
11-feb-2007 [als] export INVALID_VALUE;
|
||||
Dbf: added .ignoreErrors, .INVALID_VALUE
|
||||
04-jul-2006 [als] added export declaration
|
||||
20-dec-2005 [yc] removed fromStream and newDbf methods:
|
||||
use argument of __init__ call must be used instead;
|
||||
added class fields pointing to the header and
|
||||
record classes.
|
||||
17-dec-2005 [yc] split to several modules; reimplemented
|
||||
13-dec-2005 [yc] adapted to the changes of the `strutil` module.
|
||||
13-sep-2002 [als] support FoxPro Timestamp datatype
|
||||
15-nov-1999 [jjk] documentation updates, add demo
|
||||
24-aug-1998 [jjk] add some encodeValue methods (not tested), other tweaks
|
||||
08-jun-1998 [jjk] fix problems, add more features
|
||||
20-feb-1998 [jjk] fix problems, add more features
|
||||
19-feb-1998 [jjk] add create/write capabilities
|
||||
18-feb-1998 [jjk] from dbfload.py
|
||||
"""
|
||||
|
||||
__version__ = "$Revision: 1.7 $"[11:-2]
|
||||
__date__ = "$Date: 2007/02/11 09:23:13 $"[7:-2]
|
||||
__author__ = "Jeff Kunce <kuncej@mail.conservation.state.mo.us>"
|
||||
|
||||
__all__ = ["Dbf"]
|
||||
|
||||
from . import header, record
|
||||
from .utils import INVALID_VALUE
|
||||
|
||||
|
||||
class Dbf:
|
||||
"""DBF accessor.
|
||||
|
||||
FIXME:
|
||||
docs and examples needed (dont' forget to tell
|
||||
about problems adding new fields on the fly)
|
||||
|
||||
Implementation notes:
|
||||
``_new`` field is used to indicate whether this is
|
||||
a new data table. `addField` could be used only for
|
||||
the new tables! If at least one record was appended
|
||||
to the table it's structure couldn't be changed.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("name", "header", "stream",
|
||||
"_changed", "_new", "_ignore_errors")
|
||||
|
||||
HeaderClass = header.DbfHeader
|
||||
RecordClass = record.DbfRecord
|
||||
INVALID_VALUE = INVALID_VALUE
|
||||
|
||||
# initialization and creation helpers
|
||||
|
||||
def __init__(self, f, readOnly=False, new=False, ignoreErrors=False):
|
||||
"""Initialize instance.
|
||||
|
||||
Arguments:
|
||||
f:
|
||||
Filename or file-like object.
|
||||
new:
|
||||
True if new data table must be created. Assume
|
||||
data table exists if this argument is False.
|
||||
readOnly:
|
||||
if ``f`` argument is a string file will
|
||||
be opend in read-only mode; in other cases
|
||||
this argument is ignored. This argument is ignored
|
||||
even if ``new`` argument is True.
|
||||
headerObj:
|
||||
`header.DbfHeader` instance or None. If this argument
|
||||
is None, new empty header will be used with the
|
||||
all fields set by default.
|
||||
ignoreErrors:
|
||||
if set, failing field value conversion will return
|
||||
``INVALID_VALUE`` instead of raising conversion error.
|
||||
|
||||
"""
|
||||
if isinstance(f, str):
|
||||
# a filename
|
||||
self.name = f
|
||||
if new:
|
||||
# new table (table file must be
|
||||
# created or opened and truncated)
|
||||
self.stream = open(f, "w+b")
|
||||
else:
|
||||
# table file must exist
|
||||
self.stream = open(f, ("r+b", "rb")[bool(readOnly)])
|
||||
else:
|
||||
# a stream
|
||||
self.name = getattr(f, "name", "")
|
||||
self.stream = f
|
||||
if new:
|
||||
# if this is a new table, header will be empty
|
||||
self.header = self.HeaderClass()
|
||||
else:
|
||||
# or instantiated using stream
|
||||
self.header = self.HeaderClass.fromStream(self.stream)
|
||||
self.ignoreErrors = ignoreErrors
|
||||
self._new = bool(new)
|
||||
self._changed = False
|
||||
|
||||
# properties
|
||||
|
||||
closed = property(lambda self: self.stream.closed)
|
||||
recordCount = property(lambda self: self.header.recordCount)
|
||||
fieldNames = property(
|
||||
lambda self: [_fld.name for _fld in self.header.fields])
|
||||
fieldDefs = property(lambda self: self.header.fields)
|
||||
changed = property(lambda self: self._changed or self.header.changed)
|
||||
|
||||
def ignoreErrors(self, value):
|
||||
"""Update `ignoreErrors` flag on the header object and self"""
|
||||
self.header.ignoreErrors = self._ignore_errors = bool(value)
|
||||
|
||||
ignoreErrors = property(
|
||||
lambda self: self._ignore_errors,
|
||||
ignoreErrors,
|
||||
doc="""Error processing mode for DBF field value conversion
|
||||
|
||||
if set, failing field value conversion will return
|
||||
``INVALID_VALUE`` instead of raising conversion error.
|
||||
|
||||
""")
|
||||
|
||||
# protected methods
|
||||
|
||||
def _fixIndex(self, index):
|
||||
"""Return fixed index.
|
||||
|
||||
This method fails if index isn't a numeric object
|
||||
(long or int). Or index isn't in a valid range
|
||||
(less or equal to the number of records in the db).
|
||||
|
||||
If ``index`` is a negative number, it will be
|
||||
treated as a negative indexes for list objects.
|
||||
|
||||
Return:
|
||||
Return value is numeric object maning valid index.
|
||||
|
||||
"""
|
||||
if not isinstance(index, int):
|
||||
raise TypeError("Index must be a numeric object")
|
||||
if index < 0:
|
||||
# index from the right side
|
||||
# fix it to the left-side index
|
||||
index += len(self) + 1
|
||||
if index >= len(self):
|
||||
raise IndexError("Record index out of range")
|
||||
return index
|
||||
|
||||
# interface methods
|
||||
|
||||
def close(self):
|
||||
self.flush()
|
||||
self.stream.close()
|
||||
|
||||
def flush(self):
|
||||
"""Flush data to the associated stream."""
|
||||
if self.changed:
|
||||
self.header.setCurrentDate()
|
||||
self.header.write(self.stream)
|
||||
self.stream.flush()
|
||||
self._changed = False
|
||||
|
||||
def indexOfFieldName(self, name):
|
||||
"""Index of field named ``name``."""
|
||||
# FIXME: move this to header class
|
||||
names = [f.name for f in self.header.fields]
|
||||
return names.index(name.upper())
|
||||
|
||||
def newRecord(self):
|
||||
"""Return new record, which belong to this table."""
|
||||
return self.RecordClass(self)
|
||||
|
||||
def append(self, record):
|
||||
"""Append ``record`` to the database."""
|
||||
record.index = self.header.recordCount
|
||||
record._write()
|
||||
self.header.recordCount += 1
|
||||
self._changed = True
|
||||
self._new = False
|
||||
|
||||
def addField(self, *defs):
|
||||
"""Add field definitions.
|
||||
|
||||
For more information see `header.DbfHeader.addField`.
|
||||
|
||||
"""
|
||||
if self._new:
|
||||
self.header.addField(*defs)
|
||||
else:
|
||||
raise TypeError("At least one record was added, "
|
||||
"structure can't be changed")
|
||||
|
||||
# 'magic' methods (representation and sequence interface)
|
||||
|
||||
def __repr__(self):
|
||||
return "Dbf stream '%s'\n" % self.stream + repr(self.header)
|
||||
|
||||
def __len__(self):
|
||||
"""Return number of records."""
|
||||
return self.recordCount
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Return `DbfRecord` instance."""
|
||||
return self.RecordClass.fromStream(self, self._fixIndex(index))
|
||||
|
||||
def __setitem__(self, index, record):
|
||||
"""Write `DbfRecord` instance to the stream."""
|
||||
record.index = self._fixIndex(index)
|
||||
record._write()
|
||||
self._changed = True
|
||||
self._new = False
|
||||
|
||||
# def __del__(self):
|
||||
# """Flush stream upon deletion of the object."""
|
||||
# self.flush()
|
||||
|
||||
|
||||
def demo_read(filename):
|
||||
_dbf = Dbf(filename, True)
|
||||
for _rec in _dbf:
|
||||
print()
|
||||
print(repr(_rec))
|
||||
_dbf.close()
|
||||
|
||||
|
||||
def demo_create(filename):
|
||||
_dbf = Dbf(filename, new=True)
|
||||
_dbf.addField(
|
||||
("NAME", "C", 15),
|
||||
("SURNAME", "C", 25),
|
||||
("INITIALS", "C", 10),
|
||||
("BIRTHDATE", "D"),
|
||||
)
|
||||
for (_n, _s, _i, _b) in (
|
||||
("John", "Miller", "YC", (1981, 1, 2)),
|
||||
("Andy", "Larkin", "AL", (1982, 3, 4)),
|
||||
("Bill", "Clinth", "", (1983, 5, 6)),
|
||||
("Bobb", "McNail", "", (1984, 7, 8)),
|
||||
):
|
||||
_rec = _dbf.newRecord()
|
||||
_rec["NAME"] = _n
|
||||
_rec["SURNAME"] = _s
|
||||
_rec["INITIALS"] = _i
|
||||
_rec["BIRTHDATE"] = _b
|
||||
_rec.store()
|
||||
print(repr(_dbf))
|
||||
_dbf.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
_name = len(sys.argv) > 1 and sys.argv[1] or "county.dbf"
|
||||
demo_create(_name)
|
||||
demo_read(_name)
|
||||
|
||||
# vim: set et sw=4 sts=4 :
|
||||
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/python
|
||||
""".DBF creation helpers.
|
||||
|
||||
Note: this is a legacy interface. New code should use Dbf class
|
||||
for table creation (see examples in dbf.py)
|
||||
|
||||
TODO:
|
||||
- handle Memo fields.
|
||||
- check length of the fields according to the
|
||||
`http://www.clicketyclick.dk/databases/xbase/format/data_types.html`
|
||||
|
||||
"""
|
||||
"""History (most recent first)
|
||||
04-jul-2006 [als] added export declaration;
|
||||
updated for dbfpy 2.0
|
||||
15-dec-2005 [yc] define dbf_new.__slots__
|
||||
14-dec-2005 [yc] added vim modeline; retab'd; added doc-strings;
|
||||
dbf_new now is a new class (inherited from object)
|
||||
??-jun-2000 [--] added by Hans Fiby
|
||||
"""
|
||||
|
||||
__version__ = "$Revision: 1.4 $"[11:-2]
|
||||
__date__ = "$Date: 2006/07/04 08:18:18 $"[7:-2]
|
||||
|
||||
__all__ = ["dbf_new"]
|
||||
|
||||
from .dbf import *
|
||||
from .fields import *
|
||||
from .header import *
|
||||
from .record import *
|
||||
|
||||
|
||||
class _FieldDefinition:
|
||||
"""Field definition.
|
||||
|
||||
This is a simple structure, which contains ``name``, ``type``,
|
||||
``len``, ``dec`` and ``cls`` fields.
|
||||
|
||||
Objects also implement get/setitem magic functions, so fields
|
||||
could be accessed via sequence interface, where 'name' has
|
||||
index 0, 'type' index 1, 'len' index 2, 'dec' index 3 and
|
||||
'cls' could be located at index 4.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = "name", "type", "len", "dec", "cls"
|
||||
|
||||
# WARNING: be attentive - dictionaries are mutable!
|
||||
FLD_TYPES = {
|
||||
# type: (cls, len)
|
||||
"C": (DbfCharacterFieldDef, None),
|
||||
"N": (DbfNumericFieldDef, None),
|
||||
"L": (DbfLogicalFieldDef, 1),
|
||||
# FIXME: support memos
|
||||
# "M": (DbfMemoFieldDef),
|
||||
"D": (DbfDateFieldDef, 8),
|
||||
# FIXME: I'm not sure length should be 14 characters!
|
||||
# but temporary I use it, cuz date is 8 characters
|
||||
# and time 6 (hhmmss)
|
||||
"T": (DbfDateTimeFieldDef, 14),
|
||||
}
|
||||
|
||||
def __init__(self, name, type, len=None, dec=0):
|
||||
_cls, _len = self.FLD_TYPES[type]
|
||||
if _len is None:
|
||||
if len is None:
|
||||
raise ValueError("Field length must be defined")
|
||||
_len = len
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.len = _len
|
||||
self.dec = dec
|
||||
self.cls = _cls
|
||||
|
||||
def getDbfField(self):
|
||||
"Return `DbfFieldDef` instance from the current definition."
|
||||
return self.cls(self.name, self.len, self.dec)
|
||||
|
||||
def appendToHeader(self, dbfh):
|
||||
"""Create a `DbfFieldDef` instance and append it to the dbf header.
|
||||
|
||||
Arguments:
|
||||
dbfh: `DbfHeader` instance.
|
||||
|
||||
"""
|
||||
_dbff = self.getDbfField()
|
||||
dbfh.addField(_dbff)
|
||||
|
||||
|
||||
class dbf_new:
|
||||
"""New .DBF creation helper.
|
||||
|
||||
Example Usage:
|
||||
|
||||
dbfn = dbf_new()
|
||||
dbfn.add_field("name",'C',80)
|
||||
dbfn.add_field("price",'N',10,2)
|
||||
dbfn.add_field("date",'D',8)
|
||||
dbfn.write("tst.dbf")
|
||||
|
||||
Note:
|
||||
This module cannot handle Memo-fields,
|
||||
they are special.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("fields",)
|
||||
|
||||
FieldDefinitionClass = _FieldDefinition
|
||||
|
||||
def __init__(self):
|
||||
self.fields = []
|
||||
|
||||
def add_field(self, name, typ, len, dec=0):
|
||||
"""Add field definition.
|
||||
|
||||
Arguments:
|
||||
name:
|
||||
field name (str object). field name must not
|
||||
contain ASCII NULs and it's length shouldn't
|
||||
exceed 10 characters.
|
||||
typ:
|
||||
type of the field. this must be a single character
|
||||
from the "CNLMDT" set meaning character, numeric,
|
||||
logical, memo, date and date/time respectively.
|
||||
len:
|
||||
length of the field. this argument is used only for
|
||||
the character and numeric fields. all other fields
|
||||
have fixed length.
|
||||
FIXME: use None as a default for this argument?
|
||||
dec:
|
||||
decimal precision. used only for the numric fields.
|
||||
|
||||
"""
|
||||
self.fields.append(self.FieldDefinitionClass(name, typ, len, dec))
|
||||
|
||||
def write(self, filename):
|
||||
"""Create empty .DBF file using current structure."""
|
||||
_dbfh = DbfHeader()
|
||||
_dbfh.setCurrentDate()
|
||||
for _fldDef in self.fields:
|
||||
_fldDef.appendToHeader(_dbfh)
|
||||
|
||||
_dbfStream = open(filename, "wb")
|
||||
_dbfh.write(_dbfStream)
|
||||
_dbfStream.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# create a new DBF-File
|
||||
dbfn = dbf_new()
|
||||
dbfn.add_field("name", 'C', 80)
|
||||
dbfn.add_field("price", 'N', 10, 2)
|
||||
dbfn.add_field("date", 'D', 8)
|
||||
dbfn.write("tst.dbf")
|
||||
# test new dbf
|
||||
print("*** created tst.dbf: ***")
|
||||
dbft = Dbf('tst.dbf', readOnly=0)
|
||||
print(repr(dbft))
|
||||
# add a record
|
||||
rec = DbfRecord(dbft)
|
||||
rec['name'] = 'something'
|
||||
rec['price'] = 10.5
|
||||
rec['date'] = (2000, 1, 12)
|
||||
rec.store()
|
||||
# add another record
|
||||
rec = DbfRecord(dbft)
|
||||
rec['name'] = 'foo and bar'
|
||||
rec['price'] = 12234
|
||||
rec['date'] = (1992, 7, 15)
|
||||
rec.store()
|
||||
|
||||
# show the records
|
||||
print("*** inserted 2 records into tst.dbf: ***")
|
||||
print(repr(dbft))
|
||||
for i1 in range(len(dbft)):
|
||||
rec = dbft[i1]
|
||||
for fldName in dbft.fieldNames:
|
||||
print('{}:\t {}'.format(fldName, rec[fldName]))
|
||||
print()
|
||||
dbft.close()
|
||||
|
||||
# vim: set et sts=4 sw=4 :
|
||||
@@ -0,0 +1,475 @@
|
||||
"""DBF fields definitions.
|
||||
|
||||
TODO:
|
||||
- make memos work
|
||||
"""
|
||||
"""History (most recent first):
|
||||
26-may-2009 [als] DbfNumericFieldDef.decodeValue: strip zero bytes
|
||||
05-feb-2009 [als] DbfDateFieldDef.encodeValue: empty arg produces empty date
|
||||
16-sep-2008 [als] DbfNumericFieldDef decoding looks for decimal point
|
||||
in the value to select float or integer return type
|
||||
13-mar-2008 [als] check field name length in constructor
|
||||
11-feb-2007 [als] handle value conversion errors
|
||||
10-feb-2007 [als] DbfFieldDef: added .rawFromRecord()
|
||||
01-dec-2006 [als] Timestamp columns use None for empty values
|
||||
31-oct-2006 [als] support field types 'F' (float), 'I' (integer)
|
||||
and 'Y' (currency);
|
||||
automate export and registration of field classes
|
||||
04-jul-2006 [als] added export declaration
|
||||
10-mar-2006 [als] decode empty values for Date and Logical fields;
|
||||
show field name in errors
|
||||
10-mar-2006 [als] fix Numeric value decoding: according to spec,
|
||||
value always is string representation of the number;
|
||||
ensure that encoded Numeric value fits into the field
|
||||
20-dec-2005 [yc] use field names in upper case
|
||||
15-dec-2005 [yc] field definitions moved from `dbf`.
|
||||
"""
|
||||
|
||||
__version__ = "$Revision: 1.14 $"[11:-2]
|
||||
__date__ = "$Date: 2009/05/26 05:16:51 $"[7:-2]
|
||||
|
||||
__all__ = ["lookupFor"] # field classes added at the end of the module
|
||||
|
||||
import datetime
|
||||
import struct
|
||||
import sys
|
||||
from functools import total_ordering
|
||||
|
||||
from . import utils
|
||||
|
||||
# abstract definitions
|
||||
|
||||
|
||||
@total_ordering
|
||||
class DbfFieldDef:
|
||||
"""Abstract field definition.
|
||||
|
||||
Child classes must override ``type`` class attribute to provide datatype
|
||||
information of the field definition. For more info about types visit
|
||||
`http://www.clicketyclick.dk/databases/xbase/format/data_types.html`
|
||||
|
||||
Also child classes must override ``defaultValue`` field to provide
|
||||
default value for the field value.
|
||||
|
||||
If child class has fixed length ``length`` class attribute must be
|
||||
overridden and set to the valid value. None value means, that field
|
||||
isn't of fixed length.
|
||||
|
||||
Note: ``name`` field must not be changed after instantiation.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("name", "decimalCount", "start", "end", "ignoreErrors")
|
||||
|
||||
# length of the field, None in case of variable-length field,
|
||||
# or a number if this field is a fixed-length field
|
||||
length = None
|
||||
|
||||
# field type. for more information about fields types visit
|
||||
# `http://www.clicketyclick.dk/databases/xbase/format/data_types.html`
|
||||
# must be overridden in child classes
|
||||
typeCode = None
|
||||
|
||||
# default value for the field. this field must be
|
||||
# overridden in child classes
|
||||
defaultValue = None
|
||||
|
||||
def __init__(self, name, length=None, decimalCount=None,
|
||||
start=None, stop=None, ignoreErrors=False):
|
||||
"""Initialize instance."""
|
||||
assert self.typeCode is not None, "Type code must be overridden"
|
||||
assert self.defaultValue is not None, "Default value must be overridden"
|
||||
# fix arguments
|
||||
if len(name) > 10:
|
||||
raise ValueError("Field name \"%s\" is too long" % name)
|
||||
name = str(name).upper()
|
||||
if self.__class__.length is None:
|
||||
if length is None:
|
||||
raise ValueError("[%s] Length isn't specified" % name)
|
||||
length = int(length)
|
||||
if length <= 0:
|
||||
raise ValueError("[%s] Length must be a positive integer" % name)
|
||||
else:
|
||||
length = self.length
|
||||
if decimalCount is None:
|
||||
decimalCount = 0
|
||||
# set fields
|
||||
self.name = name
|
||||
# FIXME: validate length according to the specification at
|
||||
# http://www.clicketyclick.dk/databases/xbase/format/data_types.html
|
||||
self.length = length
|
||||
self.decimalCount = decimalCount
|
||||
self.ignoreErrors = ignoreErrors
|
||||
self.start = start
|
||||
self.end = stop
|
||||
|
||||
def __eq__(self, other):
|
||||
return repr(self) == repr(other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return repr(self) != repr(other)
|
||||
|
||||
def __lt__(self, other):
|
||||
return repr(self) < repr(other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
|
||||
def fromString(cls, string, start, ignoreErrors=False):
|
||||
"""Decode dbf field definition from the string data.
|
||||
|
||||
Arguments:
|
||||
string:
|
||||
a string, dbf definition is decoded from. length of
|
||||
the string must be 32 bytes.
|
||||
start:
|
||||
position in the database file.
|
||||
ignoreErrors:
|
||||
initial error processing mode for the new field (boolean)
|
||||
|
||||
"""
|
||||
assert len(string) == 32
|
||||
_length = string[16]
|
||||
return cls(utils.unzfill(string)[:11].decode('utf-8'), _length,
|
||||
string[17], start, start + _length, ignoreErrors=ignoreErrors)
|
||||
fromString = classmethod(fromString)
|
||||
|
||||
def toString(self):
|
||||
"""Return encoded field definition.
|
||||
|
||||
Return:
|
||||
Return value is a string object containing encoded
|
||||
definition of this field.
|
||||
|
||||
"""
|
||||
_name = self.name.ljust(11, '\0')
|
||||
return (
|
||||
_name +
|
||||
self.typeCode +
|
||||
# data address
|
||||
chr(0) * 4 +
|
||||
chr(self.length) +
|
||||
chr(self.decimalCount) +
|
||||
chr(0) * 14
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return "%-10s %1s %3d %3d" % self.fieldInfo()
|
||||
|
||||
def fieldInfo(self):
|
||||
"""Return field information.
|
||||
|
||||
Return:
|
||||
Return value is a (name, type, length, decimals) tuple.
|
||||
|
||||
"""
|
||||
return (self.name, self.typeCode, self.length, self.decimalCount)
|
||||
|
||||
def rawFromRecord(self, record):
|
||||
"""Return a "raw" field value from the record string."""
|
||||
return record[self.start:self.end]
|
||||
|
||||
def decodeFromRecord(self, record):
|
||||
"""Return decoded field value from the record string."""
|
||||
try:
|
||||
return self.decodeValue(self.rawFromRecord(record))
|
||||
except Exception:
|
||||
if self.ignoreErrors:
|
||||
return utils.INVALID_VALUE
|
||||
else:
|
||||
raise
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return decoded value from string value.
|
||||
|
||||
This method shouldn't be used publicly. It's called from the
|
||||
`decodeFromRecord` method.
|
||||
|
||||
This is an abstract method and it must be overridden in child classes.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return str object containing encoded field value.
|
||||
|
||||
This is an abstract method and it must be overridden in child classes.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
# real classes
|
||||
|
||||
|
||||
class DbfCharacterFieldDef(DbfFieldDef):
|
||||
"""Definition of the character field."""
|
||||
|
||||
typeCode = "C"
|
||||
defaultValue = b''
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return string object.
|
||||
|
||||
Return value is a ``value`` argument with stripped right spaces.
|
||||
|
||||
"""
|
||||
return value.rstrip(b' ').decode('utf-8')
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return raw data string encoded from a ``value``."""
|
||||
return str(value)[:self.length].ljust(self.length)
|
||||
|
||||
|
||||
class DbfNumericFieldDef(DbfFieldDef):
|
||||
"""Definition of the numeric field."""
|
||||
|
||||
typeCode = "N"
|
||||
# XXX: now I'm not sure it was a good idea to make a class field
|
||||
# `defaultValue` instead of a generic method as it was implemented
|
||||
# previously -- it's ok with all types except number, cuz
|
||||
# if self.decimalCount is 0, we should return 0 and 0.0 otherwise.
|
||||
defaultValue = 0
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return a number decoded from ``value``.
|
||||
|
||||
If decimals is zero, value will be decoded as an integer;
|
||||
or as a float otherwise.
|
||||
|
||||
Return:
|
||||
Return value is a int (long) or float instance.
|
||||
|
||||
"""
|
||||
value = value.strip(b' \0')
|
||||
if b'.' in value:
|
||||
# a float (has decimal separator)
|
||||
return float(value)
|
||||
elif value:
|
||||
# must be an integer
|
||||
return int(value)
|
||||
else:
|
||||
return 0
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return string containing encoded ``value``."""
|
||||
_rv = ("%*.*f" % (self.length, self.decimalCount, value))
|
||||
if len(_rv) > self.length:
|
||||
_ppos = _rv.find(".")
|
||||
if 0 <= _ppos <= self.length:
|
||||
_rv = _rv[:self.length]
|
||||
else:
|
||||
raise ValueError("[%s] Numeric overflow: %s (field width: %i)"
|
||||
% (self.name, _rv, self.length))
|
||||
return _rv
|
||||
|
||||
|
||||
class DbfFloatFieldDef(DbfNumericFieldDef):
|
||||
"""Definition of the float field - same as numeric."""
|
||||
|
||||
typeCode = "F"
|
||||
|
||||
|
||||
class DbfIntegerFieldDef(DbfFieldDef):
|
||||
"""Definition of the integer field."""
|
||||
|
||||
typeCode = "I"
|
||||
length = 4
|
||||
defaultValue = 0
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return an integer number decoded from ``value``."""
|
||||
return struct.unpack("<i", value)[0]
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return string containing encoded ``value``."""
|
||||
return struct.pack("<i", int(value))
|
||||
|
||||
|
||||
class DbfCurrencyFieldDef(DbfFieldDef):
|
||||
"""Definition of the currency field."""
|
||||
|
||||
typeCode = "Y"
|
||||
length = 8
|
||||
defaultValue = 0.0
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return float number decoded from ``value``."""
|
||||
return struct.unpack("<q", value)[0] / 10000.
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return string containing encoded ``value``."""
|
||||
return struct.pack("<q", round(value * 10000))
|
||||
|
||||
|
||||
class DbfLogicalFieldDef(DbfFieldDef):
|
||||
"""Definition of the logical field."""
|
||||
|
||||
typeCode = "L"
|
||||
defaultValue = -1
|
||||
length = 1
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return True, False or -1 decoded from ``value``."""
|
||||
# Note: value always is 1-char string
|
||||
if value == "?":
|
||||
return -1
|
||||
if value in "NnFf ":
|
||||
return False
|
||||
if value in "YyTt":
|
||||
return True
|
||||
raise ValueError("[{}] Invalid logical value {!r}".format(self.name, value))
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return a character from the "TF?" set.
|
||||
|
||||
Return:
|
||||
Return value is "T" if ``value`` is True
|
||||
"?" if value is -1 or False otherwise.
|
||||
|
||||
"""
|
||||
if value is True:
|
||||
return "T"
|
||||
if value == -1:
|
||||
return "?"
|
||||
return "F"
|
||||
|
||||
|
||||
class DbfMemoFieldDef(DbfFieldDef):
|
||||
"""Definition of the memo field.
|
||||
|
||||
Note: memos aren't currently completely supported.
|
||||
|
||||
"""
|
||||
|
||||
typeCode = "M"
|
||||
defaultValue = " " * 10
|
||||
length = 10
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return int .dbt block number decoded from the string object."""
|
||||
# return int(value)
|
||||
raise NotImplementedError
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return raw data string encoded from a ``value``.
|
||||
|
||||
Note: this is an internal method.
|
||||
|
||||
"""
|
||||
# return str(value)[:self.length].ljust(self.length)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DbfDateFieldDef(DbfFieldDef):
|
||||
"""Definition of the date field."""
|
||||
|
||||
typeCode = "D"
|
||||
defaultValue = utils.classproperty(lambda cls: datetime.date.today())
|
||||
# "yyyymmdd" gives us 8 characters
|
||||
length = 8
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return a ``datetime.date`` instance decoded from ``value``."""
|
||||
if value.strip():
|
||||
return utils.getDate(value)
|
||||
else:
|
||||
return None
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return a string-encoded value.
|
||||
|
||||
``value`` argument should be a value suitable for the
|
||||
`utils.getDate` call.
|
||||
|
||||
Return:
|
||||
Return value is a string in format "yyyymmdd".
|
||||
|
||||
"""
|
||||
if value:
|
||||
return utils.getDate(value).strftime("%Y%m%d")
|
||||
else:
|
||||
return " " * self.length
|
||||
|
||||
|
||||
class DbfDateTimeFieldDef(DbfFieldDef):
|
||||
"""Definition of the timestamp field."""
|
||||
|
||||
# a difference between JDN (Julian Day Number)
|
||||
# and GDN (Gregorian Day Number). note, that GDN < JDN
|
||||
JDN_GDN_DIFF = 1721425
|
||||
typeCode = "T"
|
||||
defaultValue = utils.classproperty(lambda cls: datetime.datetime.now())
|
||||
# two 32-bits integers representing JDN and amount of
|
||||
# milliseconds respectively gives us 8 bytes.
|
||||
# note, that values must be encoded in LE byteorder.
|
||||
length = 8
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return a `datetime.datetime` instance."""
|
||||
assert len(value) == self.length
|
||||
# LE byteorder
|
||||
_jdn, _msecs = struct.unpack("<2I", value)
|
||||
if _jdn >= 1:
|
||||
_rv = datetime.datetime.fromordinal(_jdn - self.JDN_GDN_DIFF)
|
||||
_rv += datetime.timedelta(0, _msecs / 1000.0)
|
||||
else:
|
||||
# empty date
|
||||
_rv = None
|
||||
return _rv
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return a string-encoded ``value``."""
|
||||
if value:
|
||||
value = utils.getDateTime(value)
|
||||
# LE byteorder
|
||||
_rv = struct.pack("<2I", value.toordinal() + self.JDN_GDN_DIFF,
|
||||
(value.hour * 3600 + value.minute * 60 + value.second) * 1000)
|
||||
else:
|
||||
_rv = "\0" * self.length
|
||||
assert len(_rv) == self.length
|
||||
return _rv
|
||||
|
||||
|
||||
_fieldsRegistry = {}
|
||||
|
||||
|
||||
def registerField(fieldCls):
|
||||
"""Register field definition class.
|
||||
|
||||
``fieldCls`` should be subclass of the `DbfFieldDef`.
|
||||
|
||||
Use `lookupFor` to retrieve field definition class
|
||||
by the type code.
|
||||
|
||||
"""
|
||||
assert fieldCls.typeCode is not None, "Type code isn't defined"
|
||||
# XXX: use fieldCls.typeCode.upper()? in case of any decign
|
||||
# don't forget to look to the same comment in ``lookupFor`` method
|
||||
_fieldsRegistry[fieldCls.typeCode] = fieldCls
|
||||
|
||||
|
||||
def lookupFor(typeCode):
|
||||
"""Return field definition class for the given type code.
|
||||
|
||||
``typeCode`` must be a single character. That type should be
|
||||
previously registered.
|
||||
|
||||
Use `registerField` to register new field class.
|
||||
|
||||
Return:
|
||||
Return value is a subclass of the `DbfFieldDef`.
|
||||
|
||||
"""
|
||||
# XXX: use typeCode.upper()? in case of any decign don't
|
||||
# forget to look to the same comment in ``registerField``
|
||||
return _fieldsRegistry[chr(typeCode)]
|
||||
|
||||
# register generic types
|
||||
|
||||
|
||||
for (_name, _val) in list(globals().items()):
|
||||
if isinstance(_val, type) and issubclass(_val, DbfFieldDef) \
|
||||
and (_name != "DbfFieldDef"):
|
||||
__all__.append(_name)
|
||||
registerField(_val)
|
||||
del _name, _val
|
||||
|
||||
# vim: et sts=4 sw=4 :
|
||||
@@ -0,0 +1,270 @@
|
||||
"""DBF header definition.
|
||||
|
||||
TODO:
|
||||
- handle encoding of the character fields
|
||||
(encoding information stored in the DBF header)
|
||||
|
||||
"""
|
||||
"""History (most recent first):
|
||||
16-sep-2010 [als] fromStream: fix century of the last update field
|
||||
11-feb-2007 [als] added .ignoreErrors
|
||||
10-feb-2007 [als] added __getitem__: return field definitions
|
||||
by field name or field number (zero-based)
|
||||
04-jul-2006 [als] added export declaration
|
||||
15-dec-2005 [yc] created
|
||||
"""
|
||||
|
||||
__version__ = "$Revision: 1.6 $"[11:-2]
|
||||
__date__ = "$Date: 2010/09/16 05:06:39 $"[7:-2]
|
||||
|
||||
__all__ = ["DbfHeader"]
|
||||
|
||||
import datetime
|
||||
import io
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from . import fields
|
||||
from .utils import getDate
|
||||
|
||||
|
||||
class DbfHeader:
|
||||
"""Dbf header definition.
|
||||
|
||||
For more information about dbf header format visit
|
||||
`http://www.clicketyclick.dk/databases/xbase/format/dbf.html#DBF_STRUCT`
|
||||
|
||||
Examples:
|
||||
Create an empty dbf header and add some field definitions:
|
||||
dbfh = DbfHeader()
|
||||
dbfh.addField(("name", "C", 10))
|
||||
dbfh.addField(("date", "D"))
|
||||
dbfh.addField(DbfNumericFieldDef("price", 5, 2))
|
||||
Create a dbf header with field definitions:
|
||||
dbfh = DbfHeader([
|
||||
("name", "C", 10),
|
||||
("date", "D"),
|
||||
DbfNumericFieldDef("price", 5, 2),
|
||||
])
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("signature", "fields", "lastUpdate", "recordLength",
|
||||
"recordCount", "headerLength", "changed", "_ignore_errors")
|
||||
|
||||
# instance construction and initialization methods
|
||||
|
||||
def __init__(self, fields=None, headerLength=0, recordLength=0,
|
||||
recordCount=0, signature=0x03, lastUpdate=None, ignoreErrors=False):
|
||||
"""Initialize instance.
|
||||
|
||||
Arguments:
|
||||
fields:
|
||||
a list of field definitions;
|
||||
recordLength:
|
||||
size of the records;
|
||||
headerLength:
|
||||
size of the header;
|
||||
recordCount:
|
||||
number of records stored in DBF;
|
||||
signature:
|
||||
version number (aka signature). using 0x03 as a default meaning
|
||||
"File without DBT". for more information about this field visit
|
||||
``http://www.clicketyclick.dk/databases/xbase/format/dbf.html#DBF_NOTE_1_TARGET``
|
||||
lastUpdate:
|
||||
date of the DBF's update. this could be a string ('yymmdd' or
|
||||
'yyyymmdd'), timestamp (int or float), datetime/date value,
|
||||
a sequence (assuming (yyyy, mm, dd, ...)) or an object having
|
||||
callable ``ticks`` field.
|
||||
ignoreErrors:
|
||||
error processing mode for DBF fields (boolean)
|
||||
|
||||
"""
|
||||
self.signature = signature
|
||||
if fields is None:
|
||||
self.fields = []
|
||||
else:
|
||||
self.fields = list(fields)
|
||||
self.lastUpdate = getDate(lastUpdate)
|
||||
self.recordLength = recordLength
|
||||
self.headerLength = headerLength
|
||||
self.recordCount = recordCount
|
||||
self.ignoreErrors = ignoreErrors
|
||||
# XXX: I'm not sure this is safe to
|
||||
# initialize `self.changed` in this way
|
||||
self.changed = bool(self.fields)
|
||||
|
||||
# @classmethod
|
||||
def fromString(cls, string):
|
||||
"""Return header instance from the string object."""
|
||||
return cls.fromStream(io.StringIO(str(string)))
|
||||
fromString = classmethod(fromString)
|
||||
|
||||
# @classmethod
|
||||
def fromStream(cls, stream):
|
||||
"""Return header object from the stream."""
|
||||
stream.seek(0)
|
||||
first_32 = stream.read(32)
|
||||
if type(first_32) != bytes:
|
||||
_data = bytes(first_32, sys.getfilesystemencoding())
|
||||
_data = first_32
|
||||
(_cnt, _hdrLen, _recLen) = struct.unpack("<I2H", _data[4:12])
|
||||
# reserved = _data[12:32]
|
||||
_year = _data[1]
|
||||
if _year < 80:
|
||||
# dBase II started at 1980. It is quite unlikely
|
||||
# that actual last update date is before that year.
|
||||
_year += 2000
|
||||
else:
|
||||
_year += 1900
|
||||
# create header object
|
||||
_obj = cls(None, _hdrLen, _recLen, _cnt, _data[0],
|
||||
(_year, _data[2], _data[3]))
|
||||
# append field definitions
|
||||
# position 0 is for the deletion flag
|
||||
_pos = 1
|
||||
_data = stream.read(1)
|
||||
while _data != b'\r':
|
||||
_data += stream.read(31)
|
||||
_fld = fields.lookupFor(_data[11]).fromString(_data, _pos)
|
||||
_obj._addField(_fld)
|
||||
_pos = _fld.end
|
||||
_data = stream.read(1)
|
||||
return _obj
|
||||
fromStream = classmethod(fromStream)
|
||||
|
||||
# properties
|
||||
|
||||
year = property(lambda self: self.lastUpdate.year)
|
||||
month = property(lambda self: self.lastUpdate.month)
|
||||
day = property(lambda self: self.lastUpdate.day)
|
||||
|
||||
def ignoreErrors(self, value):
|
||||
"""Update `ignoreErrors` flag on self and all fields"""
|
||||
self._ignore_errors = value = bool(value)
|
||||
for _field in self.fields:
|
||||
_field.ignoreErrors = value
|
||||
ignoreErrors = property(
|
||||
lambda self: self._ignore_errors,
|
||||
ignoreErrors,
|
||||
doc="""Error processing mode for DBF field value conversion
|
||||
|
||||
if set, failing field value conversion will return
|
||||
``INVALID_VALUE`` instead of raising conversion error.
|
||||
|
||||
""")
|
||||
|
||||
# object representation
|
||||
|
||||
def __repr__(self):
|
||||
_rv = """\
|
||||
Version (signature): 0x%02x
|
||||
Last update: %s
|
||||
Header length: %d
|
||||
Record length: %d
|
||||
Record count: %d
|
||||
FieldName Type Len Dec
|
||||
""" % (self.signature, self.lastUpdate, self.headerLength,
|
||||
self.recordLength, self.recordCount)
|
||||
_rv += "\n".join(
|
||||
["%10s %4s %3s %3s" % _fld.fieldInfo() for _fld in self.fields]
|
||||
)
|
||||
return _rv
|
||||
|
||||
# internal methods
|
||||
|
||||
def _addField(self, *defs):
|
||||
"""Internal variant of the `addField` method.
|
||||
|
||||
This method doesn't set `self.changed` field to True.
|
||||
|
||||
Return value is a length of the appended records.
|
||||
Note: this method doesn't modify ``recordLength`` and
|
||||
``headerLength`` fields. Use `addField` instead of this
|
||||
method if you don't exactly know what you're doing.
|
||||
|
||||
"""
|
||||
# insure we have dbf.DbfFieldDef instances first (instantiation
|
||||
# from the tuple could raise an error, in such a case I don't
|
||||
# wanna add any of the definitions -- all will be ignored)
|
||||
_defs = []
|
||||
_recordLength = 0
|
||||
for _def in defs:
|
||||
if isinstance(_def, fields.DbfFieldDef):
|
||||
_obj = _def
|
||||
else:
|
||||
(_name, _type, _len, _dec) = (tuple(_def) + (None,) * 4)[:4]
|
||||
_cls = fields.lookupFor(_type)
|
||||
_obj = _cls(_name, _len, _dec, ignoreErrors=self._ignore_errors)
|
||||
_recordLength += _obj.length
|
||||
_defs.append(_obj)
|
||||
# and now extend field definitions and
|
||||
# update record length
|
||||
self.fields += _defs
|
||||
return _recordLength
|
||||
|
||||
# interface methods
|
||||
|
||||
def addField(self, *defs):
|
||||
"""Add field definition to the header.
|
||||
|
||||
Examples:
|
||||
dbfh.addField(
|
||||
("name", "C", 20),
|
||||
dbf.DbfCharacterFieldDef("surname", 20),
|
||||
dbf.DbfDateFieldDef("birthdate"),
|
||||
("member", "L"),
|
||||
)
|
||||
dbfh.addField(("price", "N", 5, 2))
|
||||
dbfh.addField(dbf.DbfNumericFieldDef("origprice", 5, 2))
|
||||
|
||||
"""
|
||||
_oldLen = self.recordLength
|
||||
self.recordLength += self._addField(*defs)
|
||||
if not _oldLen:
|
||||
self.recordLength += 1
|
||||
# XXX: may be just use:
|
||||
# self.recordeLength += self._addField(*defs) + bool(not _oldLen)
|
||||
# recalculate headerLength
|
||||
self.headerLength = 32 + (32 * len(self.fields)) + 1
|
||||
self.changed = True
|
||||
|
||||
def write(self, stream):
|
||||
"""Encode and write header to the stream."""
|
||||
stream.seek(0)
|
||||
stream.write(self.toString())
|
||||
fields = [_fld.toString() for _fld in self.fields]
|
||||
stream.write(''.join(fields).encode(sys.getfilesystemencoding()))
|
||||
stream.write(b'\x0D') # cr at end of all header data
|
||||
self.changed = False
|
||||
|
||||
def toString(self):
|
||||
"""Returned 32 chars length string with encoded header."""
|
||||
return struct.pack("<4BI2H",
|
||||
self.signature,
|
||||
self.year - 1900,
|
||||
self.month,
|
||||
self.day,
|
||||
self.recordCount,
|
||||
self.headerLength,
|
||||
self.recordLength) + (b'\x00' * 20)
|
||||
# TODO: figure out if bytes(utf-8) is correct here.
|
||||
|
||||
def setCurrentDate(self):
|
||||
"""Update ``self.lastUpdate`` field with current date value."""
|
||||
self.lastUpdate = datetime.date.today()
|
||||
|
||||
def __getitem__(self, item):
|
||||
"""Return a field definition by numeric index or name string"""
|
||||
if isinstance(item, str):
|
||||
_name = item.upper()
|
||||
for _field in self.fields:
|
||||
if _field.name == _name:
|
||||
return _field
|
||||
else:
|
||||
raise KeyError(item)
|
||||
else:
|
||||
# item must be field index
|
||||
return self.fields[item]
|
||||
|
||||
# vim: et sts=4 sw=4 :
|
||||
@@ -0,0 +1,267 @@
|
||||
"""DBF record definition.
|
||||
|
||||
"""
|
||||
"""History (most recent first):
|
||||
11-feb-2007 [als] __repr__: added special case for invalid field values
|
||||
10-feb-2007 [als] added .rawFromStream()
|
||||
30-oct-2006 [als] fix record length in .fromStream()
|
||||
04-jul-2006 [als] added export declaration
|
||||
20-dec-2005 [yc] DbfRecord.write() -> DbfRecord._write();
|
||||
added delete() method.
|
||||
16-dec-2005 [yc] record definition moved from `dbf`.
|
||||
"""
|
||||
|
||||
__version__ = "$Revision: 1.7 $"[11:-2]
|
||||
__date__ = "$Date: 2007/02/11 09:05:49 $"[7:-2]
|
||||
|
||||
__all__ = ["DbfRecord"]
|
||||
|
||||
import sys
|
||||
|
||||
from . import utils
|
||||
|
||||
|
||||
class DbfRecord:
|
||||
"""DBF record.
|
||||
|
||||
Instances of this class shouldn't be created manually,
|
||||
use `dbf.Dbf.newRecord` instead.
|
||||
|
||||
Class implements mapping/sequence interface, so
|
||||
fields could be accessed via their names or indexes
|
||||
(names is a preferred way to access fields).
|
||||
|
||||
Hint:
|
||||
Use `store` method to save modified record.
|
||||
|
||||
Examples:
|
||||
Add new record to the database:
|
||||
db = Dbf(filename)
|
||||
rec = db.newRecord()
|
||||
rec["FIELD1"] = value1
|
||||
rec["FIELD2"] = value2
|
||||
rec.store()
|
||||
Or the same, but modify existed
|
||||
(second in this case) record:
|
||||
db = Dbf(filename)
|
||||
rec = db[2]
|
||||
rec["FIELD1"] = value1
|
||||
rec["FIELD2"] = value2
|
||||
rec.store()
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = "dbf", "index", "deleted", "fieldData"
|
||||
|
||||
# creation and initialization
|
||||
|
||||
def __init__(self, dbf, index=None, deleted=False, data=None):
|
||||
"""Instance initialization.
|
||||
|
||||
Arguments:
|
||||
dbf:
|
||||
A `Dbf.Dbf` instance this record belonogs to.
|
||||
index:
|
||||
An integer record index or None. If this value is
|
||||
None, record will be appended to the DBF.
|
||||
deleted:
|
||||
Boolean flag indicating whether this record
|
||||
is a deleted record.
|
||||
data:
|
||||
A sequence or None. This is a data of the fields.
|
||||
If this argument is None, default values will be used.
|
||||
|
||||
"""
|
||||
self.dbf = dbf
|
||||
# XXX: I'm not sure ``index`` is necessary
|
||||
self.index = index
|
||||
self.deleted = deleted
|
||||
if data is None:
|
||||
self.fieldData = [_fd.defaultValue for _fd in dbf.header.fields]
|
||||
else:
|
||||
self.fieldData = list(data)
|
||||
|
||||
# XXX: validate self.index before calculating position?
|
||||
position = property(lambda self: self.dbf.header.headerLength + \
|
||||
self.index * self.dbf.header.recordLength)
|
||||
|
||||
def rawFromStream(cls, dbf, index):
|
||||
"""Return raw record contents read from the stream.
|
||||
|
||||
Arguments:
|
||||
dbf:
|
||||
A `Dbf.Dbf` instance containing the record.
|
||||
index:
|
||||
Index of the record in the records' container.
|
||||
This argument can't be None in this call.
|
||||
|
||||
Return value is a string containing record data in DBF format.
|
||||
|
||||
"""
|
||||
# XXX: may be write smth assuming, that current stream
|
||||
# position is the required one? it could save some
|
||||
# time required to calculate where to seek in the file
|
||||
dbf.stream.seek(dbf.header.headerLength +
|
||||
index * dbf.header.recordLength)
|
||||
return dbf.stream.read(dbf.header.recordLength)
|
||||
rawFromStream = classmethod(rawFromStream)
|
||||
|
||||
def fromStream(cls, dbf, index):
|
||||
"""Return a record read from the stream.
|
||||
|
||||
Arguments:
|
||||
dbf:
|
||||
A `Dbf.Dbf` instance new record should belong to.
|
||||
index:
|
||||
Index of the record in the records' container.
|
||||
This argument can't be None in this call.
|
||||
|
||||
Return value is an instance of the current class.
|
||||
|
||||
"""
|
||||
return cls.fromString(dbf, cls.rawFromStream(dbf, index), index)
|
||||
fromStream = classmethod(fromStream)
|
||||
|
||||
def fromString(cls, dbf, string, index=None):
|
||||
"""Return record read from the string object.
|
||||
|
||||
Arguments:
|
||||
dbf:
|
||||
A `Dbf.Dbf` instance new record should belong to.
|
||||
string:
|
||||
A string new record should be created from.
|
||||
index:
|
||||
Index of the record in the container. If this
|
||||
argument is None, record will be appended.
|
||||
|
||||
Return value is an instance of the current class.
|
||||
|
||||
"""
|
||||
return cls(dbf, index, string[0]=="*",
|
||||
[_fd.decodeFromRecord(string) for _fd in dbf.header.fields])
|
||||
fromString = classmethod(fromString)
|
||||
|
||||
# object representation
|
||||
|
||||
def __repr__(self):
|
||||
_template = "%%%ds: %%s (%%s)" % max([len(_fld)
|
||||
for _fld in self.dbf.fieldNames])
|
||||
_rv = []
|
||||
for _fld in self.dbf.fieldNames:
|
||||
_val = self[_fld]
|
||||
if _val is utils.INVALID_VALUE:
|
||||
_rv.append(_template %
|
||||
(_fld, "None", "value cannot be decoded"))
|
||||
else:
|
||||
_rv.append(_template % (_fld, _val, type(_val)))
|
||||
return "\n".join(_rv)
|
||||
|
||||
# protected methods
|
||||
|
||||
def _write(self):
|
||||
"""Write data to the dbf stream.
|
||||
|
||||
Note:
|
||||
This isn't a public method, it's better to
|
||||
use 'store' instead publicly.
|
||||
Be design ``_write`` method should be called
|
||||
only from the `Dbf` instance.
|
||||
|
||||
|
||||
"""
|
||||
self._validateIndex(False)
|
||||
self.dbf.stream.seek(self.position)
|
||||
self.dbf.stream.write(bytes(self.toString(),
|
||||
sys.getfilesystemencoding()))
|
||||
# FIXME: may be move this write somewhere else?
|
||||
# why we should check this condition for each record?
|
||||
if self.index == len(self.dbf):
|
||||
# this is the last record,
|
||||
# we should write SUB (ASCII 26)
|
||||
self.dbf.stream.write(b"\x1A")
|
||||
|
||||
# utility methods
|
||||
|
||||
def _validateIndex(self, allowUndefined=True, checkRange=False):
|
||||
"""Valid ``self.index`` value.
|
||||
|
||||
If ``allowUndefined`` argument is True functions does nothing
|
||||
in case of ``self.index`` pointing to None object.
|
||||
|
||||
"""
|
||||
if self.index is None:
|
||||
if not allowUndefined:
|
||||
raise ValueError("Index is undefined")
|
||||
elif self.index < 0:
|
||||
raise ValueError("Index can't be negative (%s)" % self.index)
|
||||
elif checkRange and self.index <= self.dbf.header.recordCount:
|
||||
raise ValueError("There are only %d records in the DBF" %
|
||||
self.dbf.header.recordCount)
|
||||
|
||||
# interface methods
|
||||
|
||||
def store(self):
|
||||
"""Store current record in the DBF.
|
||||
|
||||
If ``self.index`` is None, this record will be appended to the
|
||||
records of the DBF this records belongs to; or replaced otherwise.
|
||||
|
||||
"""
|
||||
self._validateIndex()
|
||||
if self.index is None:
|
||||
self.index = len(self.dbf)
|
||||
self.dbf.append(self)
|
||||
else:
|
||||
self.dbf[self.index] = self
|
||||
|
||||
def delete(self):
|
||||
"""Mark method as deleted."""
|
||||
self.deleted = True
|
||||
|
||||
def toString(self):
|
||||
"""Return string packed record values."""
|
||||
# for (_def, _dat) in zip(self.dbf.header.fields, self.fieldData):
|
||||
#
|
||||
|
||||
return "".join([" *"[self.deleted]] + [
|
||||
_def.encodeValue(_dat)
|
||||
for (_def, _dat) in zip(self.dbf.header.fields, self.fieldData)
|
||||
])
|
||||
|
||||
def asList(self):
|
||||
"""Return a flat list of fields.
|
||||
|
||||
Note:
|
||||
Change of the list's values won't change
|
||||
real values stored in this object.
|
||||
|
||||
"""
|
||||
return self.fieldData[:]
|
||||
|
||||
def asDict(self):
|
||||
"""Return a dictionary of fields.
|
||||
|
||||
Note:
|
||||
Change of the dicts's values won't change
|
||||
real values stored in this object.
|
||||
|
||||
"""
|
||||
return dict([_i for _i in zip(self.dbf.fieldNames, self.fieldData)])
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Return value by field name or field index."""
|
||||
if isinstance(key, int):
|
||||
# integer index of the field
|
||||
return self.fieldData[key]
|
||||
# assuming string field name
|
||||
return self.fieldData[self.dbf.indexOfFieldName(key)]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Set field value by integer index of the field or string name."""
|
||||
if isinstance(key, int):
|
||||
# integer index of the field
|
||||
return self.fieldData[key]
|
||||
# assuming string field name
|
||||
self.fieldData[self.dbf.indexOfFieldName(key)] = value
|
||||
|
||||
# vim: et sts=4 sw=4 :
|
||||
@@ -0,0 +1,168 @@
|
||||
"""String utilities.
|
||||
|
||||
TODO:
|
||||
- allow strings in getDateTime routine;
|
||||
"""
|
||||
"""History (most recent first):
|
||||
11-feb-2007 [als] added INVALID_VALUE
|
||||
10-feb-2007 [als] allow date strings padded with spaces instead of zeroes
|
||||
20-dec-2005 [yc] handle long objects in getDate/getDateTime
|
||||
16-dec-2005 [yc] created from ``strutil`` module.
|
||||
"""
|
||||
|
||||
__version__ = "$Revision: 1.4 $"[11:-2]
|
||||
__date__ = "$Date: 2007/02/11 08:57:17 $"[7:-2]
|
||||
|
||||
import datetime
|
||||
import time
|
||||
|
||||
|
||||
def unzfill(str):
|
||||
"""Return a string without ASCII NULs.
|
||||
|
||||
This function searchers for the first NUL (ASCII 0) occurrence
|
||||
and truncates string till that position.
|
||||
|
||||
"""
|
||||
try:
|
||||
return str[:str.index(b'\0')]
|
||||
except ValueError:
|
||||
return str
|
||||
|
||||
|
||||
def getDate(date=None):
|
||||
"""Return `datetime.date` instance.
|
||||
|
||||
Type of the ``date`` argument could be one of the following:
|
||||
None:
|
||||
use current date value;
|
||||
datetime.date:
|
||||
this value will be returned;
|
||||
datetime.datetime:
|
||||
the result of the date.date() will be returned;
|
||||
string:
|
||||
assuming "%Y%m%d" or "%y%m%dd" format;
|
||||
number:
|
||||
assuming it's a timestamp (returned for example
|
||||
by the time.time() call;
|
||||
sequence:
|
||||
assuming (year, month, day, ...) sequence;
|
||||
|
||||
Additionally, if ``date`` has callable ``ticks`` attribute,
|
||||
it will be used and result of the called would be treated
|
||||
as a timestamp value.
|
||||
|
||||
"""
|
||||
if date is None:
|
||||
# use current value
|
||||
return datetime.date.today()
|
||||
if isinstance(date, datetime.date):
|
||||
return date
|
||||
if isinstance(date, datetime.datetime):
|
||||
return date.date()
|
||||
if isinstance(date, (int, float)):
|
||||
# date is a timestamp
|
||||
return datetime.date.fromtimestamp(date)
|
||||
if isinstance(date, str):
|
||||
date = date.replace(" ", "0")
|
||||
if len(date) == 6:
|
||||
# yymmdd
|
||||
return datetime.date(*time.strptime(date, "%y%m%d")[:3])
|
||||
# yyyymmdd
|
||||
return datetime.date(*time.strptime(date, "%Y%m%d")[:3])
|
||||
if hasattr(date, "__getitem__"):
|
||||
# a sequence (assuming date/time tuple)
|
||||
return datetime.date(*date[:3])
|
||||
return datetime.date.fromtimestamp(date.ticks())
|
||||
|
||||
|
||||
def getDateTime(value=None):
|
||||
"""Return `datetime.datetime` instance.
|
||||
|
||||
Type of the ``value`` argument could be one of the following:
|
||||
None:
|
||||
use current date value;
|
||||
datetime.date:
|
||||
result will be converted to the `datetime.datetime` instance
|
||||
using midnight;
|
||||
datetime.datetime:
|
||||
``value`` will be returned as is;
|
||||
string:
|
||||
*** CURRENTLY NOT SUPPORTED ***;
|
||||
number:
|
||||
assuming it's a timestamp (returned for example
|
||||
by the time.time() call;
|
||||
sequence:
|
||||
assuming (year, month, day, ...) sequence;
|
||||
|
||||
Additionally, if ``value`` has callable ``ticks`` attribute,
|
||||
it will be used and result of the called would be treated
|
||||
as a timestamp value.
|
||||
|
||||
"""
|
||||
if value is None:
|
||||
# use current value
|
||||
return datetime.datetime.today()
|
||||
if isinstance(value, datetime.datetime):
|
||||
return value
|
||||
if isinstance(value, datetime.date):
|
||||
return datetime.datetime.fromordinal(value.toordinal())
|
||||
if isinstance(value, (int, float)):
|
||||
# value is a timestamp
|
||||
return datetime.datetime.fromtimestamp(value)
|
||||
if isinstance(value, str):
|
||||
raise NotImplementedError("Strings aren't currently implemented")
|
||||
if hasattr(value, "__getitem__"):
|
||||
# a sequence (assuming date/time tuple)
|
||||
return datetime.datetime(*tuple(value)[:6])
|
||||
return datetime.datetime.fromtimestamp(value.ticks())
|
||||
|
||||
|
||||
class classproperty(property):
|
||||
"""Works in the same way as a ``property``, but for the classes."""
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
return self.fget(cls)
|
||||
|
||||
|
||||
class _InvalidValue:
|
||||
|
||||
"""Value returned from DBF records when field validation fails
|
||||
|
||||
The value is not equal to anything except for itself
|
||||
and equal to all empty values: None, 0, empty string etc.
|
||||
In other words, invalid value is equal to None and not equal
|
||||
to None at the same time.
|
||||
|
||||
This value yields zero upon explicit conversion to a number type,
|
||||
empty string for string types, and False for boolean.
|
||||
|
||||
"""
|
||||
|
||||
def __eq__(self, other):
|
||||
return not other
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (other is self)
|
||||
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
def __int__(self):
|
||||
return 0
|
||||
__long__ = __int__
|
||||
|
||||
def __float__(self):
|
||||
return 0.0
|
||||
|
||||
def __str__(self):
|
||||
return ""
|
||||
|
||||
def __repr__(self):
|
||||
return "<INVALID>"
|
||||
|
||||
|
||||
# invalid value is a constant singleton
|
||||
INVALID_VALUE = _InvalidValue()
|
||||
|
||||
# vim: set et sts=4 sw=4 :
|
||||
@@ -0,0 +1,13 @@
|
||||
from io import BytesIO, StringIO
|
||||
|
||||
|
||||
def normalize_input(stream):
|
||||
"""
|
||||
Accept either a str/bytes stream or a file-like object and always return a
|
||||
file-like object.
|
||||
"""
|
||||
if isinstance(stream, str):
|
||||
return StringIO(stream)
|
||||
elif isinstance(stream, bytes):
|
||||
return BytesIO(stream)
|
||||
return stream
|
||||
@@ -1,8 +0,0 @@
|
||||
""" Tablib.
|
||||
"""
|
||||
|
||||
from tablib.core import (
|
||||
Databook, Dataset, detect, import_set,
|
||||
InvalidDatasetType, InvalidDimensions, UnsupportedFormat
|
||||
)
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
tablib.compat
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Tablib compatiblity module.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
is_py3 = (sys.version_info[0] > 2)
|
||||
|
||||
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from tablib.packages.ordereddict import OrderedDict
|
||||
|
||||
|
||||
if is_py3:
|
||||
from io import BytesIO
|
||||
import tablib.packages.xlwt3 as xlwt
|
||||
from tablib.packages import markup3 as markup
|
||||
from tablib.packages import openpyxl3 as openpyxl
|
||||
|
||||
# py3 mappings
|
||||
ifilter = filter
|
||||
xrange = range
|
||||
unicode = str
|
||||
bytes = bytes
|
||||
basestring = str
|
||||
|
||||
else:
|
||||
from cStringIO import StringIO as BytesIO
|
||||
import tablib.packages.xlwt as xlwt
|
||||
from tablib.packages import markup
|
||||
from itertools import ifilter
|
||||
from tablib.packages import openpyxl
|
||||
|
||||
# py2 mappings
|
||||
xrange = xrange
|
||||
unicode = unicode
|
||||
bytes = str
|
||||
basestring = basestring
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# -*- 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
|
||||
from . import _xlsx as xlsx
|
||||
|
||||
available = (json, xls, yaml, csv, tsv, html, xlsx)
|
||||
@@ -1,70 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - CSV Support.
|
||||
"""
|
||||
|
||||
import sys
|
||||
if sys.version_info[0] > 2:
|
||||
is_py3 = True
|
||||
|
||||
from io import StringIO
|
||||
import csv
|
||||
else:
|
||||
is_py3 = False
|
||||
from cStringIO import StringIO
|
||||
import tablib.packages.unicodecsv as csv
|
||||
|
||||
|
||||
|
||||
import os
|
||||
|
||||
import tablib
|
||||
|
||||
|
||||
title = 'csv'
|
||||
extentions = ('csv',)
|
||||
|
||||
|
||||
DEFAULT_ENCODING = 'utf-8'
|
||||
|
||||
|
||||
|
||||
def export_set(dataset):
|
||||
"""Returns CSV representation of Dataset."""
|
||||
stream = StringIO()
|
||||
|
||||
if is_py3:
|
||||
_csv = csv.writer(stream)
|
||||
else:
|
||||
_csv = csv.writer(stream, encoding=DEFAULT_ENCODING)
|
||||
|
||||
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()
|
||||
|
||||
if is_py3:
|
||||
rows = csv.reader(in_stream.splitlines())
|
||||
else:
|
||||
rows = csv.reader(in_stream.splitlines(), encoding=DEFAULT_ENCODING)
|
||||
for i, row in enumerate(rows):
|
||||
|
||||
if (i == 0) and (headers):
|
||||
dset.headers = row
|
||||
else:
|
||||
dset.append(row)
|
||||
|
||||
|
||||
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
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- 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()
|
||||
@@ -1,54 +0,0 @@
|
||||
# -*- 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
|
||||
@@ -1,56 +0,0 @@
|
||||
# -*- 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
|
||||
@@ -1,82 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - XLS Support.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from tablib.compat import BytesIO, 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)
|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - XLSX Support.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
if sys.version_info[0] > 2:
|
||||
from io import BytesIO
|
||||
else:
|
||||
from cStringIO import StringIO as BytesIO
|
||||
|
||||
from tablib.compat import openpyxl
|
||||
|
||||
Workbook = openpyxl.workbook.Workbook
|
||||
ExcelWriter = openpyxl.writer.excel.ExcelWriter
|
||||
get_column_letter = openpyxl.cell.get_column_letter
|
||||
|
||||
from tablib.compat import unicode
|
||||
|
||||
|
||||
title = 'xlsx'
|
||||
extentions = ('xlsx',)
|
||||
|
||||
def export_set(dataset):
|
||||
"""Returns XLSX representation of Dataset."""
|
||||
|
||||
wb = Workbook()
|
||||
ws = wb.worksheets[0]
|
||||
ws.title = dataset.title if dataset.title else 'Tablib Dataset'
|
||||
|
||||
dset_sheet(dataset, ws)
|
||||
|
||||
stream = BytesIO()
|
||||
wb.save(stream)
|
||||
return stream.getvalue()
|
||||
|
||||
|
||||
def export_book(databook):
|
||||
"""Returns XLSX representation of DataBook."""
|
||||
|
||||
wb = Workbook()
|
||||
ew = ExcelWriter(workbook = wb)
|
||||
for i, dset in enumerate(databook._datasets):
|
||||
ws = wb.create_sheet()
|
||||
ws.title = dset.title if dset.title else 'Sheet%s' % (i)
|
||||
|
||||
dset_sheet(dset, ws)
|
||||
|
||||
|
||||
stream = BytesIO()
|
||||
ew.save(stream)
|
||||
return stream.getvalue()
|
||||
|
||||
|
||||
def dset_sheet(dataset, ws):
|
||||
"""Completes given worksheet from given Dataset."""
|
||||
_package = dataset._package(dicts=False)
|
||||
|
||||
for i, sep in enumerate(dataset._separators):
|
||||
_offset = i
|
||||
_package.insert((sep[0] + _offset), (sep[1],))
|
||||
|
||||
for i, row in enumerate(_package):
|
||||
row_number = i + 1
|
||||
for j, col in enumerate(row):
|
||||
col_idx = get_column_letter(j + 1)
|
||||
|
||||
# bold headers
|
||||
if (row_number == 1) and dataset.headers:
|
||||
# ws.cell('%s%s'%(col_idx, row_number)).value = unicode(
|
||||
# '%s' % col, errors='ignore')
|
||||
ws.cell('%s%s'%(col_idx, row_number)).value = unicode(col)
|
||||
style = ws.get_style('%s%s' % (col_idx, row_number))
|
||||
style.font.bold = True
|
||||
ws.freeze_panes = '%s%s' % (col_idx, row_number)
|
||||
|
||||
|
||||
# bold separators
|
||||
elif len(row) < dataset.width:
|
||||
ws.cell('%s%s'%(col_idx, row_number)).value = unicode(
|
||||
'%s' % col, errors='ignore')
|
||||
style = ws.get_style('%s%s' % (col_idx, row_number))
|
||||
style.font.bold = True
|
||||
|
||||
# wrap the rest
|
||||
else:
|
||||
try:
|
||||
if '\n' in col:
|
||||
ws.cell('%s%s'%(col_idx, row_number)).value = unicode(
|
||||
'%s' % col, errors='ignore')
|
||||
style = ws.get_style('%s%s' % (col_idx, row_number))
|
||||
style.alignment.wrap_text
|
||||
else:
|
||||
ws.cell('%s%s'%(col_idx, row_number)).value = unicode(
|
||||
'%s' % col, errors='ignore')
|
||||
except TypeError:
|
||||
ws.cell('%s%s'%(col_idx, row_number)).value = unicode(col)
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
# -*- 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,117 +0,0 @@
|
||||
"""
|
||||
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)
|
||||
@@ -1,118 +0,0 @@
|
||||
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)
|
||||
@@ -1,484 +0,0 @@
|
||||
# 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__
|
||||
@@ -1,484 +0,0 @@
|
||||
# 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,53 +0,0 @@
|
||||
# file openpyxl/__init__.py
|
||||
|
||||
# Copyright (c) 2010 openpyxl
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# @license: http://www.opensource.org/licenses/mit-license.php
|
||||
# @author: Eric Gazoni
|
||||
|
||||
"""Imports for the openpyxl package."""
|
||||
|
||||
# package imports
|
||||
from . import cell
|
||||
from . import namedrange
|
||||
from . import style
|
||||
from . import workbook
|
||||
from . import worksheet
|
||||
from . import reader
|
||||
from . import shared
|
||||
from . import writer
|
||||
|
||||
# constants
|
||||
|
||||
__major__ = 1 # for major interface/format changes
|
||||
__minor__ = 5 # for minor interface/format changes
|
||||
__release__ = 2 # for tweaks, bug-fixes, or development
|
||||
|
||||
__version__ = '%d.%d.%d' % (__major__, __minor__, __release__)
|
||||
|
||||
__author__ = 'Eric Gazoni'
|
||||
__license__ = 'MIT/Expat'
|
||||
__author_email__ = 'eric.gazoni@gmail.com'
|
||||
__maintainer_email__ = 'openpyxl-users@googlegroups.com'
|
||||
__url__ = 'http://bitbucket.org/ericgazoni/openpyxl/wiki/Home'
|
||||
__downloadUrl__ = "http://bitbucket.org/ericgazoni/openpyxl/downloads"
|
||||
|
||||
__all__ = ('reader', 'shared', 'writer',)
|
||||
@@ -1,384 +0,0 @@
|
||||
# file openpyxl/cell.py
|
||||
|
||||
# Copyright (c) 2010 openpyxl
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# @license: http://www.opensource.org/licenses/mit-license.php
|
||||
# @author: Eric Gazoni
|
||||
|
||||
"""Manage individual cells in a spreadsheet.
|
||||
|
||||
The Cell class is required to know its value and type, display options,
|
||||
and any other features of an Excel cell. Utilities for referencing
|
||||
cells using Excel's 'A1' column/row nomenclature are also provided.
|
||||
|
||||
"""
|
||||
|
||||
__docformat__ = "restructuredtext en"
|
||||
|
||||
# Python stdlib imports
|
||||
import datetime
|
||||
import re
|
||||
|
||||
# package imports
|
||||
from .shared.date_time import SharedDate
|
||||
from .shared.exc import CellCoordinatesException, \
|
||||
ColumnStringIndexException, DataTypeException
|
||||
from .style import NumberFormat
|
||||
|
||||
# constants
|
||||
COORD_RE = re.compile('^[$]?([A-Z]+)[$]?(\d+)$')
|
||||
|
||||
ABSOLUTE_RE = re.compile('^[$]?([A-Z]+)[$]?(\d+)(:[$]?([A-Z]+)[$]?(\d+))?$')
|
||||
|
||||
def coordinate_from_string(coord_string):
|
||||
"""Convert a coordinate string like 'B12' to a tuple ('B', 12)"""
|
||||
match = COORD_RE.match(coord_string.upper())
|
||||
if not match:
|
||||
msg = 'Invalid cell coordinates (%s)' % coord_string
|
||||
raise CellCoordinatesException(msg)
|
||||
column, row = match.groups()
|
||||
return (column, int(row))
|
||||
|
||||
|
||||
def absolute_coordinate(coord_string):
|
||||
"""Convert a coordinate to an absolute coordinate string (B12 -> $B$12)"""
|
||||
parts = ABSOLUTE_RE.match(coord_string).groups()
|
||||
|
||||
if all(parts[-2:]):
|
||||
return '$%s$%s:$%s$%s' % (parts[0], parts[1], parts[3], parts[4])
|
||||
else:
|
||||
return '$%s$%s' % (parts[0], parts[1])
|
||||
|
||||
|
||||
def column_index_from_string(column, fast = False):
|
||||
"""Convert a column letter into a column number (e.g. B -> 2)
|
||||
|
||||
Excel only supports 1-3 letter column names from A -> ZZZ, so we
|
||||
restrict our column names to 1-3 characters, each in the range A-Z.
|
||||
|
||||
.. note::
|
||||
|
||||
Fast mode is faster but does not check that all letters are capitals between A and Z
|
||||
|
||||
"""
|
||||
column = column.upper()
|
||||
|
||||
clen = len(column)
|
||||
|
||||
if not fast and not all('A' <= char <= 'Z' for char in column):
|
||||
msg = 'Column string must contain only characters A-Z: got %s' % column
|
||||
raise ColumnStringIndexException(msg)
|
||||
|
||||
if clen == 1:
|
||||
return ord(column[0]) - 64
|
||||
elif clen == 2:
|
||||
return ((1 + (ord(column[0]) - 65)) * 26) + (ord(column[1]) - 64)
|
||||
elif clen == 3:
|
||||
return ((1 + (ord(column[0]) - 65)) * 676) + ((1 + (ord(column[1]) - 65)) * 26) + (ord(column[2]) - 64)
|
||||
elif clen > 3:
|
||||
raise ColumnStringIndexException('Column string index can not be longer than 3 characters')
|
||||
else:
|
||||
raise ColumnStringIndexException('Column string index can not be empty')
|
||||
|
||||
|
||||
def get_column_letter(col_idx):
|
||||
"""Convert a column number into a column letter (3 -> 'C')
|
||||
|
||||
Right shift the column col_idx by 26 to find column letters in reverse
|
||||
order. These numbers are 1-based, and can be converted to ASCII
|
||||
ordinals by adding 64.
|
||||
|
||||
"""
|
||||
# these indicies corrospond to A -> ZZZ and include all allowed
|
||||
# columns
|
||||
if not 1 <= col_idx <= 18278:
|
||||
msg = 'Column index out of bounds: %s' % col_idx
|
||||
raise ColumnStringIndexException(msg)
|
||||
ordinals = []
|
||||
temp = col_idx
|
||||
while temp:
|
||||
quotient, remainder = divmod(temp, 26)
|
||||
# check for exact division and borrow if needed
|
||||
if remainder == 0:
|
||||
quotient -= 1
|
||||
remainder = 26
|
||||
ordinals.append(remainder + 64)
|
||||
temp = quotient
|
||||
ordinals.reverse()
|
||||
return ''.join([chr(ordinal) for ordinal in ordinals])
|
||||
|
||||
|
||||
class Cell(object):
|
||||
"""Describes cell associated properties.
|
||||
|
||||
Properties of interest include style, type, value, and address.
|
||||
|
||||
"""
|
||||
__slots__ = ('column',
|
||||
'row',
|
||||
'_value',
|
||||
'_data_type',
|
||||
'parent',
|
||||
'xf_index',
|
||||
'_hyperlink_rel')
|
||||
|
||||
ERROR_CODES = {'#NULL!': 0,
|
||||
'#DIV/0!': 1,
|
||||
'#VALUE!': 2,
|
||||
'#REF!': 3,
|
||||
'#NAME?': 4,
|
||||
'#NUM!': 5,
|
||||
'#N/A': 6}
|
||||
|
||||
TYPE_STRING = 's'
|
||||
TYPE_FORMULA = 'f'
|
||||
TYPE_NUMERIC = 'n'
|
||||
TYPE_BOOL = 'b'
|
||||
TYPE_NULL = 's'
|
||||
TYPE_INLINE = 'inlineStr'
|
||||
TYPE_ERROR = 'e'
|
||||
|
||||
VALID_TYPES = [TYPE_STRING, TYPE_FORMULA, TYPE_NUMERIC, TYPE_BOOL,
|
||||
TYPE_NULL, TYPE_INLINE, TYPE_ERROR]
|
||||
|
||||
RE_PATTERNS = {
|
||||
'percentage': re.compile('^\-?[0-9]*\.?[0-9]*\s?\%$'),
|
||||
'time': re.compile('^(\d|[0-1]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$'),
|
||||
'numeric': re.compile('^\-?([0-9]+\\.?[0-9]*|[0-9]*\\.?[0-9]+)((E|e)\-?[0-9]+)?$'), }
|
||||
|
||||
def __init__(self, worksheet, column, row, value = None):
|
||||
self.column = column.upper()
|
||||
self.row = row
|
||||
# _value is the stored value, while value is the displayed value
|
||||
self._value = None
|
||||
self._hyperlink_rel = None
|
||||
self._data_type = self.TYPE_NULL
|
||||
if value:
|
||||
self.value = value
|
||||
self.parent = worksheet
|
||||
self.xf_index = 0
|
||||
|
||||
def __repr__(self):
|
||||
return "<Cell %s.%s>" % (self.parent.title, self.get_coordinate())
|
||||
|
||||
def check_string(self, value):
|
||||
"""Check string coding, length, and line break character"""
|
||||
# convert to unicode string
|
||||
value = unicode(value)
|
||||
# string must never be longer than 32,767 characters
|
||||
# truncate if necessary
|
||||
value = value[:32767]
|
||||
# we require that newline is represented as "\n" in core,
|
||||
# not as "\r\n" or "\r"
|
||||
value = value.replace('\r\n', '\n')
|
||||
return value
|
||||
|
||||
def check_numeric(self, value):
|
||||
"""Cast value to int or float if necessary"""
|
||||
if not isinstance(value, (int, float)):
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
value = float(value)
|
||||
return value
|
||||
|
||||
def set_value_explicit(self, value = None, data_type = TYPE_STRING):
|
||||
"""Coerce values according to their explicit type"""
|
||||
type_coercion_map = {
|
||||
self.TYPE_INLINE: self.check_string,
|
||||
self.TYPE_STRING: self.check_string,
|
||||
self.TYPE_FORMULA: unicode,
|
||||
self.TYPE_NUMERIC: self.check_numeric,
|
||||
self.TYPE_BOOL: bool, }
|
||||
try:
|
||||
self._value = type_coercion_map[data_type](value)
|
||||
except KeyError:
|
||||
if data_type not in self.VALID_TYPES:
|
||||
msg = 'Invalid data type: %s' % data_type
|
||||
raise DataTypeException(msg)
|
||||
self._data_type = data_type
|
||||
|
||||
def data_type_for_value(self, value):
|
||||
"""Given a value, infer the correct data type"""
|
||||
if value is None:
|
||||
data_type = self.TYPE_NULL
|
||||
elif value is True or value is False:
|
||||
data_type = self.TYPE_BOOL
|
||||
elif isinstance(value, (int, float)):
|
||||
data_type = self.TYPE_NUMERIC
|
||||
elif not value:
|
||||
data_type = self.TYPE_STRING
|
||||
elif isinstance(value, (datetime.datetime, datetime.date)):
|
||||
data_type = self.TYPE_NUMERIC
|
||||
elif isinstance(value, basestring) and value[0] == '=':
|
||||
data_type = self.TYPE_FORMULA
|
||||
elif self.RE_PATTERNS['numeric'].match(value):
|
||||
data_type = self.TYPE_NUMERIC
|
||||
elif value.strip() in self.ERROR_CODES:
|
||||
data_type = self.TYPE_ERROR
|
||||
else:
|
||||
data_type = self.TYPE_STRING
|
||||
return data_type
|
||||
|
||||
def bind_value(self, value):
|
||||
"""Given a value, infer type and display options."""
|
||||
self._data_type = self.data_type_for_value(value)
|
||||
if value is None:
|
||||
self.set_value_explicit('', self.TYPE_NULL)
|
||||
return True
|
||||
elif self._data_type == self.TYPE_STRING:
|
||||
# percentage detection
|
||||
percentage_search = self.RE_PATTERNS['percentage'].match(value)
|
||||
if percentage_search and value.strip() != '%':
|
||||
value = float(value.replace('%', '')) / 100.0
|
||||
self.set_value_explicit(value, self.TYPE_NUMERIC)
|
||||
self._set_number_format(NumberFormat.FORMAT_PERCENTAGE)
|
||||
return True
|
||||
# time detection
|
||||
time_search = self.RE_PATTERNS['time'].match(value)
|
||||
if time_search:
|
||||
sep_count = value.count(':') #pylint: disable-msg=E1103
|
||||
if sep_count == 1:
|
||||
hours, minutes = [int(bit) for bit in value.split(':')] #pylint: disable-msg=E1103
|
||||
seconds = 0
|
||||
elif sep_count == 2:
|
||||
hours, minutes, seconds = \
|
||||
[int(bit) for bit in value.split(':')] #pylint: disable-msg=E1103
|
||||
days = (hours / 24.0) + (minutes / 1440.0) + \
|
||||
(seconds / 86400.0)
|
||||
self.set_value_explicit(days, self.TYPE_NUMERIC)
|
||||
self._set_number_format(NumberFormat.FORMAT_DATE_TIME3)
|
||||
return True
|
||||
if self._data_type == self.TYPE_NUMERIC:
|
||||
# date detection
|
||||
# if the value is a date, but not a date time, make it a
|
||||
# datetime, and set the time part to 0
|
||||
if isinstance(value, datetime.date) and not \
|
||||
isinstance(value, datetime.datetime):
|
||||
value = datetime.datetime.combine(value, datetime.time())
|
||||
if isinstance(value, datetime.datetime):
|
||||
value = SharedDate().datetime_to_julian(date = value)
|
||||
self.set_value_explicit(value, self.TYPE_NUMERIC)
|
||||
self._set_number_format(NumberFormat.FORMAT_DATE_YYYYMMDD2)
|
||||
return True
|
||||
self.set_value_explicit(value, self._data_type)
|
||||
|
||||
def _get_value(self):
|
||||
"""Return the value, formatted as a date if needed"""
|
||||
value = self._value
|
||||
if self.is_date():
|
||||
value = SharedDate().from_julian(value)
|
||||
return value
|
||||
|
||||
def _set_value(self, value):
|
||||
"""Set the value and infer type and display options."""
|
||||
self.bind_value(value)
|
||||
|
||||
value = property(_get_value, _set_value,
|
||||
doc = 'Get or set the value held in the cell.\n\n'
|
||||
':rtype: depends on the value (string, float, int or '
|
||||
':class:`datetime.datetime`)')
|
||||
|
||||
def _set_hyperlink(self, val):
|
||||
"""Set value and display for hyperlinks in a cell"""
|
||||
if self._hyperlink_rel is None:
|
||||
self._hyperlink_rel = self.parent.create_relationship("hyperlink")
|
||||
self._hyperlink_rel.target = val
|
||||
self._hyperlink_rel.target_mode = "External"
|
||||
if self._value is None:
|
||||
self.value = val
|
||||
|
||||
def _get_hyperlink(self):
|
||||
"""Return the hyperlink target or an empty string"""
|
||||
return self._hyperlink_rel is not None and \
|
||||
self._hyperlink_rel.target or ''
|
||||
|
||||
hyperlink = property(_get_hyperlink, _set_hyperlink,
|
||||
doc = 'Get or set the hyperlink held in the cell. '
|
||||
'Automatically sets the `value` of the cell with link text, '
|
||||
'but you can modify it afterwards by setting the '
|
||||
'`value` property, and the hyperlink will remain.\n\n'
|
||||
':rtype: string')
|
||||
|
||||
@property
|
||||
def hyperlink_rel_id(self):
|
||||
"""Return the id pointed to by the hyperlink, or None"""
|
||||
return self._hyperlink_rel is not None and \
|
||||
self._hyperlink_rel.id or None
|
||||
|
||||
def _set_number_format(self, format_code):
|
||||
"""Set a new formatting code for numeric values"""
|
||||
self.style.number_format.format_code = format_code
|
||||
|
||||
@property
|
||||
def has_style(self):
|
||||
"""Check if the parent worksheet has a style for this cell"""
|
||||
return self.get_coordinate() in self.parent._styles #pylint: disable-msg=W0212
|
||||
|
||||
@property
|
||||
def style(self):
|
||||
"""Returns the :class:`openpyxl.style.Style` object for this cell"""
|
||||
return self.parent.get_style(self.get_coordinate())
|
||||
|
||||
@property
|
||||
def data_type(self):
|
||||
"""Return the data type represented by this cell"""
|
||||
return self._data_type
|
||||
|
||||
def get_coordinate(self):
|
||||
"""Return the coordinate string for this cell (e.g. 'B12')
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
return '%s%s' % (self.column, self.row)
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
"""Return the coordinate string for this cell (e.g. 'B12')
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
return self.get_coordinate()
|
||||
|
||||
def offset(self, row = 0, column = 0):
|
||||
"""Returns a cell location relative to this cell.
|
||||
|
||||
:param row: number of rows to offset
|
||||
:type row: int
|
||||
|
||||
:param column: number of columns to offset
|
||||
:type column: int
|
||||
|
||||
:rtype: :class:`openpyxl.cell.Cell`
|
||||
"""
|
||||
offset_column = get_column_letter(column_index_from_string(
|
||||
column = self.column) + column)
|
||||
offset_row = self.row + row
|
||||
return self.parent.cell('%s%s' % (offset_column, offset_row))
|
||||
|
||||
def is_date(self):
|
||||
"""Returns whether the value is *probably* a date or not
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
return (self.has_style
|
||||
and self.style.number_format.is_date_format()
|
||||
and isinstance(self._value, (int, float)))
|
||||
@@ -1,340 +0,0 @@
|
||||
'''
|
||||
Copyright (c) 2010 openpyxl
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
@license: http://www.opensource.org/licenses/mit-license.php
|
||||
@author: Eric Gazoni
|
||||
'''
|
||||
|
||||
import math
|
||||
|
||||
from .style import NumberFormat
|
||||
from .drawing import Drawing, Shape
|
||||
from .shared.units import pixels_to_EMU, short_color
|
||||
from .cell import get_column_letter
|
||||
|
||||
class Axis(object):
|
||||
|
||||
POSITION_BOTTOM = 'b'
|
||||
POSITION_LEFT = 'l'
|
||||
|
||||
ORIENTATION_MIN_MAX = "minMax"
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.orientation = self.ORIENTATION_MIN_MAX
|
||||
self.number_format = NumberFormat()
|
||||
for attr in ('position','tick_label_position','crosses',
|
||||
'auto','label_align','label_offset','cross_between'):
|
||||
setattr(self, attr, None)
|
||||
self.min = 0
|
||||
self.max = None
|
||||
self.unit = None
|
||||
|
||||
@classmethod
|
||||
def default_category(cls):
|
||||
""" default values for category axes """
|
||||
|
||||
ax = Axis()
|
||||
ax.id = 60871424
|
||||
ax.cross = 60873344
|
||||
ax.position = Axis.POSITION_BOTTOM
|
||||
ax.tick_label_position = 'nextTo'
|
||||
ax.crosses = "autoZero"
|
||||
ax.auto = True
|
||||
ax.label_align = 'ctr'
|
||||
ax.label_offset = 100
|
||||
return ax
|
||||
|
||||
@classmethod
|
||||
def default_value(cls):
|
||||
""" default values for value axes """
|
||||
|
||||
ax = Axis()
|
||||
ax.id = 60873344
|
||||
ax.cross = 60871424
|
||||
ax.position = Axis.POSITION_LEFT
|
||||
ax.major_gridlines = None
|
||||
ax.tick_label_position = 'nextTo'
|
||||
ax.crosses = 'autoZero'
|
||||
ax.auto = False
|
||||
ax.cross_between = 'between'
|
||||
return ax
|
||||
|
||||
class Reference(object):
|
||||
""" a simple wrapper around a serie of reference data """
|
||||
|
||||
def __init__(self, sheet, pos1, pos2=None):
|
||||
|
||||
self.sheet = sheet
|
||||
self.pos1 = pos1
|
||||
self.pos2 = pos2
|
||||
|
||||
def get_type(self):
|
||||
|
||||
if isinstance(self.cache[0], basestring):
|
||||
return 'str'
|
||||
else:
|
||||
return 'num'
|
||||
|
||||
def _get_ref(self):
|
||||
""" format excel reference notation """
|
||||
|
||||
if self.pos2:
|
||||
return '%s!$%s$%s:$%s$%s' % (self.sheet.title,
|
||||
get_column_letter(self.pos1[1]+1), self.pos1[0]+1,
|
||||
get_column_letter(self.pos2[1]+1), self.pos2[0]+1)
|
||||
else:
|
||||
return '%s!$%s$%s' % (self.sheet.title,
|
||||
get_column_letter(self.pos1[1]+1), self.pos1[0]+1)
|
||||
|
||||
|
||||
def _get_cache(self):
|
||||
""" read data in sheet - to be used at writing time """
|
||||
|
||||
cache = []
|
||||
if self.pos2:
|
||||
for row in range(self.pos1[0], self.pos2[0]+1):
|
||||
for col in range(self.pos1[1], self.pos2[1]+1):
|
||||
cache.append(self.sheet.cell(row=row, column=col).value)
|
||||
else:
|
||||
cell = self.sheet.cell(row=self.pos1[0], column=self.pos1[1])
|
||||
cache.append(cell.value)
|
||||
return cache
|
||||
|
||||
|
||||
class Serie(object):
|
||||
""" a serie of data and possibly associated labels """
|
||||
|
||||
MARKER_NONE = 'none'
|
||||
|
||||
def __init__(self, values, labels=None, legend=None, color=None, xvalues=None):
|
||||
|
||||
self.marker = Serie.MARKER_NONE
|
||||
self.values = values
|
||||
self.xvalues = xvalues
|
||||
self.labels = labels
|
||||
self.legend = legend
|
||||
self.error_bar = None
|
||||
self._color = color
|
||||
|
||||
def _get_color(self):
|
||||
return self._color
|
||||
|
||||
def _set_color(self, color):
|
||||
self._color = short_color(color)
|
||||
|
||||
color = property(_get_color, _set_color)
|
||||
|
||||
def get_min_max(self):
|
||||
|
||||
if self.error_bar:
|
||||
err_cache = self.error_bar.values._get_cache()
|
||||
vals = [v + err_cache[i] \
|
||||
for i,v in enumerate(self.values._get_cache())]
|
||||
else:
|
||||
vals = self.values._get_cache()
|
||||
return min(vals), max(vals)
|
||||
|
||||
def __len__(self):
|
||||
|
||||
return len(self.values.cache)
|
||||
|
||||
class Legend(object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.position = 'r'
|
||||
self.layout = None
|
||||
|
||||
class ErrorBar(object):
|
||||
|
||||
PLUS = 1
|
||||
MINUS = 2
|
||||
PLUS_MINUS = 3
|
||||
|
||||
def __init__(self, _type, values):
|
||||
|
||||
self.type = _type
|
||||
self.values = values
|
||||
|
||||
class Chart(object):
|
||||
""" raw chart class """
|
||||
|
||||
GROUPING_CLUSTERED = 'clustered'
|
||||
GROUPING_STANDARD = 'standard'
|
||||
|
||||
BAR_CHART = 1
|
||||
LINE_CHART = 2
|
||||
SCATTER_CHART = 3
|
||||
|
||||
def __init__(self, _type, grouping):
|
||||
|
||||
self._series = []
|
||||
|
||||
# public api
|
||||
self.type = _type
|
||||
self.grouping = grouping
|
||||
self.x_axis = Axis.default_category()
|
||||
self.y_axis = Axis.default_value()
|
||||
self.legend = Legend()
|
||||
self.lang = 'fr-FR'
|
||||
self.title = ''
|
||||
self.print_margins = dict(b=.75, l=.7, r=.7, t=.75, header=0.3, footer=.3)
|
||||
|
||||
# the containing drawing
|
||||
self.drawing = Drawing()
|
||||
|
||||
# the offset for the plot part in percentage of the drawing size
|
||||
self.width = .6
|
||||
self.height = .6
|
||||
self.margin_top = self._get_max_margin_top()
|
||||
self.margin_left = 0
|
||||
|
||||
# the user defined shapes
|
||||
self._shapes = []
|
||||
|
||||
def add_serie(self, serie):
|
||||
|
||||
serie.id = len(self._series)
|
||||
self._series.append(serie)
|
||||
self._compute_min_max()
|
||||
if not None in [s.xvalues for s in self._series]:
|
||||
self._compute_xmin_xmax()
|
||||
|
||||
def add_shape(self, shape):
|
||||
|
||||
shape._chart = self
|
||||
self._shapes.append(shape)
|
||||
|
||||
def get_x_units(self):
|
||||
""" calculate one unit for x axis in EMU """
|
||||
|
||||
return max([len(s.values._get_cache()) for s in self._series])
|
||||
|
||||
def get_y_units(self):
|
||||
""" calculate one unit for y axis in EMU """
|
||||
|
||||
dh = pixels_to_EMU(self.drawing.height)
|
||||
return (dh * self.height) / self.y_axis.max
|
||||
|
||||
def get_y_chars(self):
|
||||
""" estimate nb of chars for y axis """
|
||||
|
||||
_max = max([max(s.values._get_cache()) for s in self._series])
|
||||
return len(str(int(_max)))
|
||||
|
||||
def _compute_min_max(self):
|
||||
""" compute y axis limits and units """
|
||||
|
||||
maxi = max([max(s.values._get_cache()) for s in self._series])
|
||||
|
||||
mul = None
|
||||
if maxi < 1:
|
||||
s = str(maxi).split('.')[1]
|
||||
mul = 10
|
||||
for x in s:
|
||||
if x == '0':
|
||||
mul *= 10
|
||||
else:
|
||||
break
|
||||
maxi = maxi * mul
|
||||
|
||||
maxi = math.ceil(maxi * 1.1)
|
||||
sz = len(str(int(maxi))) - 1
|
||||
unit = math.ceil(math.ceil(maxi / pow(10, sz)) * pow(10, sz-1))
|
||||
maxi = math.ceil(maxi/unit) * unit
|
||||
|
||||
if mul is not None:
|
||||
maxi = maxi/mul
|
||||
unit = unit/mul
|
||||
|
||||
if maxi / unit > 9:
|
||||
# no more that 10 ticks
|
||||
unit *= 2
|
||||
|
||||
self.y_axis.max = maxi
|
||||
self.y_axis.unit = unit
|
||||
|
||||
def _compute_xmin_xmax(self):
|
||||
""" compute x axis limits and units """
|
||||
|
||||
maxi = max([max(s.xvalues._get_cache()) for s in self._series])
|
||||
|
||||
mul = None
|
||||
if maxi < 1:
|
||||
s = str(maxi).split('.')[1]
|
||||
mul = 10
|
||||
for x in s:
|
||||
if x == '0':
|
||||
mul *= 10
|
||||
else:
|
||||
break
|
||||
maxi = maxi * mul
|
||||
|
||||
maxi = math.ceil(maxi * 1.1)
|
||||
sz = len(str(int(maxi))) - 1
|
||||
unit = math.ceil(math.ceil(maxi / pow(10, sz)) * pow(10, sz-1))
|
||||
maxi = math.ceil(maxi/unit) * unit
|
||||
|
||||
if mul is not None:
|
||||
maxi = maxi/mul
|
||||
unit = unit/mul
|
||||
|
||||
if maxi / unit > 9:
|
||||
# no more that 10 ticks
|
||||
unit *= 2
|
||||
|
||||
self.x_axis.max = maxi
|
||||
self.x_axis.unit = unit
|
||||
|
||||
def _get_max_margin_top(self):
|
||||
|
||||
mb = Shape.FONT_HEIGHT + Shape.MARGIN_BOTTOM
|
||||
plot_height = self.drawing.height * self.height
|
||||
return float(self.drawing.height - plot_height - mb)/self.drawing.height
|
||||
|
||||
def _get_min_margin_left(self):
|
||||
|
||||
ml = (self.get_y_chars() * Shape.FONT_WIDTH) + Shape.MARGIN_LEFT
|
||||
return float(ml)/self.drawing.width
|
||||
|
||||
def _get_margin_top(self):
|
||||
""" get margin in percent """
|
||||
|
||||
return min(self.margin_top, self._get_max_margin_top())
|
||||
|
||||
def _get_margin_left(self):
|
||||
|
||||
return max(self._get_min_margin_left(), self.margin_left)
|
||||
|
||||
class BarChart(Chart):
|
||||
def __init__(self):
|
||||
super(BarChart, self).__init__(Chart.BAR_CHART, Chart.GROUPING_CLUSTERED)
|
||||
|
||||
class LineChart(Chart):
|
||||
def __init__(self):
|
||||
super(LineChart, self).__init__(Chart.LINE_CHART, Chart.GROUPING_STANDARD)
|
||||
|
||||
class ScatterChart(Chart):
|
||||
def __init__(self):
|
||||
super(ScatterChart, self).__init__(Chart.SCATTER_CHART, Chart.GROUPING_STANDARD)
|
||||
|
||||
|
||||
@@ -1,401 +0,0 @@
|
||||
'''
|
||||
Copyright (c) 2010 openpyxl
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
@license: http://www.opensource.org/licenses/mit-license.php
|
||||
@author: Eric Gazoni
|
||||
'''
|
||||
|
||||
import math
|
||||
from .style import Color
|
||||
from .shared.units import pixels_to_EMU, EMU_to_pixels, short_color
|
||||
|
||||
class Shadow(object):
|
||||
|
||||
SHADOW_BOTTOM = 'b'
|
||||
SHADOW_BOTTOM_LEFT = 'bl'
|
||||
SHADOW_BOTTOM_RIGHT = 'br'
|
||||
SHADOW_CENTER = 'ctr'
|
||||
SHADOW_LEFT = 'l'
|
||||
SHADOW_TOP = 't'
|
||||
SHADOW_TOP_LEFT = 'tl'
|
||||
SHADOW_TOP_RIGHT = 'tr'
|
||||
|
||||
def __init__(self):
|
||||
self.visible = False
|
||||
self.blurRadius = 6
|
||||
self.distance = 2
|
||||
self.direction = 0
|
||||
self.alignment = self.SHADOW_BOTTOM_RIGHT
|
||||
self.color = Color(Color.BLACK)
|
||||
self.alpha = 50
|
||||
|
||||
class Drawing(object):
|
||||
""" a drawing object - eg container for shapes or charts
|
||||
we assume user specifies dimensions in pixels; units are
|
||||
converted to EMU in the drawing part
|
||||
"""
|
||||
|
||||
count = 0
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.name = ''
|
||||
self.description = ''
|
||||
self.coordinates = ((1,2), (16,8))
|
||||
self.left = 0
|
||||
self.top = 0
|
||||
self._width = EMU_to_pixels(200000)
|
||||
self._height = EMU_to_pixels(1828800)
|
||||
self.resize_proportional = False
|
||||
self.rotation = 0
|
||||
# self.shadow = Shadow()
|
||||
|
||||
def _set_width(self, w):
|
||||
|
||||
if self.resize_proportional and w:
|
||||
ratio = self._height / self._width
|
||||
self._height = round(ratio * w)
|
||||
self._width = w
|
||||
|
||||
def _get_width(self):
|
||||
|
||||
return self._width
|
||||
|
||||
width = property(_get_width, _set_width)
|
||||
|
||||
def _set_height(self, h):
|
||||
|
||||
if self.resize_proportional and h:
|
||||
ratio = self._width / self._height
|
||||
self._width = round(ratio * h)
|
||||
self._height = h
|
||||
|
||||
def _get_height(self):
|
||||
|
||||
return self._height
|
||||
|
||||
height = property(_get_height, _set_height)
|
||||
|
||||
def set_dimension(self, w=0, h=0):
|
||||
|
||||
xratio = w / self._width
|
||||
yratio = h / self._height
|
||||
|
||||
if self.resize_proportional and w and h:
|
||||
if (xratio * self._height) < h:
|
||||
self._height = math.ceil(xratio * self._height)
|
||||
self._width = width
|
||||
else:
|
||||
self._width = math.ceil(yratio * self._width)
|
||||
self._height = height
|
||||
|
||||
def get_emu_dimensions(self):
|
||||
""" return (x, y, w, h) in EMU """
|
||||
|
||||
return (pixels_to_EMU(self.left), pixels_to_EMU(self.top),
|
||||
pixels_to_EMU(self._width), pixels_to_EMU(self._height))
|
||||
|
||||
|
||||
class Shape(object):
|
||||
""" a drawing inside a chart
|
||||
coordiantes are specified by the user in the axis units
|
||||
"""
|
||||
|
||||
MARGIN_LEFT = 6 + 13 + 1
|
||||
MARGIN_BOTTOM = 17 + 11
|
||||
|
||||
FONT_WIDTH = 7
|
||||
FONT_HEIGHT = 8
|
||||
|
||||
ROUND_RECT = 'roundRect'
|
||||
RECT = 'rect'
|
||||
|
||||
# other shapes to define :
|
||||
'''
|
||||
"line"
|
||||
"lineInv"
|
||||
"triangle"
|
||||
"rtTriangle"
|
||||
"diamond"
|
||||
"parallelogram"
|
||||
"trapezoid"
|
||||
"nonIsoscelesTrapezoid"
|
||||
"pentagon"
|
||||
"hexagon"
|
||||
"heptagon"
|
||||
"octagon"
|
||||
"decagon"
|
||||
"dodecagon"
|
||||
"star4"
|
||||
"star5"
|
||||
"star6"
|
||||
"star7"
|
||||
"star8"
|
||||
"star10"
|
||||
"star12"
|
||||
"star16"
|
||||
"star24"
|
||||
"star32"
|
||||
"roundRect"
|
||||
"round1Rect"
|
||||
"round2SameRect"
|
||||
"round2DiagRect"
|
||||
"snipRoundRect"
|
||||
"snip1Rect"
|
||||
"snip2SameRect"
|
||||
"snip2DiagRect"
|
||||
"plaque"
|
||||
"ellipse"
|
||||
"teardrop"
|
||||
"homePlate"
|
||||
"chevron"
|
||||
"pieWedge"
|
||||
"pie"
|
||||
"blockArc"
|
||||
"donut"
|
||||
"noSmoking"
|
||||
"rightArrow"
|
||||
"leftArrow"
|
||||
"upArrow"
|
||||
"downArrow"
|
||||
"stripedRightArrow"
|
||||
"notchedRightArrow"
|
||||
"bentUpArrow"
|
||||
"leftRightArrow"
|
||||
"upDownArrow"
|
||||
"leftUpArrow"
|
||||
"leftRightUpArrow"
|
||||
"quadArrow"
|
||||
"leftArrowCallout"
|
||||
"rightArrowCallout"
|
||||
"upArrowCallout"
|
||||
"downArrowCallout"
|
||||
"leftRightArrowCallout"
|
||||
"upDownArrowCallout"
|
||||
"quadArrowCallout"
|
||||
"bentArrow"
|
||||
"uturnArrow"
|
||||
"circularArrow"
|
||||
"leftCircularArrow"
|
||||
"leftRightCircularArrow"
|
||||
"curvedRightArrow"
|
||||
"curvedLeftArrow"
|
||||
"curvedUpArrow"
|
||||
"curvedDownArrow"
|
||||
"swooshArrow"
|
||||
"cube"
|
||||
"can"
|
||||
"lightningBolt"
|
||||
"heart"
|
||||
"sun"
|
||||
"moon"
|
||||
"smileyFace"
|
||||
"irregularSeal1"
|
||||
"irregularSeal2"
|
||||
"foldedCorner"
|
||||
"bevel"
|
||||
"frame"
|
||||
"halfFrame"
|
||||
"corner"
|
||||
"diagStripe"
|
||||
"chord"
|
||||
"arc"
|
||||
"leftBracket"
|
||||
"rightBracket"
|
||||
"leftBrace"
|
||||
"rightBrace"
|
||||
"bracketPair"
|
||||
"bracePair"
|
||||
"straightConnector1"
|
||||
"bentConnector2"
|
||||
"bentConnector3"
|
||||
"bentConnector4"
|
||||
"bentConnector5"
|
||||
"curvedConnector2"
|
||||
"curvedConnector3"
|
||||
"curvedConnector4"
|
||||
"curvedConnector5"
|
||||
"callout1"
|
||||
"callout2"
|
||||
"callout3"
|
||||
"accentCallout1"
|
||||
"accentCallout2"
|
||||
"accentCallout3"
|
||||
"borderCallout1"
|
||||
"borderCallout2"
|
||||
"borderCallout3"
|
||||
"accentBorderCallout1"
|
||||
"accentBorderCallout2"
|
||||
"accentBorderCallout3"
|
||||
"wedgeRectCallout"
|
||||
"wedgeRoundRectCallout"
|
||||
"wedgeEllipseCallout"
|
||||
"cloudCallout"
|
||||
"cloud"
|
||||
"ribbon"
|
||||
"ribbon2"
|
||||
"ellipseRibbon"
|
||||
"ellipseRibbon2"
|
||||
"leftRightRibbon"
|
||||
"verticalScroll"
|
||||
"horizontalScroll"
|
||||
"wave"
|
||||
"doubleWave"
|
||||
"plus"
|
||||
"flowChartProcess"
|
||||
"flowChartDecision"
|
||||
"flowChartInputOutput"
|
||||
"flowChartPredefinedProcess"
|
||||
"flowChartInternalStorage"
|
||||
"flowChartDocument"
|
||||
"flowChartMultidocument"
|
||||
"flowChartTerminator"
|
||||
"flowChartPreparation"
|
||||
"flowChartManualInput"
|
||||
"flowChartManualOperation"
|
||||
"flowChartConnector"
|
||||
"flowChartPunchedCard"
|
||||
"flowChartPunchedTape"
|
||||
"flowChartSummingJunction"
|
||||
"flowChartOr"
|
||||
"flowChartCollate"
|
||||
"flowChartSort"
|
||||
"flowChartExtract"
|
||||
"flowChartMerge"
|
||||
"flowChartOfflineStorage"
|
||||
"flowChartOnlineStorage"
|
||||
"flowChartMagneticTape"
|
||||
"flowChartMagneticDisk"
|
||||
"flowChartMagneticDrum"
|
||||
"flowChartDisplay"
|
||||
"flowChartDelay"
|
||||
"flowChartAlternateProcess"
|
||||
"flowChartOffpageConnector"
|
||||
"actionButtonBlank"
|
||||
"actionButtonHome"
|
||||
"actionButtonHelp"
|
||||
"actionButtonInformation"
|
||||
"actionButtonForwardNext"
|
||||
"actionButtonBackPrevious"
|
||||
"actionButtonEnd"
|
||||
"actionButtonBeginning"
|
||||
"actionButtonReturn"
|
||||
"actionButtonDocument"
|
||||
"actionButtonSound"
|
||||
"actionButtonMovie"
|
||||
"gear6"
|
||||
"gear9"
|
||||
"funnel"
|
||||
"mathPlus"
|
||||
"mathMinus"
|
||||
"mathMultiply"
|
||||
"mathDivide"
|
||||
"mathEqual"
|
||||
"mathNotEqual"
|
||||
"cornerTabs"
|
||||
"squareTabs"
|
||||
"plaqueTabs"
|
||||
"chartX"
|
||||
"chartStar"
|
||||
"chartPlus"
|
||||
'''
|
||||
|
||||
def __init__(self, coordinates=((0,0), (1,1)), text=None, scheme="accent1"):
|
||||
|
||||
self.coordinates = coordinates # in axis unit
|
||||
self.text = text
|
||||
self.scheme = scheme
|
||||
self.style = Shape.RECT
|
||||
self._border_width = 3175 # in EMU
|
||||
self._border_color = Color.BLACK[2:] #"F3B3C5"
|
||||
self._color = Color.WHITE[2:]
|
||||
self._text_color = Color.BLACK[2:]
|
||||
|
||||
def _get_border_color(self):
|
||||
return self._border_color
|
||||
|
||||
def _set_border_color(self, color):
|
||||
self._border_color = short_color(color)
|
||||
|
||||
border_color = property(_get_border_color, _set_border_color)
|
||||
|
||||
def _get_color(self):
|
||||
return self._color
|
||||
|
||||
def _set_color(self, color):
|
||||
self._color = short_color(color)
|
||||
|
||||
color = property(_get_color, _set_color)
|
||||
|
||||
def _get_text_color(self):
|
||||
return self._text_color
|
||||
|
||||
def _set_text_color(self, color):
|
||||
self._text_color = short_color(color)
|
||||
|
||||
text_color = property(_get_text_color, _set_text_color)
|
||||
|
||||
def _get_border_width(self):
|
||||
|
||||
return EMU_to_pixels(self._border_width)
|
||||
|
||||
def _set_border_width(self, w):
|
||||
|
||||
self._border_width = pixels_to_EMU(w)
|
||||
# print self._border_width
|
||||
|
||||
border_width = property(_get_border_width, _set_border_width)
|
||||
|
||||
def get_coordinates(self):
|
||||
""" return shape coordinates in percentages (left, top, right, bottom) """
|
||||
|
||||
(x1, y1), (x2, y2) = self.coordinates
|
||||
|
||||
drawing_width = pixels_to_EMU(self._chart.drawing.width)
|
||||
drawing_height = pixels_to_EMU(self._chart.drawing.height)
|
||||
plot_width = drawing_width * self._chart.width
|
||||
plot_height = drawing_height * self._chart.height
|
||||
|
||||
margin_left = self._chart._get_margin_left() * drawing_width
|
||||
xunit = plot_width / self._chart.get_x_units()
|
||||
|
||||
margin_top = self._chart._get_margin_top() * drawing_height
|
||||
yunit = self._chart.get_y_units()
|
||||
|
||||
x_start = (margin_left + (float(x1) * xunit)) / drawing_width
|
||||
y_start = (margin_top + plot_height - (float(y1) * yunit)) / drawing_height
|
||||
|
||||
x_end = (margin_left + (float(x2) * xunit)) / drawing_width
|
||||
y_end = (margin_top + plot_height - (float(y2) * yunit)) / drawing_height
|
||||
|
||||
def _norm_pct(pct):
|
||||
""" force shapes to appear by truncating too large sizes """
|
||||
if pct>1: pct = 1
|
||||
elif pct<0: pct = 0
|
||||
return pct
|
||||
|
||||
# allow user to specify y's in whatever order
|
||||
# excel expect y_end to be lower
|
||||
if y_end < y_start:
|
||||
y_end, y_start = y_start, y_end
|
||||
|
||||
return (_norm_pct(x_start), _norm_pct(y_start),
|
||||
_norm_pct(x_end), _norm_pct(y_end))
|
||||
@@ -1,68 +0,0 @@
|
||||
# file openpyxl/namedrange.py
|
||||
|
||||
# Copyright (c) 2010 openpyxl
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# @license: http://www.opensource.org/licenses/mit-license.php
|
||||
# @author: Eric Gazoni
|
||||
|
||||
"""Track named groups of cells in a worksheet"""
|
||||
|
||||
# Python stdlib imports
|
||||
import re
|
||||
|
||||
# package imports
|
||||
from .shared.exc import NamedRangeException
|
||||
|
||||
# constants
|
||||
NAMED_RANGE_RE = re.compile("'?([^']*)'?!((\$([A-Za-z]+))?\$([0-9]+)(:(\$([A-Za-z]+))?(\$([0-9]+)))?)$")
|
||||
|
||||
class NamedRange(object):
|
||||
"""A named group of cells"""
|
||||
__slots__ = ('name', 'destinations', 'local_only')
|
||||
|
||||
def __init__(self, name, destinations):
|
||||
self.name = name
|
||||
self.destinations = destinations
|
||||
self.local_only = False
|
||||
|
||||
def __str__(self):
|
||||
return ','.join(['%s!%s' % (sheet, name) for sheet, name in self.destinations])
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return '<%s "%s">' % (self.__class__.__name__, str(self))
|
||||
|
||||
|
||||
def split_named_range(range_string):
|
||||
"""Separate a named range into its component parts"""
|
||||
|
||||
destinations = []
|
||||
|
||||
for range_string in range_string.split(','):
|
||||
|
||||
match = NAMED_RANGE_RE.match(range_string)
|
||||
if not match:
|
||||
raise NamedRangeException('Invalid named range string: "%s"' % range_string)
|
||||
else:
|
||||
sheet_name, xlrange = match.groups()[:2]
|
||||
destinations.append((sheet_name, xlrange))
|
||||
|
||||
return destinations
|
||||
@@ -1,33 +0,0 @@
|
||||
# file openpyxl/reader/__init__.py
|
||||
|
||||
# Copyright (c) 2010 openpyxl
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# @license: http://www.opensource.org/licenses/mit-license.php
|
||||
# @author: Eric Gazoni
|
||||
|
||||
"""Imports for the openpyxl.reader namespace."""
|
||||
|
||||
# package imports
|
||||
from ..reader import excel
|
||||
from ..reader import strings
|
||||
from ..reader import style
|
||||
from ..reader import workbook
|
||||
from ..reader import worksheet
|
||||
@@ -1,109 +0,0 @@
|
||||
# file openpyxl/reader/excel.py
|
||||
|
||||
# Copyright (c) 2010 openpyxl
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# @license: http://www.opensource.org/licenses/mit-license.php
|
||||
# @author: Eric Gazoni
|
||||
|
||||
"""Read an xlsx file into Python"""
|
||||
|
||||
# Python stdlib imports
|
||||
from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile
|
||||
|
||||
# package imports
|
||||
from ..shared.exc import OpenModeError, InvalidFileException
|
||||
from ..shared.ooxml import ARC_SHARED_STRINGS, ARC_CORE, ARC_APP, \
|
||||
ARC_WORKBOOK, PACKAGE_WORKSHEETS, ARC_STYLE
|
||||
from ..workbook import Workbook
|
||||
from ..reader.strings import read_string_table
|
||||
from ..reader.style import read_style_table
|
||||
from ..reader.workbook import read_sheets_titles, read_named_ranges, \
|
||||
read_properties_core, get_sheet_ids
|
||||
from ..reader.worksheet import read_worksheet
|
||||
from ..reader.iter_worksheet import unpack_worksheet
|
||||
|
||||
def load_workbook(filename, use_iterators = False):
|
||||
"""Open the given filename and return the workbook
|
||||
|
||||
:param filename: the path to open
|
||||
:type filename: string
|
||||
|
||||
:param use_iterators: use lazy load for cells
|
||||
:type use_iterators: bool
|
||||
|
||||
:rtype: :class:`openpyxl.workbook.Workbook`
|
||||
|
||||
.. note::
|
||||
|
||||
When using lazy load, all worksheets will be :class:`openpyxl.reader.iter_worksheet.IterableWorksheet`
|
||||
and the returned workbook will be read-only.
|
||||
|
||||
"""
|
||||
|
||||
if isinstance(filename, file):
|
||||
# fileobject must have been opened with 'rb' flag
|
||||
# it is required by zipfile
|
||||
if 'b' not in filename.mode:
|
||||
raise OpenModeError("File-object must be opened in binary mode")
|
||||
|
||||
try:
|
||||
archive = ZipFile(filename, 'r', ZIP_DEFLATED)
|
||||
except (BadZipfile, RuntimeError, IOError, ValueError):
|
||||
raise InvalidFileException()
|
||||
wb = Workbook()
|
||||
|
||||
if use_iterators:
|
||||
wb._set_optimized_read()
|
||||
|
||||
try:
|
||||
_load_workbook(wb, archive, filename, use_iterators)
|
||||
except KeyError:
|
||||
raise InvalidFileException()
|
||||
finally:
|
||||
archive.close()
|
||||
return wb
|
||||
|
||||
def _load_workbook(wb, archive, filename, use_iterators):
|
||||
|
||||
# get workbook-level information
|
||||
wb.properties = read_properties_core(archive.read(ARC_CORE))
|
||||
try:
|
||||
string_table = read_string_table(archive.read(ARC_SHARED_STRINGS))
|
||||
except KeyError:
|
||||
string_table = {}
|
||||
style_table = read_style_table(archive.read(ARC_STYLE))
|
||||
|
||||
# get worksheets
|
||||
wb.worksheets = [] # remove preset worksheet
|
||||
sheet_names = read_sheets_titles(archive.read(ARC_APP))
|
||||
for i, sheet_name in enumerate(sheet_names):
|
||||
sheet_codename = 'sheet%d.xml' % (i + 1)
|
||||
worksheet_path = '%s/%s' % (PACKAGE_WORKSHEETS, sheet_codename)
|
||||
|
||||
if not use_iterators:
|
||||
new_ws = read_worksheet(archive.read(worksheet_path), wb, sheet_name, string_table, style_table)
|
||||
else:
|
||||
xml_source = unpack_worksheet(archive, worksheet_path)
|
||||
new_ws = read_worksheet(xml_source, wb, sheet_name, string_table, style_table, filename, sheet_codename)
|
||||
#new_ws = read_worksheet(archive.read(worksheet_path), wb, sheet_name, string_table, style_table, filename, sheet_codename)
|
||||
wb.add_sheet(new_ws, index = i)
|
||||
|
||||
wb._named_ranges = read_named_ranges(archive.read(ARC_WORKBOOK), wb)
|
||||
@@ -1,348 +0,0 @@
|
||||
# file openpyxl/reader/iter_worksheet.py
|
||||
|
||||
# Copyright (c) 2010 openpyxl
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# @license: http://www.opensource.org/licenses/mit-license.php
|
||||
# @author: Eric Gazoni
|
||||
|
||||
""" Iterators-based worksheet reader
|
||||
*Still very raw*
|
||||
"""
|
||||
|
||||
from ....compat import BytesIO as StringIO
|
||||
import warnings
|
||||
import operator
|
||||
from functools import partial
|
||||
from itertools import groupby, ifilter
|
||||
from ..worksheet import Worksheet
|
||||
from ..cell import coordinate_from_string, get_column_letter, Cell
|
||||
from ..reader.excel import get_sheet_ids
|
||||
from ..reader.strings import read_string_table
|
||||
from ..reader.style import read_style_table, NumberFormat
|
||||
from ..shared.date_time import SharedDate
|
||||
from ..reader.worksheet import read_dimension
|
||||
from ..shared.ooxml import (MIN_COLUMN, MAX_COLUMN, PACKAGE_WORKSHEETS,
|
||||
MAX_ROW, MIN_ROW, ARC_SHARED_STRINGS, ARC_APP, ARC_STYLE)
|
||||
try:
|
||||
from xml.etree.cElementTree import iterparse
|
||||
except ImportError:
|
||||
from xml.etree.ElementTree import iterparse
|
||||
|
||||
|
||||
from zipfile import ZipFile
|
||||
from .. import cell
|
||||
import re
|
||||
import tempfile
|
||||
import zlib
|
||||
import zipfile
|
||||
import struct
|
||||
|
||||
TYPE_NULL = Cell.TYPE_NULL
|
||||
MISSING_VALUE = None
|
||||
|
||||
RE_COORDINATE = re.compile('^([A-Z]+)([0-9]+)$')
|
||||
|
||||
SHARED_DATE = SharedDate()
|
||||
|
||||
_COL_CONVERSION_CACHE = dict((get_column_letter(i), i) for i in xrange(1, 18279))
|
||||
def column_index_from_string(str_col, _col_conversion_cache=_COL_CONVERSION_CACHE):
|
||||
# we use a function argument to get indexed name lookup
|
||||
return _col_conversion_cache[str_col]
|
||||
del _COL_CONVERSION_CACHE
|
||||
|
||||
RAW_ATTRIBUTES = ['row', 'column', 'coordinate', 'internal_value', 'data_type', 'style_id', 'number_format']
|
||||
|
||||
try:
|
||||
from collections import namedtuple
|
||||
BaseRawCell = namedtuple('RawCell', RAW_ATTRIBUTES)
|
||||
except ImportError:
|
||||
|
||||
# warnings.warn("""Unable to import 'namedtuple' module, this may cause memory issues when using optimized reader. Please upgrade your Python installation to 2.6+""")
|
||||
|
||||
class BaseRawCell(object):
|
||||
|
||||
def __init__(self, *args):
|
||||
assert len(args)==len(RAW_ATTRIBUTES)
|
||||
|
||||
for attr, val in zip(RAW_ATTRIBUTES, args):
|
||||
setattr(self, attr, val)
|
||||
|
||||
def _replace(self, **kwargs):
|
||||
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
class RawCell(BaseRawCell):
|
||||
"""Optimized version of the :class:`openpyxl.cell.Cell`, using named tuples.
|
||||
|
||||
Useful attributes are:
|
||||
|
||||
* row
|
||||
* column
|
||||
* coordinate
|
||||
* internal_value
|
||||
|
||||
You can also access if needed:
|
||||
|
||||
* data_type
|
||||
* number_format
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def is_date(self):
|
||||
res = (self.data_type == Cell.TYPE_NUMERIC
|
||||
and self.number_format is not None
|
||||
and ('d' in self.number_format
|
||||
or 'm' in self.number_format
|
||||
or 'y' in self.number_format
|
||||
or 'h' in self.number_format
|
||||
or 's' in self.number_format
|
||||
))
|
||||
|
||||
return res
|
||||
|
||||
def iter_rows(workbook_name, sheet_name, xml_source, range_string = '', row_offset = 0, column_offset = 0):
|
||||
|
||||
archive = get_archive_file(workbook_name)
|
||||
|
||||
source = xml_source
|
||||
|
||||
if range_string:
|
||||
min_col, min_row, max_col, max_row = get_range_boundaries(range_string, row_offset, column_offset)
|
||||
else:
|
||||
min_col, min_row, max_col, max_row = read_dimension(xml_source = source)
|
||||
min_col = column_index_from_string(min_col)
|
||||
max_col = column_index_from_string(max_col) + 1
|
||||
max_row += 6
|
||||
|
||||
try:
|
||||
string_table = read_string_table(archive.read(ARC_SHARED_STRINGS))
|
||||
except KeyError:
|
||||
string_table = {}
|
||||
|
||||
style_table = read_style_table(archive.read(ARC_STYLE))
|
||||
|
||||
source.seek(0)
|
||||
p = iterparse(source)
|
||||
|
||||
return get_squared_range(p, min_col, min_row, max_col, max_row, string_table, style_table)
|
||||
|
||||
|
||||
def get_rows(p, min_column = MIN_COLUMN, min_row = MIN_ROW, max_column = MAX_COLUMN, max_row = MAX_ROW):
|
||||
|
||||
return groupby(get_cells(p, min_row, min_column, max_row, max_column), operator.attrgetter('row'))
|
||||
|
||||
def get_cells(p, min_row, min_col, max_row, max_col, _re_coordinate=RE_COORDINATE):
|
||||
|
||||
for _event, element in p:
|
||||
|
||||
if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c':
|
||||
coord = element.get('r')
|
||||
column_str, row = _re_coordinate.match(coord).groups()
|
||||
|
||||
row = int(row)
|
||||
column = column_index_from_string(column_str)
|
||||
|
||||
if min_col <= column <= max_col and min_row <= row <= max_row:
|
||||
data_type = element.get('t', 'n')
|
||||
style_id = element.get('s')
|
||||
value = element.findtext('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v')
|
||||
yield RawCell(row, column_str, coord, value, data_type, style_id, None)
|
||||
|
||||
if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v':
|
||||
continue
|
||||
element.clear()
|
||||
|
||||
|
||||
|
||||
def get_range_boundaries(range_string, row = 0, column = 0):
|
||||
|
||||
if ':' in range_string:
|
||||
min_range, max_range = range_string.split(':')
|
||||
min_col, min_row = coordinate_from_string(min_range)
|
||||
max_col, max_row = coordinate_from_string(max_range)
|
||||
|
||||
min_col = column_index_from_string(min_col) + column
|
||||
max_col = column_index_from_string(max_col) + column
|
||||
min_row += row
|
||||
max_row += row
|
||||
|
||||
else:
|
||||
min_col, min_row = coordinate_from_string(range_string)
|
||||
min_col = column_index_from_string(min_col)
|
||||
max_col = min_col + 1
|
||||
max_row = min_row
|
||||
|
||||
return (min_col, min_row, max_col, max_row)
|
||||
|
||||
def get_archive_file(archive_name):
|
||||
|
||||
return ZipFile(archive_name, 'r')
|
||||
|
||||
def get_xml_source(archive_file, sheet_name):
|
||||
|
||||
return archive_file.read('%s/%s' % (PACKAGE_WORKSHEETS, sheet_name))
|
||||
|
||||
def get_missing_cells(row, columns):
|
||||
|
||||
return dict([(column, RawCell(row, column, '%s%s' % (column, row), MISSING_VALUE, TYPE_NULL, None, None)) for column in columns])
|
||||
|
||||
def get_squared_range(p, min_col, min_row, max_col, max_row, string_table, style_table):
|
||||
|
||||
expected_columns = [get_column_letter(ci) for ci in xrange(min_col, max_col)]
|
||||
|
||||
current_row = min_row
|
||||
for row, cells in get_rows(p, min_row = min_row, max_row = max_row, min_column = min_col, max_column = max_col):
|
||||
full_row = []
|
||||
if current_row < row:
|
||||
|
||||
for gap_row in xrange(current_row, row):
|
||||
|
||||
dummy_cells = get_missing_cells(gap_row, expected_columns)
|
||||
|
||||
yield tuple([dummy_cells[column] for column in expected_columns])
|
||||
|
||||
current_row = row
|
||||
|
||||
temp_cells = list(cells)
|
||||
|
||||
retrieved_columns = dict([(c.column, c) for c in temp_cells])
|
||||
|
||||
missing_columns = list(set(expected_columns) - set(retrieved_columns.keys()))
|
||||
|
||||
replacement_columns = get_missing_cells(row, missing_columns)
|
||||
|
||||
for column in expected_columns:
|
||||
|
||||
if column in retrieved_columns:
|
||||
cell = retrieved_columns[column]
|
||||
|
||||
if cell.style_id is not None:
|
||||
style = style_table[int(cell.style_id)]
|
||||
cell = cell._replace(number_format = style.number_format.format_code) #pylint: disable-msg=W0212
|
||||
if cell.internal_value is not None:
|
||||
if cell.data_type == Cell.TYPE_STRING:
|
||||
cell = cell._replace(internal_value = string_table[int(cell.internal_value)]) #pylint: disable-msg=W0212
|
||||
elif cell.data_type == Cell.TYPE_BOOL:
|
||||
cell = cell._replace(internal_value = cell.internal_value == 'True')
|
||||
elif cell.is_date:
|
||||
cell = cell._replace(internal_value = SHARED_DATE.from_julian(float(cell.internal_value)))
|
||||
elif cell.data_type == Cell.TYPE_NUMERIC:
|
||||
cell = cell._replace(internal_value = float(cell.internal_value))
|
||||
full_row.append(cell)
|
||||
|
||||
else:
|
||||
full_row.append(replacement_columns[column])
|
||||
|
||||
current_row = row + 1
|
||||
|
||||
yield tuple(full_row)
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
class IterableWorksheet(Worksheet):
|
||||
|
||||
def __init__(self, parent_workbook, title, workbook_name,
|
||||
sheet_codename, xml_source):
|
||||
|
||||
Worksheet.__init__(self, parent_workbook, title)
|
||||
self._workbook_name = workbook_name
|
||||
self._sheet_codename = sheet_codename
|
||||
self._xml_source = xml_source
|
||||
|
||||
def iter_rows(self, range_string = '', row_offset = 0, column_offset = 0):
|
||||
""" Returns a squared range based on the `range_string` parameter,
|
||||
using generators.
|
||||
|
||||
:param range_string: range of cells (e.g. 'A1:C4')
|
||||
:type range_string: string
|
||||
|
||||
:param row: row index of the cell (e.g. 4)
|
||||
:type row: int
|
||||
|
||||
:param column: column index of the cell (e.g. 3)
|
||||
:type column: int
|
||||
|
||||
:rtype: generator
|
||||
|
||||
"""
|
||||
|
||||
return iter_rows(workbook_name = self._workbook_name,
|
||||
sheet_name = self._sheet_codename,
|
||||
xml_source = self._xml_source,
|
||||
range_string = range_string,
|
||||
row_offset = row_offset,
|
||||
column_offset = column_offset)
|
||||
|
||||
def cell(self, *args, **kwargs):
|
||||
|
||||
raise NotImplementedError("use 'iter_rows()' instead")
|
||||
|
||||
def range(self, *args, **kwargs):
|
||||
|
||||
raise NotImplementedError("use 'iter_rows()' instead")
|
||||
|
||||
def unpack_worksheet(archive, filename):
|
||||
|
||||
temp_file = tempfile.TemporaryFile(mode='r+', prefix='openpyxl.', suffix='.unpack.temp')
|
||||
|
||||
zinfo = archive.getinfo(filename)
|
||||
|
||||
if zinfo.compress_type == zipfile.ZIP_STORED:
|
||||
decoder = None
|
||||
elif zinfo.compress_type == zipfile.ZIP_DEFLATED:
|
||||
decoder = zlib.decompressobj(-zlib.MAX_WBITS)
|
||||
else:
|
||||
raise zipfile.BadZipFile("Unrecognized compression method")
|
||||
|
||||
archive.fp.seek(_get_file_offset(archive, zinfo))
|
||||
bytes_to_read = zinfo.compress_size
|
||||
|
||||
while True:
|
||||
buff = archive.fp.read(min(bytes_to_read, 102400))
|
||||
if not buff:
|
||||
break
|
||||
bytes_to_read -= len(buff)
|
||||
if decoder:
|
||||
buff = decoder.decompress(buff)
|
||||
temp_file.write(buff)
|
||||
|
||||
if decoder:
|
||||
temp_file.write(decoder.decompress('Z'))
|
||||
|
||||
return temp_file
|
||||
|
||||
def _get_file_offset(archive, zinfo):
|
||||
|
||||
try:
|
||||
return zinfo.file_offset
|
||||
except AttributeError:
|
||||
# From http://stackoverflow.com/questions/3781261/how-to-simulate-zipfile-open-in-python-2-5
|
||||
|
||||
# Seek over the fixed size fields to the "file name length" field in
|
||||
# the file header (26 bytes). Unpack this and the "extra field length"
|
||||
# field ourselves as info.extra doesn't seem to be the correct length.
|
||||
archive.fp.seek(zinfo.header_offset + 26)
|
||||
file_name_len, extra_len = struct.unpack("<HH", archive.fp.read(4))
|
||||
return zinfo.header_offset + 30 + file_name_len + extra_len
|
||||
@@ -1,64 +0,0 @@
|
||||
# file openpyxl/reader/strings.py
|
||||
|
||||
# Copyright (c) 2010 openpyxl
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# @license: http://www.opensource.org/licenses/mit-license.php
|
||||
# @author: Eric Gazoni
|
||||
|
||||
"""Read the shared strings table."""
|
||||
|
||||
# package imports
|
||||
from ..shared.xmltools import fromstring, QName
|
||||
from ..shared.ooxml import NAMESPACES
|
||||
|
||||
|
||||
def read_string_table(xml_source):
|
||||
"""Read in all shared strings in the table"""
|
||||
table = {}
|
||||
xmlns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
|
||||
root = fromstring(text=xml_source)
|
||||
string_index_nodes = root.findall(QName(xmlns, 'si').text)
|
||||
for index, string_index_node in enumerate(string_index_nodes):
|
||||
table[index] = get_string(xmlns, string_index_node)
|
||||
return table
|
||||
|
||||
|
||||
def get_string(xmlns, string_index_node):
|
||||
"""Read the contents of a specific string index"""
|
||||
rich_nodes = string_index_node.findall(QName(xmlns, 'r').text)
|
||||
if rich_nodes:
|
||||
reconstructed_text = []
|
||||
for rich_node in rich_nodes:
|
||||
partial_text = get_text(xmlns, rich_node)
|
||||
reconstructed_text.append(partial_text)
|
||||
return ''.join(reconstructed_text)
|
||||
else:
|
||||
return get_text(xmlns, string_index_node)
|
||||
|
||||
|
||||
def get_text(xmlns, rich_node):
|
||||
"""Read rich text, discarding formatting if not disallowed"""
|
||||
text_node = rich_node.find(QName(xmlns, 't').text)
|
||||
partial_text = text_node.text or ''
|
||||
|
||||
if text_node.get(QName(NAMESPACES['xml'], 'space').text) != 'preserve':
|
||||
partial_text = partial_text.strip()
|
||||
return unicode(partial_text)
|
||||
@@ -1,69 +0,0 @@
|
||||
# file openpyxl/reader/style.py
|
||||
|
||||
# Copyright (c) 2010 openpyxl
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# @license: http://www.opensource.org/licenses/mit-license.php
|
||||
# @author: Eric Gazoni
|
||||
|
||||
"""Read shared style definitions"""
|
||||
|
||||
# package imports
|
||||
from ..shared.xmltools import fromstring, QName
|
||||
from ..shared.exc import MissingNumberFormat
|
||||
from ..style import Style, NumberFormat
|
||||
|
||||
|
||||
def read_style_table(xml_source):
|
||||
"""Read styles from the shared style table"""
|
||||
table = {}
|
||||
xmlns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
|
||||
root = fromstring(xml_source)
|
||||
custom_num_formats = parse_custom_num_formats(root, xmlns)
|
||||
builtin_formats = NumberFormat._BUILTIN_FORMATS
|
||||
cell_xfs = root.find(QName(xmlns, 'cellXfs').text)
|
||||
cell_xfs_nodes = cell_xfs.findall(QName(xmlns, 'xf').text)
|
||||
for index, cell_xfs_node in enumerate(cell_xfs_nodes):
|
||||
new_style = Style()
|
||||
number_format_id = int(cell_xfs_node.get('numFmtId'))
|
||||
if number_format_id < 164:
|
||||
new_style.number_format.format_code = \
|
||||
builtin_formats.get(number_format_id, 'General')
|
||||
else:
|
||||
|
||||
if number_format_id in custom_num_formats:
|
||||
new_style.number_format.format_code = \
|
||||
custom_num_formats[number_format_id]
|
||||
else:
|
||||
raise MissingNumberFormat('%s' % number_format_id)
|
||||
table[index] = new_style
|
||||
return table
|
||||
|
||||
|
||||
def parse_custom_num_formats(root, xmlns):
|
||||
"""Read in custom numeric formatting rules from the shared style table"""
|
||||
custom_formats = {}
|
||||
num_fmts = root.find(QName(xmlns, 'numFmts').text)
|
||||
if num_fmts is not None:
|
||||
num_fmt_nodes = num_fmts.findall(QName(xmlns, 'numFmt').text)
|
||||
for num_fmt_node in num_fmt_nodes:
|
||||
custom_formats[int(num_fmt_node.get('numFmtId'))] = \
|
||||
num_fmt_node.get('formatCode')
|
||||
return custom_formats
|
||||
@@ -1,156 +0,0 @@
|
||||
# file openpyxl/reader/workbook.py
|
||||
|
||||
# Copyright (c) 2010 openpyxl
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# @license: http://www.opensource.org/licenses/mit-license.php
|
||||
# @author: Eric Gazoni
|
||||
|
||||
"""Read in global settings to be maintained by the workbook object."""
|
||||
|
||||
# package imports
|
||||
from ..shared.xmltools import fromstring, QName
|
||||
from ..shared.ooxml import NAMESPACES
|
||||
from ..workbook import DocumentProperties
|
||||
from ..shared.date_time import W3CDTF_to_datetime
|
||||
from ..namedrange import NamedRange, split_named_range
|
||||
|
||||
import datetime
|
||||
|
||||
# constants
|
||||
BUGGY_NAMED_RANGES = ['NA()', '#REF!']
|
||||
DISCARDED_RANGES = ['Excel_BuiltIn', 'Print_Area']
|
||||
|
||||
def get_sheet_ids(xml_source):
|
||||
|
||||
sheet_names = read_sheets_titles(xml_source)
|
||||
|
||||
return dict((sheet, 'sheet%d.xml' % (i + 1)) for i, sheet in enumerate(sheet_names))
|
||||
|
||||
|
||||
def read_properties_core(xml_source):
|
||||
"""Read assorted file properties."""
|
||||
properties = DocumentProperties()
|
||||
root = fromstring(xml_source)
|
||||
creator_node = root.find(QName(NAMESPACES['dc'], 'creator').text)
|
||||
if creator_node is not None:
|
||||
properties.creator = creator_node.text
|
||||
else:
|
||||
properties.creator = ''
|
||||
last_modified_by_node = root.find(
|
||||
QName(NAMESPACES['cp'], 'lastModifiedBy').text)
|
||||
if last_modified_by_node is not None:
|
||||
properties.last_modified_by = last_modified_by_node.text
|
||||
else:
|
||||
properties.last_modified_by = ''
|
||||
|
||||
created_node = root.find(QName(NAMESPACES['dcterms'], 'created').text)
|
||||
if created_node is not None:
|
||||
properties.created = W3CDTF_to_datetime(created_node.text)
|
||||
else:
|
||||
properties.created = datetime.datetime.now()
|
||||
|
||||
modified_node = root.find(QName(NAMESPACES['dcterms'], 'modified').text)
|
||||
if modified_node is not None:
|
||||
properties.modified = W3CDTF_to_datetime(modified_node.text)
|
||||
else:
|
||||
properties.modified = properties.created
|
||||
|
||||
return properties
|
||||
|
||||
|
||||
def get_number_of_parts(xml_source):
|
||||
"""Get a list of contents of the workbook."""
|
||||
parts_size = {}
|
||||
parts_names = []
|
||||
root = fromstring(xml_source)
|
||||
heading_pairs = root.find(QName('http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
|
||||
'HeadingPairs').text)
|
||||
vector = heading_pairs.find(QName(NAMESPACES['vt'], 'vector').text)
|
||||
children = vector.getchildren()
|
||||
for child_id in range(0, len(children), 2):
|
||||
part_name = children[child_id].find(QName(NAMESPACES['vt'],
|
||||
'lpstr').text).text
|
||||
if not part_name in parts_names:
|
||||
parts_names.append(part_name)
|
||||
part_size = int(children[child_id + 1].find(QName(
|
||||
NAMESPACES['vt'], 'i4').text).text)
|
||||
parts_size[part_name] = part_size
|
||||
return parts_size, parts_names
|
||||
|
||||
|
||||
def read_sheets_titles(xml_source):
|
||||
"""Read titles for all sheets."""
|
||||
root = fromstring(xml_source)
|
||||
titles_root = root.find(QName('http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
|
||||
'TitlesOfParts').text)
|
||||
vector = titles_root.find(QName(NAMESPACES['vt'], 'vector').text)
|
||||
parts, names = get_number_of_parts(xml_source)
|
||||
|
||||
# we can't assume 'Worksheets' to be written in english,
|
||||
# but it's always the first item of the parts list (see bug #22)
|
||||
size = parts[names[0]]
|
||||
children = [c.text for c in vector.getchildren()]
|
||||
return children[:size]
|
||||
|
||||
|
||||
def read_named_ranges(xml_source, workbook):
|
||||
"""Read named ranges, excluding poorly defined ranges."""
|
||||
named_ranges = []
|
||||
root = fromstring(xml_source)
|
||||
names_root = root.find(QName('http://schemas.openxmlformats.org/spreadsheetml/2006/main',
|
||||
'definedNames').text)
|
||||
if names_root is not None:
|
||||
|
||||
for name_node in names_root.getchildren():
|
||||
range_name = name_node.get('name')
|
||||
|
||||
if name_node.get("hidden", '0') == '1':
|
||||
continue
|
||||
|
||||
valid = True
|
||||
|
||||
for discarded_range in DISCARDED_RANGES:
|
||||
if discarded_range in range_name:
|
||||
valid = False
|
||||
|
||||
for bad_range in BUGGY_NAMED_RANGES:
|
||||
if bad_range in name_node.text:
|
||||
valid = False
|
||||
|
||||
if valid:
|
||||
destinations = split_named_range(name_node.text)
|
||||
|
||||
new_destinations = []
|
||||
for worksheet, cells_range in destinations:
|
||||
|
||||
# it can happen that a valid named range references
|
||||
# a missing worksheet, when Excel didn't properly maintain
|
||||
# the named range list
|
||||
#
|
||||
# we just ignore them here
|
||||
worksheet = workbook.get_sheet_by_name(worksheet)
|
||||
if worksheet:
|
||||
new_destinations.append((worksheet, cells_range))
|
||||
|
||||
named_range = NamedRange(range_name, new_destinations)
|
||||
named_ranges.append(named_range)
|
||||
|
||||
return named_ranges
|
||||
@@ -1,114 +0,0 @@
|
||||
# file openpyxl/reader/worksheet.py
|
||||
|
||||
# Copyright (c) 2010 openpyxl
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# @license: http://www.opensource.org/licenses/mit-license.php
|
||||
# @author: Eric Gazoni
|
||||
|
||||
"""Reader for a single worksheet."""
|
||||
|
||||
# Python stdlib imports
|
||||
try:
|
||||
from xml.etree.cElementTree import iterparse
|
||||
except ImportError:
|
||||
from xml.etree.ElementTree import iterparse
|
||||
|
||||
from ....compat import ifilter
|
||||
from ....compat import BytesIO as StringIO
|
||||
|
||||
# package imports
|
||||
from ..cell import Cell, coordinate_from_string
|
||||
from ..worksheet import Worksheet
|
||||
|
||||
def _get_xml_iter(xml_source):
|
||||
|
||||
if not hasattr(xml_source, 'name'):
|
||||
return StringIO(xml_source)
|
||||
else:
|
||||
xml_source.seek(0)
|
||||
return xml_source
|
||||
|
||||
def read_dimension(xml_source):
|
||||
|
||||
source = _get_xml_iter(xml_source)
|
||||
|
||||
it = iterparse(source)
|
||||
|
||||
for event, element in it:
|
||||
|
||||
if element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}dimension':
|
||||
ref = element.get('ref')
|
||||
|
||||
min_range, max_range = ref.split(':')
|
||||
min_col, min_row = coordinate_from_string(min_range)
|
||||
max_col, max_row = coordinate_from_string(max_range)
|
||||
|
||||
return min_col, min_row, max_col, max_row
|
||||
|
||||
else:
|
||||
element.clear()
|
||||
|
||||
return None
|
||||
|
||||
def filter_cells(x):
|
||||
(event, element) = x
|
||||
|
||||
return element.tag == '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c'
|
||||
|
||||
def fast_parse(ws, xml_source, string_table, style_table):
|
||||
|
||||
source = _get_xml_iter(xml_source)
|
||||
|
||||
it = iterparse(source)
|
||||
|
||||
for event, element in ifilter(filter_cells, it):
|
||||
|
||||
value = element.findtext('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v')
|
||||
|
||||
if value is not None:
|
||||
|
||||
coordinate = element.get('r')
|
||||
data_type = element.get('t', 'n')
|
||||
style_id = element.get('s')
|
||||
|
||||
if data_type == Cell.TYPE_STRING:
|
||||
value = string_table.get(int(value))
|
||||
|
||||
ws.cell(coordinate).value = value
|
||||
|
||||
if style_id is not None:
|
||||
ws._styles[coordinate] = style_table.get(int(style_id))
|
||||
|
||||
# to avoid memory exhaustion, clear the item after use
|
||||
element.clear()
|
||||
|
||||
from ..reader.iter_worksheet import IterableWorksheet
|
||||
|
||||
def read_worksheet(xml_source, parent, preset_title, string_table,
|
||||
style_table, workbook_name = None, sheet_codename = None):
|
||||
"""Read an xml worksheet"""
|
||||
if workbook_name and sheet_codename:
|
||||
ws = IterableWorksheet(parent, preset_title, workbook_name,
|
||||
sheet_codename, xml_source)
|
||||
else:
|
||||
ws = Worksheet(parent, preset_title)
|
||||
fast_parse(ws, xml_source, string_table, style_table)
|
||||
return ws
|
||||
@@ -1,33 +0,0 @@
|
||||
# file openpyxl/shared/__init__.py
|
||||
|
||||
# Copyright (c) 2010 openpyxl
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# @license: http://www.opensource.org/licenses/mit-license.php
|
||||
# @author: Eric Gazoni
|
||||
|
||||
"""Imports for the openpyxl.shared namespace."""
|
||||
|
||||
# package imports
|
||||
from . import date_time
|
||||
from . import exc
|
||||
from . import ooxml
|
||||
from . import password_hasher
|
||||
from . import xmltools
|
||||
@@ -1,154 +0,0 @@
|
||||
# file openpyxl/shared/date_time.py
|
||||
|
||||
# Copyright (c) 2010 openpyxl
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# @license: http://www.opensource.org/licenses/mit-license.php
|
||||
# @author: Eric Gazoni
|
||||
|
||||
"""Manage Excel date weirdness."""
|
||||
|
||||
# Python stdlib imports
|
||||
from __future__ import division
|
||||
from math import floor
|
||||
import calendar
|
||||
import datetime
|
||||
import time
|
||||
import re
|
||||
|
||||
# constants
|
||||
W3CDTF_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
|
||||
|
||||
RE_W3CDTF = '(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(.(\d{2}))?Z'
|
||||
|
||||
EPOCH = datetime.datetime.utcfromtimestamp(0)
|
||||
|
||||
def datetime_to_W3CDTF(dt):
|
||||
"""Convert from a datetime to a timestamp string."""
|
||||
return datetime.datetime.strftime(dt, W3CDTF_FORMAT)
|
||||
|
||||
|
||||
def W3CDTF_to_datetime(formatted_string):
|
||||
"""Convert from a timestamp string to a datetime object."""
|
||||
match = re.match(RE_W3CDTF,formatted_string)
|
||||
digits = map(int, match.groups()[:6])
|
||||
return datetime.datetime(*digits)
|
||||
|
||||
|
||||
class SharedDate(object):
|
||||
"""Date formatting utilities for Excel with shared state.
|
||||
|
||||
Excel has a two primary date tracking schemes:
|
||||
Windows - Day 1 == 1900-01-01
|
||||
Mac - Day 1 == 1904-01-01
|
||||
|
||||
SharedDate stores which system we are using and converts dates between
|
||||
Python and Excel accordingly.
|
||||
|
||||
"""
|
||||
CALENDAR_WINDOWS_1900 = 1900
|
||||
CALENDAR_MAC_1904 = 1904
|
||||
datetime_object_type = 'DateTime'
|
||||
|
||||
def __init__(self):
|
||||
self.excel_base_date = self.CALENDAR_WINDOWS_1900
|
||||
|
||||
def datetime_to_julian(self, date):
|
||||
"""Convert from python datetime to excel julian date representation."""
|
||||
|
||||
if isinstance(date, datetime.datetime):
|
||||
return self.to_julian(date.year, date.month, date.day, \
|
||||
hours=date.hour, minutes=date.minute, seconds=date.second)
|
||||
elif isinstance(date, datetime.date):
|
||||
return self.to_julian(date.year, date.month, date.day)
|
||||
|
||||
def to_julian(self, year, month, day, hours=0, minutes=0, seconds=0):
|
||||
"""Convert from Python date to Excel JD."""
|
||||
# explicitly disallow bad years
|
||||
# Excel 2000 treats JD=0 as 1/0/1900 (buggy, disallow)
|
||||
# Excel 2000 treats JD=2958466 as a bad date (Y10K bug!)
|
||||
if year < 1900 or year > 10000:
|
||||
msg = 'Year not supported by Excel: %s' % year
|
||||
raise ValueError(msg)
|
||||
if self.excel_base_date == self.CALENDAR_WINDOWS_1900:
|
||||
# Fudge factor for the erroneous fact that the year 1900 is
|
||||
# treated as a Leap Year in MS Excel. This affects every date
|
||||
# following 28th February 1900
|
||||
if year == 1900 and month <= 2:
|
||||
excel_1900_leap_year = False
|
||||
else:
|
||||
excel_1900_leap_year = True
|
||||
excel_base_date = 2415020
|
||||
else:
|
||||
raise NotImplementedError('Mac dates are not yet supported.')
|
||||
#excel_base_date = 2416481
|
||||
#excel_1900_leap_year = False
|
||||
|
||||
# Julian base date adjustment
|
||||
if month > 2:
|
||||
month = month - 3
|
||||
else:
|
||||
month = month + 9
|
||||
year -= 1
|
||||
|
||||
# Calculate the Julian Date, then subtract the Excel base date
|
||||
# JD 2415020 = 31 - Dec - 1899 -> Excel Date of 0
|
||||
century, decade = int(str(year)[:2]), int(str(year)[2:])
|
||||
excel_date = floor(146097 * century / 4) + \
|
||||
floor((1461 * decade) / 4) + floor((153 * month + 2) / 5) + \
|
||||
day + 1721119 - excel_base_date
|
||||
if excel_1900_leap_year:
|
||||
excel_date += 1
|
||||
|
||||
# check to ensure that we exclude 2/29/1900 as a possible value
|
||||
if self.excel_base_date == self.CALENDAR_WINDOWS_1900 \
|
||||
and excel_date == 60:
|
||||
msg = 'Error: Excel believes 1900 was a leap year'
|
||||
raise ValueError(msg)
|
||||
excel_time = ((hours * 3600) + (minutes * 60) + seconds) / 86400
|
||||
return excel_date + excel_time
|
||||
|
||||
def from_julian(self, value=0):
|
||||
"""Convert from the Excel JD back to a date"""
|
||||
if self.excel_base_date == self.CALENDAR_WINDOWS_1900:
|
||||
excel_base_date = 25569
|
||||
if value < 60:
|
||||
excel_base_date -= 1
|
||||
elif value == 60:
|
||||
msg = 'Error: Excel believes 1900 was a leap year'
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
raise NotImplementedError('Mac dates are not yet supported.')
|
||||
#excel_base_date = 24107
|
||||
|
||||
if value >= 1:
|
||||
utc_days = value - excel_base_date
|
||||
|
||||
return EPOCH + datetime.timedelta(days=utc_days)
|
||||
|
||||
elif value >= 0:
|
||||
hours = floor(value * 24)
|
||||
mins = floor(value * 24 * 60) - floor(hours * 60)
|
||||
secs = floor(value * 24 * 60 * 60) - floor(hours * 60 * 60) - \
|
||||
floor(mins * 60)
|
||||
return datetime.time(int(hours), int(mins), int(secs))
|
||||
else:
|
||||
msg = 'Negative dates (%s) are not supported' % value
|
||||
raise ValueError(msg)
|
||||
@@ -1,59 +0,0 @@
|
||||
# file openpyxl/shared/exc.py
|
||||
|
||||
# Copyright (c) 2010 openpyxl
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# @license: http://www.opensource.org/licenses/mit-license.php
|
||||
# @author: Eric Gazoni
|
||||
|
||||
"""Definitions for openpyxl shared exception classes."""
|
||||
|
||||
|
||||
class CellCoordinatesException(Exception):
|
||||
"""Error for converting between numeric and A1-style cell references."""
|
||||
|
||||
class ColumnStringIndexException(Exception):
|
||||
"""Error for bad column names in A1-style cell references."""
|
||||
|
||||
class DataTypeException(Exception):
|
||||
"""Error for any data type inconsistencies."""
|
||||
|
||||
class NamedRangeException(Exception):
|
||||
"""Error for badly formatted named ranges."""
|
||||
|
||||
class SheetTitleException(Exception):
|
||||
"""Error for bad sheet names."""
|
||||
|
||||
class InsufficientCoordinatesException(Exception):
|
||||
"""Error for partially specified cell coordinates."""
|
||||
|
||||
class OpenModeError(Exception):
|
||||
"""Error for fileobj opened in non-binary mode."""
|
||||
|
||||
class InvalidFileException(Exception):
|
||||
"""Error for trying to open a non-ooxml file."""
|
||||
|
||||
class ReadOnlyWorkbookException(Exception):
|
||||
"""Error for trying to modify a read-only workbook"""
|
||||
|
||||
class MissingNumberFormat(Exception):
|
||||
"""Error when a referenced number format is not in the stylesheet"""
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
# file openpyxl/shared/ooxml.py
|
||||
|
||||
# Copyright (c) 2010 openpyxl
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# @license: http://www.opensource.org/licenses/mit-license.php
|
||||
# @author: Eric Gazoni
|
||||
|
||||
"""Constants for fixed paths in a file and xml namespace urls."""
|
||||
|
||||
MIN_ROW = 0
|
||||
MIN_COLUMN = 0
|
||||
MAX_COLUMN = 16384
|
||||
MAX_ROW = 1048576
|
||||
|
||||
# constants
|
||||
PACKAGE_PROPS = 'docProps'
|
||||
PACKAGE_XL = 'xl'
|
||||
PACKAGE_RELS = '_rels'
|
||||
PACKAGE_THEME = PACKAGE_XL + '/' + 'theme'
|
||||
PACKAGE_WORKSHEETS = PACKAGE_XL + '/' + 'worksheets'
|
||||
PACKAGE_DRAWINGS = PACKAGE_XL + '/' + 'drawings'
|
||||
PACKAGE_CHARTS = PACKAGE_XL + '/' + 'charts'
|
||||
|
||||
ARC_CONTENT_TYPES = '[Content_Types].xml'
|
||||
ARC_ROOT_RELS = PACKAGE_RELS + '/.rels'
|
||||
ARC_WORKBOOK_RELS = PACKAGE_XL + '/' + PACKAGE_RELS + '/workbook.xml.rels'
|
||||
ARC_CORE = PACKAGE_PROPS + '/core.xml'
|
||||
ARC_APP = PACKAGE_PROPS + '/app.xml'
|
||||
ARC_WORKBOOK = PACKAGE_XL + '/workbook.xml'
|
||||
ARC_STYLE = PACKAGE_XL + '/styles.xml'
|
||||
ARC_THEME = PACKAGE_THEME + '/theme1.xml'
|
||||
ARC_SHARED_STRINGS = PACKAGE_XL + '/sharedStrings.xml'
|
||||
|
||||
NAMESPACES = {
|
||||
'cp': 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties',
|
||||
'dc': 'http://purl.org/dc/elements/1.1/',
|
||||
'dcterms': 'http://purl.org/dc/terms/',
|
||||
'dcmitype': 'http://purl.org/dc/dcmitype/',
|
||||
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
||||
'vt': 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes',
|
||||
'xml': 'http://www.w3.org/XML/1998/namespace'
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user