mirror of
https://github.com/kennethreitz/tablib.git
synced 2026-06-05 15:00:19 +00:00
Compare commits
180 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26b6faa88d | |||
| 140736ff33 | |||
| 5379c5683d | |||
| e8b44b5777 | |||
| a0822bc9b0 | |||
| 89b431213b | |||
| 695e8c5af7 | |||
| 0797ec67d4 | |||
| 1852624a7e | |||
| f81dc41a57 | |||
| 34415b89b8 | |||
| d25655588b | |||
| 22c4d185e1 | |||
| e3b3659ea4 | |||
| 22d337790a | |||
| 0784d4b32c | |||
| 332c5bccd9 | |||
| 7055d18a2e | |||
| 6a7c685111 | |||
| 0e5b8f7058 | |||
| e3e6b656e3 | |||
| 99896a5f28 | |||
| 25da44f569 | |||
| 7727171379 | |||
| 91bd4eb9c7 | |||
| 9b74b139fd | |||
| 823a543f41 | |||
| 1aa275bf99 | |||
| 17bb0d3b2c | |||
| 1a9aee9289 | |||
| 196edb82cc | |||
| a2990d5852 | |||
| d992ece86a | |||
| 46f302255d | |||
| 9e3ab4c13f | |||
| eaed0e48c2 | |||
| 501187b357 | |||
| ea4aef88b6 | |||
| 24d800fac3 | |||
| d8136ab613 | |||
| 36bbe2726b | |||
| 1427be2901 | |||
| 10ce000d31 | |||
| a91254117c | |||
| b67762604f | |||
| 83a8346e8f | |||
| 657ab98d04 | |||
| 9ddb4de942 | |||
| 5fad80a540 | |||
| cabab73045 | |||
| 2bb0525990 | |||
| f364bb576e | |||
| 09d057094e | |||
| 8082c4ad43 | |||
| 00e9ae0120 | |||
| f01c22213e | |||
| a58bf269d9 | |||
| 437a135dd3 | |||
| 0409ff50af | |||
| dd24edcc24 | |||
| cf28f4baa8 | |||
| 52dcf79c41 | |||
| 49f098ee22 | |||
| 642b1d8def | |||
| f6964bba8f | |||
| 8d6e75ad20 | |||
| 30487999ba | |||
| b74308e81e | |||
| 577289cbc3 | |||
| cf10703e31 | |||
| 778ad0265e | |||
| e3dedb8887 | |||
| c6e240fa52 | |||
| 5c747c9c2e | |||
| 0bbd990ed8 | |||
| fcada243a2 | |||
| fca8ad6182 | |||
| 35d9e390fd | |||
| 8ca180c461 | |||
| ff63558a67 | |||
| f621b56178 | |||
| 2b529bcb1c | |||
| 90c3435600 | |||
| 1fa28ee2ca | |||
| a5cae7c249 | |||
| 666991ca1e | |||
| 5f4162918f | |||
| b554ce36bb | |||
| e5e22d3ca2 | |||
| 8626351618 | |||
| cdfacb6d6e | |||
| 108c9de130 | |||
| 271aeebf56 | |||
| e75a00541d | |||
| 3b0e0c7991 | |||
| 23440fb7a3 | |||
| 459f310857 | |||
| f9021f53c2 | |||
| 7fda829d27 | |||
| ca08ac8a7b | |||
| 08b51113d3 | |||
| 3e391fc8e3 | |||
| a230844914 | |||
| bc82be09c5 | |||
| ed9fe01604 | |||
| e69546a0ff | |||
| d4b659ece9 | |||
| 55eb3f93e3 | |||
| be7182aea9 | |||
| 48def2cba6 | |||
| df8c0335d1 | |||
| d0b09f0fce | |||
| 9efd982bfa | |||
| a3c82804cd | |||
| 2e75e93f57 | |||
| a26d782e88 | |||
| f5c0c5c34d | |||
| d9aee8e605 | |||
| 315a082b70 | |||
| 120ce9fcd6 | |||
| 914a82eac9 | |||
| 3931bcb4e6 | |||
| 471e56c387 | |||
| 8553dbc040 | |||
| 96c93871cf | |||
| a54949bc08 | |||
| ed686c2391 | |||
| 143677be77 | |||
| b2c35c2543 | |||
| 9dfd9d0c8e | |||
| 140e23c980 | |||
| ac797f1eda | |||
| 7c90595364 | |||
| 38ac98fdb2 | |||
| 07c7d172d9 | |||
| 9c7707be60 | |||
| 14bee65208 | |||
| 4fc5e0655d | |||
| da2e670d0d | |||
| 5912bf4870 | |||
| 28e9d7e23e | |||
| 930d38cf5a | |||
| 5e433c263d | |||
| 19ac9b9716 | |||
| 6feb59504a | |||
| 817eedd6f5 | |||
| 4d1c5a9996 | |||
| 520a1986d7 | |||
| 1ea793112c | |||
| 41a7a5d329 | |||
| c4edaa2ca8 | |||
| c612bb3dae | |||
| bfe70066b8 | |||
| fbfbe01b70 | |||
| 06a394ea5c | |||
| 9427decdb0 | |||
| fb59035f8d | |||
| 187d12cffc | |||
| eaa4de7793 | |||
| d479c5735a | |||
| 96668bb393 | |||
| b369baba40 | |||
| 25f846a78a | |||
| 22fe18239f | |||
| 149bafa97b | |||
| 9f7fec2379 | |||
| 762ac39e27 | |||
| 2a7aa959b3 | |||
| d85523b6a6 | |||
| 25a5bcea0c | |||
| f58d4b67dc | |||
| a310ab7a09 | |||
| 7f2f925ddb | |||
| 3fc898e222 | |||
| de46f45e2e | |||
| 392eaac299 | |||
| 3a9c3944cf | |||
| 8c402da729 | |||
| 8feb6e8ddf | |||
| 9072b6ddae |
@@ -17,3 +17,4 @@ profile
|
||||
|
||||
# vi noise
|
||||
*.swp
|
||||
docs/_build/*
|
||||
|
||||
@@ -10,4 +10,6 @@ Development Lead
|
||||
Patches and Suggestions
|
||||
```````````````````````
|
||||
|
||||
- Luke Lee
|
||||
- Luke Lee
|
||||
- Josh Ourisman
|
||||
- Luca Beltrame
|
||||
+70
-12
@@ -1,32 +1,90 @@
|
||||
History
|
||||
=======
|
||||
-------
|
||||
|
||||
0.9.3 (2011-01-31)
|
||||
++++++++++++++++++
|
||||
|
||||
* Databook duplication leak fix.
|
||||
* HTML Table output.
|
||||
* Added column sorting.
|
||||
|
||||
|
||||
0.9.2 (2010-11-17)
|
||||
++++++++++++++++++
|
||||
|
||||
* Tanspose 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 taggins 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 vendorized modules.
|
||||
|
||||
|
||||
0.8.4 (2010-10-04)
|
||||
++++++++++++++++++
|
||||
|
||||
* Upated 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)
|
||||
++++++++++++++++++
|
||||
|
||||
0.8.2 (2010-09-28)
|
||||
------------------
|
||||
* 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 compenated in documentation.
|
||||
|
||||
|
||||
0.7.0 (2010-09-20)
|
||||
------------------
|
||||
++++++++++++++++++
|
||||
|
||||
* Renamed DataBook Databook for consistiency.
|
||||
* Export properties changed to methods (XLS filename / StringIO bug).
|
||||
@@ -35,31 +93,31 @@ History
|
||||
|
||||
|
||||
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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2010 Kenneth Reitz.
|
||||
Copyright (c) 2011 Kenneth Reitz.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
Tablib includes some vendorized python libraries: ordereddict, pyyaml,
|
||||
simplejson, and xlwt.
|
||||
|
||||
Markup License
|
||||
==============
|
||||
|
||||
Markup is in the public domain.
|
||||
|
||||
|
||||
|
||||
OrderedDict License
|
||||
===================
|
||||
|
||||
Copyright (c) 2009 Raymond Hettinger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
(the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
PyYAML License
|
||||
==============
|
||||
|
||||
Copyright (c) 2006 Kirill Simonov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
|
||||
SimpleJSON License
|
||||
==================
|
||||
|
||||
Copyright (c) 2006 Bob Ippolito
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
|
||||
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
|
||||
+6
-4
@@ -18,12 +18,15 @@ Output formats supported:
|
||||
- Excel (Sets + Books)
|
||||
- JSON (Sets + Books)
|
||||
- YAML (Sets + Books)
|
||||
- HTML (Sets)
|
||||
- TSV (Sets)
|
||||
- CSV (Sets)
|
||||
|
||||
Import formats supported:
|
||||
|
||||
- JSON (Sets + Books)
|
||||
- YAML (Sets + Books)
|
||||
- TSV (Sets)
|
||||
- CSV (Sets)
|
||||
|
||||
Note that tablib *purposefully* excludes XML support. It always will.
|
||||
@@ -59,7 +62,7 @@ Intelligently add new rows: ::
|
||||
|
||||
Intelligently add new columns: ::
|
||||
|
||||
>>> data.append(col=('age', 90, 67, 83))
|
||||
>>> data.append(col=(90, 67, 83), header='age')
|
||||
|
||||
Slice rows: ::
|
||||
|
||||
@@ -150,7 +153,7 @@ CSV
|
||||
+++
|
||||
::
|
||||
|
||||
>>> data.yaml = 'age, first_name, last_name\n90, John, Adams'
|
||||
>>> data.csv = 'age, first_name, last_name\n90, John, Adams'
|
||||
>>> print data[0]
|
||||
('John', 'Adams', 90)
|
||||
|
||||
@@ -169,8 +172,7 @@ To install tablib, simply: ::
|
||||
Or, if you absolutely must: ::
|
||||
|
||||
$ easy_install tablib
|
||||
|
||||
|
||||
|
||||
Contribute
|
||||
----------
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
* Add seperator support to HTML out
|
||||
* Hooks System
|
||||
- pre/post-append
|
||||
- pre/post-import
|
||||
- pre/post-export
|
||||
* Big Data
|
||||
* Backwards-compatible OrderedDict support
|
||||
* Write more exhausive unit-tests.
|
||||
* Write stress tests.
|
||||
* Make CSV write customizable.
|
||||
* Integrate django-tablib
|
||||
* Mention django-tablib in Documention
|
||||
* Dataset title usage in documentation (#17)
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Tablib.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Tablib.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Tablib"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Tablib"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
make -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
@@ -0,0 +1,3 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
.DS_Store
|
||||
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
Modifications:
|
||||
|
||||
Copyright (c) 2011 Kenneth Reitz.
|
||||
|
||||
|
||||
Original Project:
|
||||
|
||||
Copyright (c) 2010 by Armin Ronacher.
|
||||
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms of the theme, with or
|
||||
without modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
We kindly ask you to only use these themes in an unmodified manner just
|
||||
for Flask and Flask-related products, not for unrelated projects. If you
|
||||
like the visual style and want to use it for your own projects, please
|
||||
consider making some larger changes to the themes (such as changing
|
||||
font faces, sizes, colors or margins).
|
||||
|
||||
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
krTheme Sphinx Style
|
||||
====================
|
||||
|
||||
This repository contains sphinx styles Kenneth Reitz uses in most of
|
||||
his projects. It is a drivative of Mitsuhiko's themes for Flask and Flask related
|
||||
projects. To use this style in your Sphinx documentation, follow
|
||||
this guide:
|
||||
|
||||
1. put this folder as _themes into your docs folder. Alternatively
|
||||
you can also use git submodules to check out the contents there.
|
||||
|
||||
2. add this to your conf.py: ::
|
||||
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
html_theme_path = ['_themes']
|
||||
html_theme = 'flask'
|
||||
|
||||
The following themes exist:
|
||||
|
||||
**kr**
|
||||
the standard flask documentation theme for large projects
|
||||
|
||||
**kr_small**
|
||||
small one-page theme. Intended to be used by very small addon libraries.
|
||||
|
||||
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
# flasky extensions. flasky pygments style based on tango style
|
||||
from pygments.style import Style
|
||||
from pygments.token import Keyword, Name, Comment, String, Error, \
|
||||
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
|
||||
|
||||
|
||||
class FlaskyStyle(Style):
|
||||
background_color = "#f8f8f8"
|
||||
default_style = ""
|
||||
|
||||
styles = {
|
||||
# No corresponding class for the following:
|
||||
#Text: "", # class: ''
|
||||
Whitespace: "underline #f8f8f8", # class: 'w'
|
||||
Error: "#a40000 border:#ef2929", # class: 'err'
|
||||
Other: "#000000", # class 'x'
|
||||
|
||||
Comment: "italic #8f5902", # class: 'c'
|
||||
Comment.Preproc: "noitalic", # class: 'cp'
|
||||
|
||||
Keyword: "bold #004461", # class: 'k'
|
||||
Keyword.Constant: "bold #004461", # class: 'kc'
|
||||
Keyword.Declaration: "bold #004461", # class: 'kd'
|
||||
Keyword.Namespace: "bold #004461", # class: 'kn'
|
||||
Keyword.Pseudo: "bold #004461", # class: 'kp'
|
||||
Keyword.Reserved: "bold #004461", # class: 'kr'
|
||||
Keyword.Type: "bold #004461", # class: 'kt'
|
||||
|
||||
Operator: "#582800", # class: 'o'
|
||||
Operator.Word: "bold #004461", # class: 'ow' - like keywords
|
||||
|
||||
Punctuation: "bold #000000", # class: 'p'
|
||||
|
||||
# because special names such as Name.Class, Name.Function, etc.
|
||||
# are not recognized as such later in the parsing, we choose them
|
||||
# to look the same as ordinary variables.
|
||||
Name: "#000000", # class: 'n'
|
||||
Name.Attribute: "#c4a000", # class: 'na' - to be revised
|
||||
Name.Builtin: "#004461", # class: 'nb'
|
||||
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
|
||||
Name.Class: "#000000", # class: 'nc' - to be revised
|
||||
Name.Constant: "#000000", # class: 'no' - to be revised
|
||||
Name.Decorator: "#888", # class: 'nd' - to be revised
|
||||
Name.Entity: "#ce5c00", # class: 'ni'
|
||||
Name.Exception: "bold #cc0000", # class: 'ne'
|
||||
Name.Function: "#000000", # class: 'nf'
|
||||
Name.Property: "#000000", # class: 'py'
|
||||
Name.Label: "#f57900", # class: 'nl'
|
||||
Name.Namespace: "#000000", # class: 'nn' - to be revised
|
||||
Name.Other: "#000000", # class: 'nx'
|
||||
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
|
||||
Name.Variable: "#000000", # class: 'nv' - to be revised
|
||||
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
|
||||
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
|
||||
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
|
||||
|
||||
Number: "#990000", # class: 'm'
|
||||
|
||||
Literal: "#000000", # class: 'l'
|
||||
Literal.Date: "#000000", # class: 'ld'
|
||||
|
||||
String: "#4e9a06", # class: 's'
|
||||
String.Backtick: "#4e9a06", # class: 'sb'
|
||||
String.Char: "#4e9a06", # class: 'sc'
|
||||
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
|
||||
String.Double: "#4e9a06", # class: 's2'
|
||||
String.Escape: "#4e9a06", # class: 'se'
|
||||
String.Heredoc: "#4e9a06", # class: 'sh'
|
||||
String.Interpol: "#4e9a06", # class: 'si'
|
||||
String.Other: "#4e9a06", # class: 'sx'
|
||||
String.Regex: "#4e9a06", # class: 'sr'
|
||||
String.Single: "#4e9a06", # class: 's1'
|
||||
String.Symbol: "#4e9a06", # class: 'ss'
|
||||
|
||||
Generic: "#000000", # class: 'g'
|
||||
Generic.Deleted: "#a40000", # class: 'gd'
|
||||
Generic.Emph: "italic #000000", # class: 'ge'
|
||||
Generic.Error: "#ef2929", # class: 'gr'
|
||||
Generic.Heading: "bold #000080", # class: 'gh'
|
||||
Generic.Inserted: "#00A000", # class: 'gi'
|
||||
Generic.Output: "#888", # class: 'go'
|
||||
Generic.Prompt: "#745334", # class: 'gp'
|
||||
Generic.Strong: "bold #000000", # class: 'gs'
|
||||
Generic.Subheading: "bold #800080", # class: 'gu'
|
||||
Generic.Traceback: "bold #a40000", # class: 'gt'
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
{%- extends "basic/layout.html" %}
|
||||
{%- block extrahead %}
|
||||
{{ super() }}
|
||||
{% if theme_touch_icon %}
|
||||
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
|
||||
{% endif %}
|
||||
<link media="only screen and (max-device-width: 480px)" href="{{
|
||||
pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
|
||||
{% endblock %}
|
||||
{%- block relbar2 %}{% endblock %}
|
||||
{%- block footer %}
|
||||
<div class="footer">
|
||||
© Copyright {{ copyright }}.
|
||||
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
|
||||
</div>
|
||||
{%- endblock %}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
<h3>Related Topics</h3>
|
||||
<ul>
|
||||
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
|
||||
{%- for parent in parents %}
|
||||
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
|
||||
{%- endfor %}
|
||||
{%- if prev %}
|
||||
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
|
||||
}}">{{ prev.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- if next %}
|
||||
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
|
||||
}}">{{ next.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- for parent in parents %}
|
||||
</ul></li>
|
||||
{%- endfor %}
|
||||
</ul></li>
|
||||
</ul>
|
||||
Vendored
+387
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
* flasky.css_t
|
||||
* ~~~~~~~~~~~~
|
||||
*
|
||||
* :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz.
|
||||
* :license: Flask Design License, see LICENSE for details.
|
||||
*/
|
||||
|
||||
{% set page_width = '940px' %}
|
||||
{% set sidebar_width = '220px' %}
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro';
|
||||
font-size: 17px;
|
||||
background-color: white;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: {{ page_width }};
|
||||
margin: 30px auto 0 auto;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 {{ sidebar_width }};
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
width: {{ sidebar_width }};
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 0 30px;
|
||||
}
|
||||
|
||||
img.floatingflask {
|
||||
padding: 0 0 10px 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
width: {{ page_width }};
|
||||
margin: 20px auto 30px auto;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a:hover {
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 18px 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper p.logo {
|
||||
padding: 0 0 20px 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
color: #444;
|
||||
font-size: 24px;
|
||||
font-weight: normal;
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo a,
|
||||
div.sphinxsidebar h3 a,
|
||||
div.sphinxsidebar p.logo a:hover,
|
||||
div.sphinxsidebar h3 a:hover {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #555;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #ccc;
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #004B6B;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #6D4100;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
|
||||
div.body h2 { font-size: 180%; }
|
||||
div.body h3 { font-size: 150%; }
|
||||
div.body h4 { font-size: 130%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: #ddd;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #444;
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
background: #fafafa;
|
||||
margin: 20px -30px;
|
||||
padding: 10px 30px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.admonition tt.xref, div.admonition a tt {
|
||||
border-bottom: 1px solid #fafafa;
|
||||
}
|
||||
|
||||
dd div.admonition {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.highlight {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dt:target, .highlight {
|
||||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre, tt {
|
||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
padding-right: 0.08em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
border: 1px solid #888;
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
border: 1px solid #888;
|
||||
padding: 0.25em 0.7em;
|
||||
}
|
||||
|
||||
table.field-list, table.footnote {
|
||||
border: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
margin: 15px 0;
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
background: #fdfdfd;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
table.footnote + table.footnote {
|
||||
margin-top: -15px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
table.field-list th {
|
||||
padding: 0 0.8em 0 0;
|
||||
}
|
||||
|
||||
table.field-list td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.footnote td.label {
|
||||
width: 0px;
|
||||
padding: 0.3em 0 0.3em 0.5em;
|
||||
}
|
||||
|
||||
table.footnote td {
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 0 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 10px 0 10px 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #eee;
|
||||
padding: 7px 30px;
|
||||
margin: 15px -30px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
dl pre, blockquote pre, li pre {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
dl dl pre {
|
||||
margin-left: -90px;
|
||||
padding-left: 90px;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: #FBFBFB;
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
|
||||
a.reference {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
a.footnote-reference {
|
||||
text-decoration: none;
|
||||
font-size: 0.7em;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.footnote-reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
a:hover tt {
|
||||
background: #EEE;
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* small_flask.css_t
|
||||
* ~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* :copyright: Copyright 2010 by Armin Ronacher.
|
||||
* :license: Flask Design License, see LICENSE for details.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: none;
|
||||
background: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
display: block;
|
||||
float: none;
|
||||
width: 102.5%;
|
||||
margin: 50px -30px -20px -30px;
|
||||
padding: 10px 20px;
|
||||
background: #333;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
|
||||
div.sphinxsidebar h3 a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 10px 0 20px 0;
|
||||
}
|
||||
|
||||
div.related ul,
|
||||
div.related ul li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.body {
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = flasky.css
|
||||
pygments_style = flask_theme_support.FlaskyStyle
|
||||
|
||||
[options]
|
||||
touch_icon =
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
{% extends "basic/layout.html" %}
|
||||
{% block header %}
|
||||
{{ super() }}
|
||||
{% if pagename == 'index' %}
|
||||
<div class=indexwrapper>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
{% if pagename == 'index' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{# do not display relbars #}
|
||||
{% block relbar1 %}{% endblock %}
|
||||
{% block relbar2 %}
|
||||
{% if theme_github_fork %}
|
||||
<a href="http://github.com/{{ theme_github_fork }}"><img style="position: fixed; top: 0; right: 0; border: 0;"
|
||||
src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block sidebar1 %}{% endblock %}
|
||||
{% block sidebar2 %}{% endblock %}
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* flasky.css_t
|
||||
* ~~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- flasky theme based on nature theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 17px;
|
||||
color: #000;
|
||||
background: white;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 40px auto 0 auto;
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 30px 30px;
|
||||
}
|
||||
|
||||
img.floatingflask {
|
||||
padding: 0 0 10px 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
text-align: right;
|
||||
color: #888;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
width: 650px;
|
||||
margin: 0 auto 40px auto;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #888;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.related {
|
||||
line-height: 32px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.related ul {
|
||||
padding: 0 0 0 10px;
|
||||
}
|
||||
|
||||
div.related a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #004B6B;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #6D4100;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body {
|
||||
padding-bottom: 40px; /* saved for footer */
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
{% if theme_index_logo %}
|
||||
div.indexwrapper h1 {
|
||||
text-indent: -999999px;
|
||||
background: url({{ theme_index_logo }}) no-repeat center center;
|
||||
height: {{ theme_index_logo_height }};
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
div.body h2 { font-size: 180%; }
|
||||
div.body h3 { font-size: 150%; }
|
||||
div.body h4 { font-size: 130%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: white;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #444;
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
background: #fafafa;
|
||||
margin: 20px -30px;
|
||||
padding: 10px 30px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.highlight{
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dt:target, .highlight {
|
||||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
div.warning {
|
||||
background-color: #ffe4e4;
|
||||
border: 1px solid #f66;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre, tt {
|
||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
padding-right: 0.08em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
border: 1px solid #888;
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
border: 1px solid #888;
|
||||
padding: 0.25em 0.7em;
|
||||
}
|
||||
|
||||
table.field-list, table.footnote {
|
||||
border: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
margin: 15px 0;
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
table.field-list th {
|
||||
padding: 0 0.8em 0 0;
|
||||
}
|
||||
|
||||
table.field-list td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.footnote td {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 0;
|
||||
margin: 15px -30px;
|
||||
padding: 8px;
|
||||
line-height: 1.3em;
|
||||
padding: 7px 30px;
|
||||
background: #eee;
|
||||
border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
}
|
||||
|
||||
dl pre {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: #FBFBFB;
|
||||
}
|
||||
|
||||
a:hover tt {
|
||||
background: #EEE;
|
||||
}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = flasky.css
|
||||
nosidebar = true
|
||||
pygments_style = flask_theme_support.FlaskyStyle
|
||||
|
||||
[options]
|
||||
index_logo = ''
|
||||
index_logo_height = 120px
|
||||
github_fork = ''
|
||||
@@ -0,0 +1,64 @@
|
||||
.. _api:
|
||||
|
||||
===
|
||||
API
|
||||
===
|
||||
|
||||
|
||||
.. module:: tablib
|
||||
|
||||
This part of the documentation covers all the interfaces of Tablib. For
|
||||
parts where Tablib depends on external libraries, we document the most
|
||||
important right here and provide links to the canonical documentation.
|
||||
|
||||
|
||||
--------------
|
||||
Dataset Object
|
||||
--------------
|
||||
|
||||
|
||||
.. autoclass:: Dataset
|
||||
:inherited-members:
|
||||
|
||||
|
||||
---------------
|
||||
Databook Object
|
||||
---------------
|
||||
|
||||
|
||||
.. autoclass:: Databook
|
||||
:inherited-members:
|
||||
|
||||
|
||||
|
||||
---------
|
||||
Functions
|
||||
---------
|
||||
|
||||
|
||||
.. autofunction:: detect
|
||||
|
||||
.. autofunction:: import_set
|
||||
|
||||
|
||||
----------
|
||||
Exceptions
|
||||
----------
|
||||
|
||||
|
||||
.. class:: InvalidDatasetType
|
||||
|
||||
You're trying to add something that doesn't quite look right.
|
||||
|
||||
|
||||
.. class:: InvalidDimensions
|
||||
|
||||
You're trying to add something that doesn't quite fit right.
|
||||
|
||||
|
||||
.. class:: UnsupportedFormat
|
||||
|
||||
You're trying to add something that doesn't quite taste right.
|
||||
|
||||
|
||||
Now, go start some :ref:`Tablib Development <development>`.
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Tablib documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue Oct 5 15:25:21 2010.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
import tablib
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Tablib'
|
||||
copyright = u'2011, Kenneth Reitz. Styles (modified) © Armin Ronacher'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = tablib.core.__version__
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'flask_theme_support.FlaskyStyle'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
html_show_sphinx = False
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Tablibdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Tablib.tex', u'Tablib Documentation',
|
||||
u'Kenneth Reitz', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'tablib', u'Tablib Documentation',
|
||||
[u'Kenneth Reitz'], 1)
|
||||
]
|
||||
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
html_theme_path = ['_themes']
|
||||
html_theme = 'kr'
|
||||
@@ -0,0 +1,246 @@
|
||||
.. _development:
|
||||
|
||||
Development
|
||||
===========
|
||||
|
||||
Tablib is under active development, and contributors are welcome.
|
||||
|
||||
If you have a feature request, suggestion, or bug report, please open a new issue on GitHub_. To submit patches, please send a pull request on GitHub_.
|
||||
|
||||
If you'd like to contribute, there's plenty to do. Here's a short todo list.
|
||||
|
||||
.. include:: ../TODO.rst
|
||||
|
||||
|
||||
.. _GitHub: http://github.com/kennethreitz/tablib/
|
||||
|
||||
|
||||
|
||||
.. _design:
|
||||
|
||||
---------------------
|
||||
Design Considerations
|
||||
---------------------
|
||||
|
||||
Tablib was developed with a few :pep:`20` idioms in mind.
|
||||
|
||||
#. Beautiful is better than ugly.
|
||||
#. Explicit is better than implicit.
|
||||
#. Simple is better than complex.
|
||||
#. Complex is better than complicated.
|
||||
#. Readability counts.
|
||||
|
||||
A few other things to keep in mind:
|
||||
|
||||
#. Keep your code DRY.
|
||||
#. Strive to be as simple (to use) as possible.
|
||||
|
||||
.. _scm:
|
||||
|
||||
--------------
|
||||
Source Control
|
||||
--------------
|
||||
|
||||
|
||||
Tablib source is controlled with Git_, the lean, mean, distributed source control machine.
|
||||
|
||||
The repository is publicly accessable.
|
||||
|
||||
``git clone git://github.com/kennethreitz/tablib.git``
|
||||
|
||||
The project is hosted both on **GitHub** and **git.kennethreitz.com**.
|
||||
|
||||
|
||||
GitHub:
|
||||
http://github.com/kennethreitz/tablib
|
||||
"Mirror":
|
||||
http://git.kennethreitz.com/projects/tablib
|
||||
|
||||
|
||||
Git Branch Structure
|
||||
++++++++++++++++++++
|
||||
|
||||
Feature / Hotfix / Release branches follow a `Successful Git Branching Model`_ . Git-flow_ is a great tool for managing the repository. I highly recommend it.
|
||||
|
||||
``develop``
|
||||
The "next release" branch. Likely unstable.
|
||||
``master``
|
||||
Current production release (|version|) on PyPi.
|
||||
``gh-pages``
|
||||
Current release of http://tablib.org.
|
||||
|
||||
Each release is tagged.
|
||||
|
||||
When submitting patches, please place your feature/change in its own branch prior to opening a pull reqeust on GitHub_.
|
||||
|
||||
|
||||
.. _Git: http://git-scm.org
|
||||
.. _`Successful Git Branching Model`: http://nvie.com/posts/a-successful-git-branching-model/
|
||||
.. _git-flow: http://github.com/nvie/gitflow
|
||||
|
||||
|
||||
.. _newformats:
|
||||
|
||||
------------------
|
||||
Adding New Formats
|
||||
------------------
|
||||
|
||||
Tablib welcomes new format additions! Format suggestions include:
|
||||
|
||||
* Tab Seperated Values
|
||||
* MySQL Dump
|
||||
* HTML Table
|
||||
|
||||
|
||||
Coding by Convention
|
||||
++++++++++++++++++++
|
||||
|
||||
Tablib features a micro-framework for adding format support. The easiest way to understand it is to use it. So, let's define our own format, named *xxx*.
|
||||
|
||||
1. Write a new format interface.
|
||||
|
||||
:class:`tablib.core` follows a simple pattern for automatically utilizing your format throughout Tablib. Function names are crucial.
|
||||
|
||||
Example **tablib/formats/_xxx.py**: ::
|
||||
|
||||
title = 'xxx'
|
||||
|
||||
def export_set(dset):
|
||||
....
|
||||
# returns string representation of given dataset
|
||||
|
||||
def export_book(dbook):
|
||||
....
|
||||
# returns string representation of given databook
|
||||
|
||||
def import_set(dset, in_stream):
|
||||
...
|
||||
# populates given Dataset with given datastream
|
||||
|
||||
def import_book(dbook, in_stream):
|
||||
...
|
||||
# returns Databook instance
|
||||
|
||||
def detect(stream):
|
||||
...
|
||||
# returns True if given stream is parsable as xxx
|
||||
|
||||
.. admonition:: Excluding Support
|
||||
|
||||
|
||||
If the format excludes support for an import/export mechanism (*eg.* :class:`csv <tablib.Dataset.csv>` excludes :class:`Databook <tablib.Databook>` support), simply don't define the respecive functions. Appropriate errors will be raised.
|
||||
|
||||
2.
|
||||
|
||||
Add your new format module to the :class:`tablib.formats.avalable` tuple.
|
||||
|
||||
3.
|
||||
Add a mock property to the :class:`Dataset <tablib.Dataset>` class with verbose `reStructured Text`_ docstring. This alleviates IDE confusion, and allows for pretty auto-generated Sphinx_ documentation.
|
||||
|
||||
4. Write respective :ref:`tests <testing>`.
|
||||
|
||||
.. _testing:
|
||||
|
||||
--------------
|
||||
Testing Tablib
|
||||
--------------
|
||||
|
||||
Testing is crucial to Tablib's stability. This stable project is used in production by many companies and developers, so it is important to be certian that every version released is fully operational. When developing a new feature for Tablib, be sure to write proper tests for it as well.
|
||||
|
||||
When developing a feature for Tablib, the easiest way to test your changes for potential issues is to simply run the test suite directly. ::
|
||||
|
||||
$ ./test_tablib.py
|
||||
|
||||
|
||||
`Hudson CI`_, amongst other tools, supports Java's xUnit testing report format. Nose_ allows us to generate our own xUnit reports.
|
||||
|
||||
Installing nose is simple. ::
|
||||
|
||||
$ pip install nose
|
||||
|
||||
Once installed, we can generate our xUnit report with a single command. ::
|
||||
|
||||
$ nosetests test_tablib.py --with-xunit
|
||||
|
||||
This will generate a **nosetests.xml** file, which can then be analyzed.
|
||||
|
||||
.. _Nose: http://somethingaboutorange.com/mrl/projects/nose/
|
||||
|
||||
|
||||
|
||||
.. _hudson:
|
||||
|
||||
----------------------
|
||||
Continuous Integration
|
||||
----------------------
|
||||
|
||||
Every commit made to the **develop** branch is automatically tested and inspected upon receipt with `Hudson CI`_. If you have access to the main respository and broke the build, you will receive an email accordingly.
|
||||
|
||||
Anyone may view the build status and history at any time.
|
||||
|
||||
http://git.kennethreitz.com/ci/
|
||||
|
||||
|
||||
If you are trustworthy and plan to contribute to tablib on a regular basis, please contact `Kenneth Reitz`_ to get an account on the Hudson Server.
|
||||
|
||||
|
||||
Additional reports will also be included here in the future, including :pep:`8` checks and stress reports for extremely large datasets.
|
||||
|
||||
.. _`Hudson CI`: http://hudson.dev.java.net
|
||||
.. _`Kenneth Reitz`: http://kennethreitz.com/contact-me/
|
||||
|
||||
|
||||
.. _docs:
|
||||
|
||||
-----------------
|
||||
Building the Docs
|
||||
-----------------
|
||||
|
||||
Documentation is written in the powerful, flexible, and standard Python documentation format, `reStructured Text`_.
|
||||
Documentation builds are powered by the powerful Pocoo project, Sphinx_. The :ref:`API Documentation <api>` is mostly documented inline throught the module.
|
||||
|
||||
The Docs live in ``tablib/docs``. In order to build them, you will first need to install Sphinx. ::
|
||||
|
||||
$ pip install sphinx
|
||||
|
||||
|
||||
Then, to build an HTML version of the docs, simply run the following from the **docs** directory: ::
|
||||
|
||||
$ make html
|
||||
|
||||
Your ``docs/_build/html`` directory will then contain an HTML representation of the documentation, ready for publication on most web servers.
|
||||
|
||||
You can also generate the documentation in **ebpub**, **latex**, **json**, *&c* similarly.
|
||||
|
||||
.. admonition:: GitHub Pages
|
||||
|
||||
To push the documentation up to `GitHub Pages`_, you will first need to run `sphinx-to-github`_ against your ``docs/_build/html`` directory.
|
||||
|
||||
GitHub Pages are powered by an HTML generation system called Jeckyl_, which is configured to ignore files and folders that begin with "``_``" (*ie.* **_static**).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
and `sphinx-to-github`_. ::
|
||||
|
||||
Installing sphinx-to-github is simple. ::
|
||||
|
||||
$ pip install sphinx-to-github
|
||||
|
||||
Running it against the docs is even simpler. ::
|
||||
|
||||
$ sphinx-to-github _build/html
|
||||
|
||||
Move the resulting files to the **gh-pages** branch of your repository, and push it up to GitHub.
|
||||
|
||||
.. _`reStructured Text`: http://docutils.sourceforge.net/rst.html
|
||||
.. _Sphinx: http://sphinx.pocoo.org
|
||||
.. _`GitHub Pages`: http://pages.github.com
|
||||
.. _Jeckyl: http://github.com/mojombo/jekyll
|
||||
.. _`sphinx-to-github`: http://github.com/michaeljones/sphinx-to-github
|
||||
|
||||
----------
|
||||
|
||||
Make sure to check out the :ref:`API Documentation <api>`.
|
||||
@@ -0,0 +1,62 @@
|
||||
.. Tablib documentation master file, created by
|
||||
sphinx-quickstart on Tue Oct 5 15:25:21 2010.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Tablib: Pythonic Tabular Data
|
||||
=============================
|
||||
|
||||
.. Contents:
|
||||
..
|
||||
.. .. toctree::
|
||||
.. :maxdepth: 2
|
||||
..
|
||||
|
||||
.. Indices and tables
|
||||
.. ==================
|
||||
..
|
||||
.. * :ref:`genindex`
|
||||
.. * :ref:`modindex`
|
||||
.. * :ref:`search`
|
||||
|
||||
|
||||
Tablib is an :ref:`MIT Lisenced <mit>` format-agnostic tabular dataset library, written in Python. It allows you to import, export, and manipulate tabular data sets. Advanced features include, segregation, dynamic columns, tags & filtering, and seamless format import & exmport.
|
||||
|
||||
I recommend you start with :ref:`Installation <install>`.
|
||||
|
||||
User's Guide
|
||||
------------
|
||||
|
||||
This part of the documentation, which is mostly prose, begins with some background information about Tablib, then focuses on step-by-step instructions for getting the most out of your datasets.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
intro
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
install
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
tutorial
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
development
|
||||
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
If you are looking for information on a specific function, class or
|
||||
method, this part of the documentation is for you.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api
|
||||
@@ -0,0 +1,78 @@
|
||||
.. _install:
|
||||
Installation
|
||||
============
|
||||
|
||||
This part of the documentation covers the installation of Tablib. The first step to using any software package is getting it properly installed. Please read this section carefully, or you may miss out on some nice :ref:`speed enhancments <peed-extentions>`.
|
||||
|
||||
|
||||
.. _installing:
|
||||
|
||||
-----------------
|
||||
Installing Tablib
|
||||
-----------------
|
||||
|
||||
To install Tablib, it only takes one simple command. ::
|
||||
|
||||
$ pip install tablib
|
||||
|
||||
Or, if you must: ::
|
||||
|
||||
$ easy_install tablib
|
||||
|
||||
But, you really shouldn't do that.
|
||||
|
||||
|
||||
-------------------
|
||||
Download the Source
|
||||
-------------------
|
||||
|
||||
You can also install tablib from source. The latest release (|version|) is available from GitHub.
|
||||
|
||||
* tarball_
|
||||
* zipball_
|
||||
|
||||
.. _
|
||||
Once you have a copy of the source, you can embed it in your Python package, or install it into your site-packages easily. ::
|
||||
|
||||
$ python setup.py install
|
||||
|
||||
|
||||
To download the full source history from Git, see :ref:`Source Control <scm>`.
|
||||
|
||||
.. _tarball: http://github.com/kennethreitz/tablib/tarball/master
|
||||
.. _zipball: http://github.com/kennethreitz/tablib/zipball/master
|
||||
|
||||
|
||||
.. _speed-extentions:
|
||||
Speed Extentions
|
||||
----------------
|
||||
|
||||
.. versionadded:: 0.8.5
|
||||
|
||||
Tablib is partially dependent on the **pyyaml**, **simplejson**, and **xlwt** modules. To reduce installation issues, fully integrated versions of all required libraries are included in Tablib.
|
||||
|
||||
However, if performance is important to you (and it should be), you can install **pyyaml** with C extentions from PyPi. ::
|
||||
|
||||
$ pip install PyYAML
|
||||
|
||||
If you're using Python 2.5 (currently unsupported), you should also install the **simplejson** module. If you're using Python 2.6+, the built-in **json** module is already optimized and in use. ::
|
||||
|
||||
$ pip install simplejson
|
||||
|
||||
|
||||
|
||||
.. _updates:
|
||||
Staying Updated
|
||||
---------------
|
||||
|
||||
The latest version of Tablib will always be available here:
|
||||
|
||||
* PyPi: http://pypi.python.org/pypi/tablib/
|
||||
* GitHub: http://github.com/kennethreitz/tablib/
|
||||
|
||||
When a new version is available, upgrading is simple. ::
|
||||
|
||||
$ pip install tablib --upgrade
|
||||
|
||||
|
||||
Now, go get a :ref:`Quick Start <quickstart>`.
|
||||
@@ -0,0 +1,55 @@
|
||||
.. _intro:
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
This part of the documentation covers all the interfaces of Tablib.
|
||||
Tablib is a format-agnostic tabular dataset library, written in Python. It allows you to Pythonically import, export, and manipulate tabular data sets. Advanced features include, segregation, dynamic columns, tags / filtering, and seamless format import/exmport.
|
||||
|
||||
|
||||
Philosphy
|
||||
---------
|
||||
|
||||
Tablib was developed with a few :pep:`20` idioms in mind.
|
||||
|
||||
|
||||
#. Beautiful is better than ugly.
|
||||
#. Explicit is better than implicit.
|
||||
#. Simple is better than complex.
|
||||
#. Complex is better than complicated.
|
||||
#. Readability counts.
|
||||
|
||||
All contributions to Tablib should keep these important rules in mind.
|
||||
|
||||
.. _mit:
|
||||
|
||||
MIT License
|
||||
-----------
|
||||
|
||||
A large number of open source projects you find today are `GPL Licensed`_. While the GPL certianly has essential applications, it should most certianly not be your go-to license for your next open source project.
|
||||
|
||||
A project that is released as GPL cannot be usd in any commercial product without the product itself also being offered as open source. The MIT and BSD licenses are great alternatives to the GPL that allow your open-source software to be used in proprietary, closed-source software.
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
.. _pythonsupport:
|
||||
|
||||
Pythons Supported
|
||||
-----------------
|
||||
|
||||
At this time, the following Python platforms are officially supported:
|
||||
|
||||
* Python 2.6
|
||||
* Python 2.7
|
||||
|
||||
Support for other Pythons will be rolled out soon.
|
||||
|
||||
|
||||
|
||||
|
||||
Now, go :ref:`Install Tablib <install>`.
|
||||
@@ -0,0 +1,353 @@
|
||||
.. _quickstart:
|
||||
|
||||
==========
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
|
||||
.. module:: tablib
|
||||
|
||||
|
||||
Eager to get started? This page gives a good introduction in how to get started with Tablib. This assumes you already have Tablib installed. If you do not, head over to the :ref:`Installation <install>` section.
|
||||
|
||||
First, make sure that:
|
||||
|
||||
* Tablib is :ref:`installed <install>`
|
||||
* Tablib is :ref:`up-to-date <updates>`
|
||||
|
||||
|
||||
Lets gets started with some simple use cases and examples.
|
||||
|
||||
|
||||
|
||||
------------------
|
||||
Creating a Dataset
|
||||
------------------
|
||||
|
||||
|
||||
A :class:`Dataset <tablib.Dataset>` is nothing more than what its name implies—a set of data.
|
||||
|
||||
Creating your own instance of the :class:`tablib.Dataset` object is simple. ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
|
||||
You can now start filling this :class:`Dataset <tablib.Dataset>` object with data.
|
||||
|
||||
.. admonition:: Example Context
|
||||
|
||||
From here on out, if you see ``data``, assume that it's a fresh :class:`Dataset <tablib.Dataset>` object.
|
||||
|
||||
|
||||
|
||||
-----------
|
||||
Adding Rows
|
||||
-----------
|
||||
|
||||
|
||||
Let's say you want to collect a simple list of names. ::
|
||||
|
||||
# collection of names
|
||||
names = ['Kenneth Reitz', 'Bessie Monke']
|
||||
|
||||
for name in names:
|
||||
# split name appropriately
|
||||
fname, lname = name.split()
|
||||
|
||||
# add names to Dataset
|
||||
data.append([fname, lname])
|
||||
|
||||
You can get a nice, Pythonic view of the dataset at any time with :class:`Dataset.dict`.
|
||||
|
||||
>>> data.dict
|
||||
[('Kenneth', 'Reitz'), ('Bessie', 'Monke')]
|
||||
|
||||
|
||||
|
||||
--------------
|
||||
Adding Headers
|
||||
--------------
|
||||
|
||||
|
||||
It's time enhance our :class:`Dataset` by giving our columns some titles. To do so, set :class:`Dataset.headers`. ::
|
||||
|
||||
data.headers = ['First Name', 'Last Name']
|
||||
|
||||
Now our data looks a little different. ::
|
||||
|
||||
>>> data.dict
|
||||
[{'Last Name': 'Reitz', 'First Name': 'Kenneth'}, {'Last Name': 'Monke', 'First Name': 'Bessie'}]
|
||||
|
||||
|
||||
|
||||
|
||||
--------------
|
||||
Adding Columns
|
||||
--------------
|
||||
|
||||
|
||||
Now that we have a basic :class:`Dataset` in place, let's add a column of **ages** to it. ::
|
||||
|
||||
data.append(col=[22, 20], header='Age')
|
||||
|
||||
Let's view the data now. ::
|
||||
|
||||
>>> data.dict
|
||||
[{'Last Name': 'Reitz', 'First Name': 'Kenneth', 'Age': 22}, {'Last Name': 'Monke', 'First Name': 'Bessie', 'Age': 20}]
|
||||
|
||||
It's that easy.
|
||||
|
||||
|
||||
--------------
|
||||
Exporting Data
|
||||
--------------
|
||||
|
||||
Tablib's killer feature is the ability to export your :class:`Dataset` objects into a number of formats.
|
||||
|
||||
**Comma-Seperated Values** ::
|
||||
|
||||
>>> data.csv
|
||||
Last Name,First Name,Age
|
||||
Reitz,Kenneth,22
|
||||
Monke,Bessie,20
|
||||
|
||||
**JavaScript Object Notation** ::
|
||||
|
||||
>>> data.json
|
||||
[{"Last Name": "Reitz", "First Name": "Kenneth", "Age": 22}, {"Last Name": "Monke", "First Name": "Bessie", "Age": 20}]
|
||||
|
||||
|
||||
**YAML Ain't Markup Language** ::
|
||||
|
||||
>>> data.yaml
|
||||
- {Age: 22, First Name: Kenneth, Last Name: Reitz}
|
||||
- {Age: 20, First Name: Bessie, Last Name: Monke}
|
||||
|
||||
|
||||
**Microsoft Excel** ::
|
||||
|
||||
>>> data.xls
|
||||
<censored binary data>
|
||||
|
||||
|
||||
------------------------
|
||||
Selecting Rows & Columns
|
||||
------------------------
|
||||
|
||||
|
||||
You can slice and dice your data, just like a standard Python list. ::
|
||||
|
||||
>>> data[0]
|
||||
('Kenneth', 'Reitz', 22)
|
||||
|
||||
|
||||
If we had a set of data consisting of thousands of rows, it could be useful to get a list of values in a column.
|
||||
To do so, we access the :class:`Dataset` as if it were a standard Python dictionary. ::
|
||||
|
||||
>>> data['First Name']
|
||||
['Kenneth', 'Bessie']
|
||||
|
||||
Let's find the average age. ::
|
||||
|
||||
>>> ages = data['Age']
|
||||
>>> float(sum(ages)) / len(ages)
|
||||
21.0
|
||||
|
||||
|
||||
|
||||
-----------------------
|
||||
Removing Rows & Columns
|
||||
-----------------------
|
||||
|
||||
It's easier than you could imagine. ::
|
||||
|
||||
>>> del data['Col Name']
|
||||
|
||||
::
|
||||
|
||||
>>> del data[0:12]
|
||||
|
||||
|
||||
==============
|
||||
Advanced Usage
|
||||
==============
|
||||
|
||||
|
||||
This part of the documentation services to give you an idea that are otherwise hard to extract from the :ref:`API Documentation <api>`
|
||||
|
||||
And now for something completely different.
|
||||
|
||||
|
||||
.. _dyncols:
|
||||
|
||||
---------------
|
||||
Dynamic Columns
|
||||
---------------
|
||||
|
||||
.. versionadded:: 0.8.3
|
||||
|
||||
Thanks to Josh Ourisman, Tablib now supports adding dynamic columns. A dynamic column is a single callable object (*ie.* a function).
|
||||
|
||||
Let's add a dynamic column to our :class:`Dataset` object. In this example, we have a function that generates a random grade for our students. ::
|
||||
|
||||
import random
|
||||
|
||||
def random_grade(row):
|
||||
"""Returns a random integer for entry."""
|
||||
return (random.randint(60,100)/100.0)
|
||||
|
||||
data.append(col=[random_grade], header='Grade')
|
||||
|
||||
Let's have a look at our data. ::
|
||||
|
||||
>>> data.yaml
|
||||
- {Age: 22, First Name: Kenneth, Grade: 0.6, Last Name: Reitz}
|
||||
- {Age: 20, First Name: Bessie, Grade: 0.75, Last Name: Monke}
|
||||
|
||||
|
||||
Let's remove that column. ::
|
||||
|
||||
>>> del data['Grade']
|
||||
|
||||
|
||||
When you add a dynamic column, the first argument that is passed in to the given callable is the current data row. You can use this to perform calculations against your data row.
|
||||
|
||||
For example, we can use the data available in the row to guess the gender of a student. ::
|
||||
|
||||
def guess_gender(row):
|
||||
"""Calculates gender of given student data row."""
|
||||
m_names = ('Kenneth', 'Mike', 'Yuri')
|
||||
f_names = ('Bessie', 'Samantha', 'Heather')
|
||||
|
||||
name = row[0]
|
||||
|
||||
if name in m_names:
|
||||
return 'Male'
|
||||
elif name in f_names:
|
||||
return 'Female'
|
||||
else:
|
||||
return 'Unknown'
|
||||
|
||||
Adding this function to our dataset as a dynamic column would result in: ::
|
||||
|
||||
>>> data.yaml
|
||||
- {Age: 22, First Name: Kenneth, Gender: Male, Last Name: Reitz}
|
||||
- {Age: 20, First Name: Bessie, Gender: Female, Last Name: Monke}
|
||||
|
||||
|
||||
.. _tags:
|
||||
|
||||
----------------------------
|
||||
Filtering Datasets with Tags
|
||||
----------------------------
|
||||
|
||||
.. versionadded:: 0.9.0
|
||||
|
||||
|
||||
When constructing a :class:`Dataset` object, you can add tags to rows by specifying the ``tags`` parameter.
|
||||
This allows you to filter your :class:`Dataset` later. This can be useful so seperate rows of data based on
|
||||
arbitrary criteria (*e.g.* origin) that you don't want to include in your :class:`Dataset`.
|
||||
|
||||
Let's tag some students. ::
|
||||
|
||||
students = tablib.Dataset()
|
||||
|
||||
students.headers = ['first', 'last']
|
||||
|
||||
students.append(['Kenneth', 'Reitz'], tags=['male', 'technical'])
|
||||
students.append(['Bessie', 'Monke'], tags=['female', 'creative'])
|
||||
|
||||
Now that we have extra meta-data on our rows, we can use easily filter our :class:`Dataset`. Let's just see Male students. ::
|
||||
|
||||
|
||||
>>> data.filter(['male']).yaml
|
||||
- {first: Kenneth, Last: Reitz}
|
||||
|
||||
It's that simple. The original :class:`Dataset` is untouched.
|
||||
|
||||
|
||||
Excel Workbook With Multiple Sheets
|
||||
------------------------------------
|
||||
|
||||
When dealine with a large number of :class:`Datasets <Dataset>` in spreadsheet format, it's quite common to group mulitple spreadsheets into a single Excel file, known as a Workbook. Tablib makes it extremely easy to build webooks with the handy, :class:`Databook` class.
|
||||
|
||||
|
||||
Let's say we have 3 different :class:`Datasets <Dataset>`. All we have to do is add then to a :class:`Databook` object... ::
|
||||
|
||||
book = tablib.Databook([data1, data2, data3])
|
||||
|
||||
... and export to Excel just like :class:`Datasets <Dataset>`. ::
|
||||
|
||||
with open('students.xls', 'wb') as f:
|
||||
f.write(book.xls)
|
||||
|
||||
The resulting **students.xls** file will contain a seperate spreadsheet for each :class:`Dataset` object in the :class:`Databook`.
|
||||
|
||||
.. admonition:: Binary Warning
|
||||
|
||||
Make sure to open the output file in binary mode.
|
||||
|
||||
|
||||
.. _seperators:
|
||||
|
||||
----------
|
||||
Seperators
|
||||
----------
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
When, it's often useful to create a blank row containing information on the upcomming data. So,
|
||||
|
||||
|
||||
|
||||
::
|
||||
|
||||
daniel_tests = [
|
||||
('11/24/09', 'Math 101 Mid-term Exam', 56.),
|
||||
('05/24/10', 'Math 101 Final Exam', 62.)
|
||||
]
|
||||
|
||||
suzie_tests = [
|
||||
('11/24/09', 'Math 101 Mid-term Exam', 56.),
|
||||
('05/24/10', 'Math 101 Final Exam', 62.)
|
||||
]
|
||||
|
||||
# Create new dataset
|
||||
tests = tablib.Dataset()
|
||||
tests.headers = ['Date', 'Test Name', 'Grade']
|
||||
|
||||
# Daniel's Tests
|
||||
tests.append_seperator('Daniel\'s Scores')
|
||||
|
||||
for test_row in daniel_tests:
|
||||
tests.append(test_row)
|
||||
|
||||
# Susie's Tests
|
||||
tests.append_seperator('Susie\'s Scores')
|
||||
|
||||
for test_row in suzie_tests:
|
||||
tests.append(test_row)
|
||||
|
||||
# Write spreadsheet to disk
|
||||
with open('grades.xls', 'wb') as f:
|
||||
f.write(tests.xls)
|
||||
|
||||
The resulting **tests.xls** will have the following layout:
|
||||
|
||||
|
||||
Daniel's Scores:
|
||||
* '11/24/09', 'Math 101 Mid-term Exam', 56.
|
||||
* '05/24/10', 'Math 101 Final Exam', 62.
|
||||
|
||||
Suzie's Scores:
|
||||
* '11/24/09', 'Math 101 Mid-term Exam', 56.
|
||||
* '05/24/10', 'Math 101 Final Exam', 62.
|
||||
|
||||
|
||||
|
||||
.. admonition:: Format Support
|
||||
|
||||
At this time, only :class:`Excel <Dataset.xls>` output supports seperators.
|
||||
|
||||
----
|
||||
|
||||
Now, go check out the :ref:`API Documentation <api>` or begin :ref:`Tablib Development <development>`.
|
||||
Vendored
+11
-1
@@ -1,7 +1,17 @@
|
||||
import os
|
||||
from fabric.api import *
|
||||
|
||||
|
||||
def scrub():
|
||||
""" Death to the bytecode! """
|
||||
local("rm -fr dist build")
|
||||
local('rm -fr dist build')
|
||||
local("find . -name \"*.pyc\" -exec rm '{}' ';'")
|
||||
|
||||
def docs():
|
||||
"""Build docs."""
|
||||
os.system('make html')
|
||||
os.chdir('_build/html')
|
||||
os.system('sphinxtogithub .')
|
||||
os.system('git add -A')
|
||||
os.system('git commit -m \'documentation update\'')
|
||||
os.system('git push origin gh-pages')
|
||||
@@ -15,29 +15,36 @@ if sys.argv[-1] == "publish":
|
||||
publish()
|
||||
sys.exit()
|
||||
|
||||
required = []
|
||||
|
||||
setup(
|
||||
name='tablib',
|
||||
version='0.8.2',
|
||||
version='0.9.3',
|
||||
description='Format agnostic tabular data library (XLS, JSON, YAML, CSV)',
|
||||
long_description=open('README.rst').read() + '\n\n' +
|
||||
open('HISTORY.rst').read(),
|
||||
author='Kenneth Reitz',
|
||||
author_email='me@kennethreitz.com',
|
||||
url='http://github.com/kennethreitz/tablib',
|
||||
packages=['tablib', 'tablib.formats'],
|
||||
install_requires=['xlwt', 'simplejson', 'PyYAML'],
|
||||
url='http://tablib.org',
|
||||
packages= [
|
||||
'tablib', 'tablib.formats',
|
||||
'tablib.packages',
|
||||
'tablib.packages.simplejson',
|
||||
'tablib.packages.xlwt',
|
||||
'tablib.packages.yaml',
|
||||
],
|
||||
install_requires=required,
|
||||
license='MIT',
|
||||
classifiers=(
|
||||
'Development Status :: 4 - Beta',
|
||||
'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',
|
||||
),
|
||||
# entry_points={
|
||||
# 'console_scripts': [
|
||||
# 'tabbed = tablib.cli:start',
|
||||
# ],
|
||||
# }
|
||||
)
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
|
||||
from tablib.core import (
|
||||
Databook, Dataset, InvalidDatasetType,
|
||||
InvalidDimensions, UnsupportedFormat
|
||||
Databook, Dataset, detect, import_set,
|
||||
InvalidDatasetType, InvalidDimensions, UnsupportedFormat
|
||||
)
|
||||
|
||||
|
||||
+506
-75
@@ -1,26 +1,137 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tablib.core
|
||||
~~~~~~~~~~~
|
||||
|
||||
""" Tablib - Core Library.
|
||||
This module implements the central tablib objects.
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:license: MIT, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from tablib.formats import FORMATS as formats
|
||||
from copy import copy
|
||||
from operator import itemgetter
|
||||
|
||||
from tablib import formats
|
||||
|
||||
|
||||
__title__ = 'tablib'
|
||||
__version__ = '0.8.1'
|
||||
__build__ = 0x000801
|
||||
__version__ = '0.9.3'
|
||||
__build__ = 0x000903
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'MIT'
|
||||
__copyright__ = 'Copyright 2010 Kenneth Reitz'
|
||||
__copyright__ = 'Copyright 2011 Kenneth Reitz'
|
||||
|
||||
|
||||
class Row(object):
|
||||
"""Internal Row object. Mainly used for filtering."""
|
||||
|
||||
__slots__ = ['tuple', '_row', 'tags']
|
||||
|
||||
def __init__(self, row=list(), tags=list()):
|
||||
self._row = list(row)
|
||||
self.tags = list(tags)
|
||||
|
||||
def __iter__(self):
|
||||
return (col for col in self._row)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._row)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._row)
|
||||
|
||||
def __getslice__(self, i, j):
|
||||
return self._row[i,j]
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self._row[i]
|
||||
|
||||
def __setitem__(self, i, value):
|
||||
self._row[i] = value
|
||||
|
||||
def __delitem__(self, i):
|
||||
del self._row[i]
|
||||
|
||||
def __getstate__(self):
|
||||
result = dict()
|
||||
result['_row'] = self._row
|
||||
result['tags'] = self.tags
|
||||
|
||||
return result
|
||||
|
||||
def __setstate__(self, state):
|
||||
self._row = state['_row']
|
||||
self.tags = state['tags']
|
||||
|
||||
def append(self, value):
|
||||
self._row.append(value)
|
||||
|
||||
def insert(self, index, value):
|
||||
self._row.insert(index, value)
|
||||
|
||||
def __contains__(self, item):
|
||||
return (item in self._row)
|
||||
|
||||
@property
|
||||
def tuple(self):
|
||||
'''Tuple representation of :class:`Row`.'''
|
||||
return tuple(self._row)
|
||||
|
||||
@property
|
||||
def list(self):
|
||||
'''List representation of :class:`Row`.'''
|
||||
return list(self._row)
|
||||
|
||||
def has_tag(self, tag):
|
||||
"""Returns true if current row contains tag."""
|
||||
|
||||
if tag == None:
|
||||
return False
|
||||
elif isinstance(tag, basestring):
|
||||
return (tag in self.tags)
|
||||
else:
|
||||
return True if len(set(tag) & set(self.tags)) else False
|
||||
|
||||
|
||||
class Dataset(object):
|
||||
"""Epic Tabular-Dataset object. """
|
||||
"""The :class:`Dataset` object is the heart of Tablib. It provides all core
|
||||
functionality.
|
||||
|
||||
Usually you create a :class:`Dataset` instance in your main module, and append
|
||||
rows and columns as you collect data. ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.headers = ('name', 'age')
|
||||
|
||||
for (name, age) in some_collector():
|
||||
data.append((name, age))
|
||||
|
||||
You can also set rows and headers upon instantiation. This is useful if dealing
|
||||
with dozens or hundres of :class:`Dataset` objects. ::
|
||||
|
||||
headers = ('first_name', 'last_name')
|
||||
data = [('John', 'Adams'), ('George', 'Washington')]
|
||||
|
||||
data = tablib.Dataset(*data, headers=headers)
|
||||
|
||||
|
||||
:param \*args: (optional) list of rows to populate Dataset
|
||||
:param headers: (optional) list strings for Dataset header row
|
||||
|
||||
|
||||
.. admonition:: Format Attributes Definition
|
||||
|
||||
If you look at the code, the various output/import formats are not
|
||||
defined within the :class:`Dataset` object. To add support for a new format, see
|
||||
:ref:`Adding New Formats <newformats>`.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._data = list(args)
|
||||
self._data = list(Row(arg) for arg in args)
|
||||
self.__headers = None
|
||||
|
||||
|
||||
# ('title', index) tuples
|
||||
self._separators = []
|
||||
|
||||
@@ -36,7 +147,7 @@ class Dataset(object):
|
||||
|
||||
self._register_formats()
|
||||
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return self.height
|
||||
|
||||
@@ -49,16 +160,34 @@ class Dataset(object):
|
||||
else:
|
||||
raise KeyError
|
||||
else:
|
||||
return self._data[key]
|
||||
_results = self._data[key]
|
||||
if isinstance(_results, Row):
|
||||
return _results.tuple
|
||||
else:
|
||||
return [result.tuple for result in _results]
|
||||
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._validate(value)
|
||||
self._data[key] = tuple(value)
|
||||
self._data[key] = Row(value)
|
||||
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._data[key]
|
||||
if isinstance(key, basestring):
|
||||
|
||||
if key in self.headers:
|
||||
|
||||
pos = self.headers.index(key)
|
||||
del self.headers[pos]
|
||||
|
||||
for i, row in enumerate(self._data):
|
||||
|
||||
del row[pos]
|
||||
self._data[i] = row
|
||||
else:
|
||||
raise KeyError
|
||||
else:
|
||||
del self._data[key]
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
@@ -67,17 +196,17 @@ class Dataset(object):
|
||||
except AttributeError:
|
||||
return '<dataset object>'
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def _register_formats(cls):
|
||||
"""Adds format properties."""
|
||||
for fmt in formats:
|
||||
for fmt in formats.available:
|
||||
try:
|
||||
try:
|
||||
setattr(cls, fmt.title, property(fmt.export_set, fmt.import_set))
|
||||
except AttributeError:
|
||||
setattr(cls, fmt.title, property(fmt.export_set))
|
||||
|
||||
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
@@ -87,12 +216,12 @@ class Dataset(object):
|
||||
if row:
|
||||
is_valid = (len(row) == self.width) if self.width else True
|
||||
elif col:
|
||||
if self.headers:
|
||||
is_valid = (len(col) - 1) == self.height
|
||||
if len(col) < 1:
|
||||
is_valid = True
|
||||
else:
|
||||
is_valid = (len(col) == self.height) if self.height else True
|
||||
else:
|
||||
is_valid = all((len(x)== self.width for x in self._data))
|
||||
is_valid = all((len(x) == self.width for x in self._data))
|
||||
|
||||
if is_valid:
|
||||
return True
|
||||
@@ -115,16 +244,38 @@ class Dataset(object):
|
||||
|
||||
return data
|
||||
|
||||
|
||||
|
||||
def _clean_col(self, col):
|
||||
"""Prepares the given column for insert/append."""
|
||||
|
||||
col = list(col)
|
||||
|
||||
if self.headers:
|
||||
header = [col.pop(0)]
|
||||
else:
|
||||
header = []
|
||||
|
||||
if len(col) == 1 and callable(col[0]):
|
||||
col = map(col[0], self._data)
|
||||
col = tuple(header + col)
|
||||
|
||||
return col
|
||||
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
"""Returns the height of the Dataset."""
|
||||
"""The number of rows currently in the :class:`Dataset`.
|
||||
Cannot be directly modified.
|
||||
"""
|
||||
return len(self._data)
|
||||
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
"""Returns the width of the Dataset."""
|
||||
"""The number of columns currently in the :class:`Dataset`.
|
||||
Cannot be directly modified.
|
||||
"""
|
||||
|
||||
try:
|
||||
return len(self._data[0])
|
||||
except IndexError:
|
||||
@@ -136,7 +287,11 @@ class Dataset(object):
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
"""Headers property."""
|
||||
"""An *optional* list of strings to be used for header rows and attribute names.
|
||||
|
||||
This must be set manually. The given list length must equal :class:`Dataset.width`.
|
||||
|
||||
"""
|
||||
return self.__headers
|
||||
|
||||
|
||||
@@ -155,58 +310,150 @@ class Dataset(object):
|
||||
|
||||
@property
|
||||
def dict(self):
|
||||
"""Returns python dict of Dataset."""
|
||||
"""A JSON representation of the :class:`Dataset` object. If headers have been
|
||||
set, a JSON list of objects will be returned. If no headers have
|
||||
been set, a JSON list of lists (rows) will be returned instead.
|
||||
|
||||
A dataset object can also be imported by setting the `Dataset.json` attribute: ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.json = '[{"last_name": "Adams","age": 90,"first_name": "John"}]'
|
||||
|
||||
"""
|
||||
return self._package()
|
||||
|
||||
|
||||
|
||||
@dict.setter
|
||||
def dict(self, pickle):
|
||||
"""Returns python dict of Dataset."""
|
||||
"""A native Python representation of the Dataset object. If headers have been
|
||||
set, a list of Python dictionaries will be returned. If no headers have been
|
||||
set, a list of tuples (rows) will be returned instead.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.dict` attribute. ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.dict = [{'age': 90, 'first_name': 'Kenneth', 'last_name': 'Reitz'}]
|
||||
|
||||
"""
|
||||
if not len(pickle):
|
||||
return
|
||||
|
||||
# if list of rows
|
||||
if isinstance(pickle[0], list):
|
||||
self.wipe()
|
||||
for row in pickle:
|
||||
self.append(row)
|
||||
self.append(Row(row))
|
||||
|
||||
# if list of objects
|
||||
elif isinstance(pickle[0], dict):
|
||||
self.wipe()
|
||||
self.headers = pickle[0].keys()
|
||||
for row in pickle:
|
||||
self.append(row.values())
|
||||
self.append(Row(row.values()))
|
||||
else:
|
||||
raise UnsupportedFormat
|
||||
|
||||
@property
|
||||
def xls():
|
||||
"""An Excel Spreadsheet representation of the :class:`Dataset` object, with :ref:`seperators`. Cannot be set.
|
||||
|
||||
def append(self, row=None, col=None):
|
||||
"""Adds a row to the end of Dataset"""
|
||||
if row:
|
||||
self._validate(row)
|
||||
self._data.append(tuple(row))
|
||||
elif col:
|
||||
self._validate(col=col)
|
||||
.. admonition:: Binary Warning
|
||||
|
||||
if self.headers:
|
||||
# pop the first item off, add to headers
|
||||
self.headers.append(col[0])
|
||||
col = col[1:]
|
||||
|
||||
if self.height and self.width:
|
||||
:class:`Dataset.xls` contains binary data, so make sure to write in binary mode::
|
||||
|
||||
for i, row in enumerate(self._data):
|
||||
_row = list(row)
|
||||
_row.append(col[i])
|
||||
self._data[i] = tuple(_row)
|
||||
else:
|
||||
self._data = [tuple([row]) for row in col]
|
||||
with open('output.xls', 'wb') as f:
|
||||
f.write(data.xls)'
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@property
|
||||
def csv():
|
||||
"""A CSV representation of the :class:`Dataset` object. The top row will contain
|
||||
headers, if they have been set. Otherwise, the top row will contain
|
||||
the first row of the dataset.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.csv` attribute. ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.csv = 'age, first_name, last_name\\n90, John, Adams'
|
||||
|
||||
Import assumes (for now) that headers exist.
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
def tsv():
|
||||
"""A TSV representation of the :class:`Dataset` object. The top row will contain
|
||||
headers, if they have been set. Otherwise, the top row will contain
|
||||
the first row of the dataset.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.tsv` attribute. ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.tsv = 'age\tfirst_name\tlast_name\\n90\tJohn\tAdams'
|
||||
|
||||
Import assumes (for now) that headers exist.
|
||||
"""
|
||||
|
||||
@property
|
||||
def yaml():
|
||||
"""A YAML representation of the :class:`Dataset` object. If headers have been
|
||||
set, a YAML list of objects will be returned. If no headers have
|
||||
been set, a YAML list of lists (rows) will be returned instead.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.json` attribute: ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.yaml = '- {age: 90, first_name: John, last_name: Adams}'
|
||||
|
||||
Import assumes (for now) that headers exist.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@property
|
||||
def json():
|
||||
"""A JSON representation of the :class:`Dataset` object. If headers have been
|
||||
set, a JSON list of objects will be returned. If no headers have
|
||||
been set, a JSON list of lists (rows) will be returned instead.
|
||||
|
||||
A dataset object can also be imported by setting the :class:`Dataset.json` attribute: ::
|
||||
|
||||
data = tablib.Dataset()
|
||||
data.json = '[{age: 90, first_name: "John", liast_name: "Adams"}]'
|
||||
|
||||
Import assumes (for now) that headers exist.
|
||||
"""
|
||||
|
||||
@property
|
||||
def html():
|
||||
"""A HTML table representation of the :class:`Dataset` object. If
|
||||
headers have been set, they will be used as table headers.
|
||||
|
||||
..notice:: This method can be used for export only.
|
||||
"""
|
||||
pass
|
||||
|
||||
def append(self, row=None, col=None, header=None, tags=list()):
|
||||
"""Adds a row or column to the :class:`Dataset`.
|
||||
Usage is :class:`Dataset.insert` for documentation.
|
||||
"""
|
||||
|
||||
if row is not None:
|
||||
self.insert(self.height, row=row, tags=tags)
|
||||
elif col is not None:
|
||||
self.insert(self.width, col=col, header=header)
|
||||
|
||||
def insert_separator(self, index, text='-'):
|
||||
"""Adds a separator to Dataset at given index."""
|
||||
"""Adds a separator to :class:`Dataset` at given index."""
|
||||
|
||||
sep = (index, text)
|
||||
self._separators.append(sep)
|
||||
|
||||
|
||||
def append_separator(self, text='-'):
|
||||
"""Adds a separator to Dataset."""
|
||||
"""Adds a :ref:`seperator <seperators>` to the :class:`Dataset`."""
|
||||
|
||||
# change offsets if headers are or aren't defined
|
||||
if not self.headers:
|
||||
@@ -217,30 +464,212 @@ class Dataset(object):
|
||||
self.insert_separator(index, text)
|
||||
|
||||
|
||||
def insert(self, i, row=None):
|
||||
"""Inserts a row at given position in Dataset"""
|
||||
def insert(self, index, row=None, col=None, header=None, tags=list()):
|
||||
"""Inserts a row or column to the :class:`Dataset` at the given index.
|
||||
|
||||
Rows and columns inserted must be the correct size (height or width).
|
||||
|
||||
The default behaviour is to insert the given row to the :class:`Dataset`
|
||||
object at the given index. If the ``col`` parameter is given, however,
|
||||
a new column will be insert to the :class:`Dataset` object instead.
|
||||
|
||||
You can also insert a column of a single callable object, which will
|
||||
add a new column with the return values of the callable each as an
|
||||
item in the column. ::
|
||||
|
||||
data.append(col=random.randint)
|
||||
|
||||
See :ref:`dyncols` for an in-depth example.
|
||||
|
||||
.. versionchanged:: 0.9.0
|
||||
If inserting a column, and :class:`Dataset.headers` is set, the
|
||||
header attribute must be set, and will be considered the header for
|
||||
that row.
|
||||
|
||||
.. versionadded:: 0.9.0
|
||||
If inserting a row, you can add :ref:`tags <tags>` to the row you are inserting.
|
||||
This gives you the ability to :class:`filter <Dataset.filter>` your
|
||||
:class:`Dataset` later.
|
||||
|
||||
"""
|
||||
if row:
|
||||
self._validate(row)
|
||||
self._data.insert(i, tuple(row))
|
||||
self._data.insert(index, Row(row, tags=tags))
|
||||
elif col:
|
||||
pass
|
||||
|
||||
|
||||
col = list(col)
|
||||
|
||||
# Callable Columns...
|
||||
if len(col) == 1 and callable(col[0]):
|
||||
col = map(col[0], self._data)
|
||||
|
||||
col = self._clean_col(col)
|
||||
self._validate(col=col)
|
||||
|
||||
if self.headers:
|
||||
# pop the first item off, add to headers
|
||||
if not header:
|
||||
raise HeadersNeeded()
|
||||
self.headers.insert(index, header)
|
||||
|
||||
if self.height and self.width:
|
||||
|
||||
for i, row in enumerate(self._data):
|
||||
|
||||
row.insert(index, col[i])
|
||||
self._data[i] = row
|
||||
else:
|
||||
self._data = [Row([row]) for row in col]
|
||||
|
||||
|
||||
def filter(self, tag):
|
||||
"""Returns a new instance of the :class:`Dataset`, excluding any rows
|
||||
that do not contain the given :ref:`tags <tags>`.
|
||||
"""
|
||||
_dset = copy(self)
|
||||
_dset._data = [row for row in _dset._data if row.has_tag(tag)]
|
||||
|
||||
return _dset
|
||||
|
||||
|
||||
def sort(self, col, reverse=False):
|
||||
"""Sort a :class:`Dataset` by a specific column, given string (for
|
||||
header) or integer (for column index). The order can be reversed by
|
||||
setting ``reverse`` to ``True``.
|
||||
Returns a new :class:`Dataset` instance where columns have been
|
||||
sorted."""
|
||||
|
||||
if isinstance(col, basestring):
|
||||
|
||||
if not self.headers:
|
||||
raise HeadersNeeded
|
||||
|
||||
_sorted = sorted(self.dict, key=itemgetter(col), reverse=reverse)
|
||||
_dset = Dataset(headers=self.headers)
|
||||
|
||||
for item in _sorted:
|
||||
row = [item[key] for key in self.headers]
|
||||
_dset.append(row=row)
|
||||
|
||||
else:
|
||||
if self.headers:
|
||||
col = self.headers[col]
|
||||
|
||||
_sorted = sorted(self.dict, key=itemgetter(col), reverse=reverse)
|
||||
_dset = Dataset(headers=self.headers)
|
||||
|
||||
for item in _sorted:
|
||||
if self.headers:
|
||||
row = [item[key] for key in self.headers]
|
||||
else:
|
||||
row = item
|
||||
_dset.append(row=row)
|
||||
|
||||
|
||||
return _dset
|
||||
|
||||
def transpose(self):
|
||||
"""Transpose a :class:`Dataset`, turning rows into columns and vice
|
||||
versa, returning a new ``Dataset`` instance. The first row of the
|
||||
original instance becomes the new header row."""
|
||||
|
||||
# Don't transpose if there is no data
|
||||
if not self:
|
||||
return
|
||||
|
||||
_dset = Dataset()
|
||||
# The first element of the headers stays in the headers,
|
||||
# it is our "hinge" on which we rotate the data
|
||||
new_headers = [self.headers[0]] + self[self.headers[0]]
|
||||
|
||||
_dset.headers = new_headers
|
||||
for column in self.headers:
|
||||
|
||||
if column == self.headers[0]:
|
||||
# It's in the headers, so skip it
|
||||
continue
|
||||
|
||||
# Adding the column name as now they're a regular column
|
||||
row_data = [column] + self[column]
|
||||
row_data = Row(row_data)
|
||||
_dset.append(row=row_data)
|
||||
|
||||
return _dset
|
||||
|
||||
|
||||
def stack_rows(self, other):
|
||||
"""Stack two :class:`Dataset` instances together by
|
||||
joining at the row level, and return new combined
|
||||
``Dataset`` instance."""
|
||||
|
||||
if not isinstance(other, Dataset):
|
||||
return
|
||||
|
||||
if self.width != other.width:
|
||||
raise InvalidDimensions
|
||||
|
||||
# Copy the source data
|
||||
_dset = copy(self)
|
||||
|
||||
rows_to_stack = [row for row in _dset._data]
|
||||
other_rows = [row for row in other._data]
|
||||
|
||||
rows_to_stack.extend(other_rows)
|
||||
_dset._data = rows_to_stack
|
||||
|
||||
return _dset
|
||||
|
||||
|
||||
def stack_columns(self, other):
|
||||
"""Stack two :class:`Dataset` instances together by
|
||||
joining at the column level, and return a new
|
||||
combined ``Dataset`` instance. If either ``Dataset``
|
||||
has headers set, than the other must as well."""
|
||||
|
||||
if not isinstance(other, Dataset):
|
||||
return
|
||||
|
||||
if self.headers or other.headers:
|
||||
if not self.headers or not other.headers:
|
||||
raise HeadersNeeded
|
||||
|
||||
if self.height != other.height:
|
||||
raise InvalidDimensions
|
||||
|
||||
try:
|
||||
new_headers = self.headers + other.headers
|
||||
except TypeError:
|
||||
new_headers = None
|
||||
|
||||
_dset = Dataset()
|
||||
|
||||
for column in self.headers:
|
||||
_dset.append(col=self[column])
|
||||
|
||||
for column in other.headers:
|
||||
_dset.append(col=other[column])
|
||||
|
||||
_dset.headers = new_headers
|
||||
|
||||
return _dset
|
||||
|
||||
def wipe(self):
|
||||
"""Erases all data from Dataset."""
|
||||
"""Removes all content and headers from the :class:`Dataset` object."""
|
||||
self._data = list()
|
||||
self.__headers = None
|
||||
|
||||
|
||||
class Databook(object):
|
||||
"""A book of Dataset objects.
|
||||
Currently, this exists only for XLS workbook support.
|
||||
"""A book of :class:`Dataset` objects.
|
||||
"""
|
||||
|
||||
def __init__(self, sets=[]):
|
||||
self._datasets = sets
|
||||
self._register_formats()
|
||||
def __init__(self, sets=None):
|
||||
|
||||
if sets is None:
|
||||
self._datasets = list()
|
||||
else:
|
||||
self._datasets = sets
|
||||
|
||||
self._register_formats()
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
@@ -250,34 +679,34 @@ class Databook(object):
|
||||
|
||||
|
||||
def wipe(self):
|
||||
"""Wipe book clean."""
|
||||
"""Removes all :class:`Dataset` objects from the :class:`Databook`."""
|
||||
self._datasets = []
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def _register_formats(cls):
|
||||
"""Adds format properties."""
|
||||
for fmt in formats:
|
||||
for fmt in formats.available:
|
||||
try:
|
||||
try:
|
||||
setattr(cls, fmt.title, property(fmt.export_book, fmt.import_book))
|
||||
except AttributeError:
|
||||
setattr(cls, fmt.title, property(fmt.export_book))
|
||||
|
||||
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
def add_sheet(self, dataset):
|
||||
"""Adds given dataset."""
|
||||
"""Adds given :class:`Dataset` to the :class:`Databook`."""
|
||||
if type(dataset) is Dataset:
|
||||
self._datasets.append(dataset)
|
||||
else:
|
||||
raise InvalidDatasetType
|
||||
|
||||
|
||||
|
||||
def _package(self):
|
||||
"""Packages Databook for delivery."""
|
||||
"""Packages :class:`Databook` for delivery."""
|
||||
collector = []
|
||||
for dset in self._datasets:
|
||||
collector.append(dict(
|
||||
@@ -289,21 +718,21 @@ class Databook(object):
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
"""The number of the Datasets within DataBook."""
|
||||
"""The number of the :class:`Dataset` objects within :class:`Databook`."""
|
||||
return len(self._datasets)
|
||||
|
||||
|
||||
def detect(stream):
|
||||
"""Return (format, stream) of given stream."""
|
||||
for fmt in formats:
|
||||
for fmt in formats.available:
|
||||
try:
|
||||
if fmt.detect(stream):
|
||||
return (fmt, stream)
|
||||
return (fmt, stream)
|
||||
except AttributeError:
|
||||
pass
|
||||
pass
|
||||
return (None, stream)
|
||||
|
||||
|
||||
|
||||
|
||||
def import_set(stream):
|
||||
"""Return dataset of given stream."""
|
||||
(format, stream) = detect(stream)
|
||||
@@ -312,7 +741,7 @@ def import_set(stream):
|
||||
data = Dataset()
|
||||
format.import_set(data, stream)
|
||||
return data
|
||||
|
||||
|
||||
except AttributeError, e:
|
||||
return None
|
||||
|
||||
@@ -324,6 +753,8 @@ class InvalidDatasetType(Exception):
|
||||
class InvalidDimensions(Exception):
|
||||
"Invalid size"
|
||||
|
||||
|
||||
class HeadersNeeded(Exception):
|
||||
"Header parameter must be given when appending a column in this Dataset."
|
||||
|
||||
class UnsupportedFormat(NotImplementedError):
|
||||
"Format is not supported"
|
||||
|
||||
@@ -7,5 +7,7 @@ import _csv as csv
|
||||
import _json as json
|
||||
import _xls as xls
|
||||
import _yaml as yaml
|
||||
import _tsv as tsv
|
||||
import _html as html
|
||||
|
||||
FORMATS = (csv, json, xls, yaml)
|
||||
available = (json, xls, yaml, csv, tsv, html)
|
||||
|
||||
@@ -7,8 +7,6 @@ import cStringIO
|
||||
import csv
|
||||
import os
|
||||
|
||||
import simplejson as json
|
||||
|
||||
import tablib
|
||||
|
||||
|
||||
@@ -40,3 +38,12 @@ def import_set(dset, in_stream, headers=True):
|
||||
dset.headers = row
|
||||
else:
|
||||
dset.append(row)
|
||||
|
||||
|
||||
def detect(stream):
|
||||
"""Returns True if given stream is valid CSV."""
|
||||
try:
|
||||
rows = dialect = csv.Sniffer().sniff(stream)
|
||||
return True
|
||||
except csv.Error:
|
||||
return False
|
||||
@@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - HTML export support.
|
||||
"""
|
||||
|
||||
from StringIO 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()
|
||||
+20
-3
@@ -3,9 +3,17 @@
|
||||
""" Tablib - JSON Support
|
||||
"""
|
||||
|
||||
import simplejson as json
|
||||
try:
|
||||
import json # load system JSON (Python >= 2.6)
|
||||
except ImportError:
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import tablib.packages.simplejson as json # use the vendorized copy
|
||||
|
||||
import tablib.core
|
||||
|
||||
|
||||
title = 'json'
|
||||
extentions = ('json', 'jsn')
|
||||
|
||||
@@ -18,11 +26,11 @@ def export_set(dataset):
|
||||
def export_book(databook):
|
||||
"""Returns JSON representation of Databook."""
|
||||
return json.dumps(databook._package())
|
||||
|
||||
|
||||
|
||||
def import_set(dset, in_stream):
|
||||
"""Returns dataset from JSON stream."""
|
||||
|
||||
|
||||
dset.wipe()
|
||||
dset.dict = json.loads(in_stream)
|
||||
|
||||
@@ -36,3 +44,12 @@ def import_book(dbook, in_stream):
|
||||
data.title = sheet['title']
|
||||
data.dict = sheet['data']
|
||||
dbook.add_sheet(data)
|
||||
|
||||
|
||||
def detect(stream):
|
||||
"""Returns True if given stream is valid JSON."""
|
||||
try:
|
||||
json.loads(stream)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - TSV (Tab Separated Values) Support.
|
||||
"""
|
||||
|
||||
import cStringIO
|
||||
import csv
|
||||
import os
|
||||
|
||||
import tablib
|
||||
|
||||
|
||||
title = 'tsv'
|
||||
extentions = ('tsv',)
|
||||
|
||||
|
||||
|
||||
def export_set(dataset):
|
||||
"""Returns a TSV representation of Dataset."""
|
||||
stream = cStringIO.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
|
||||
+22
-4
@@ -3,15 +3,22 @@
|
||||
""" Tablib - XLS Support.
|
||||
"""
|
||||
|
||||
import xlwt
|
||||
import cStringIO
|
||||
|
||||
try:
|
||||
import xlwt
|
||||
except ImportError:
|
||||
import tablib.packages.xlwt as xlwt
|
||||
|
||||
|
||||
title = 'xls'
|
||||
extentions = ('xls',)
|
||||
|
||||
# special styles
|
||||
wrap = xlwt.easyxf("alignment: wrap on")
|
||||
bold = xlwt.easyxf("font: bold on")
|
||||
|
||||
|
||||
def export_set(dataset):
|
||||
"""Returns XLS representation of Dataset."""
|
||||
|
||||
@@ -19,7 +26,7 @@ def export_set(dataset):
|
||||
ws = wb.add_sheet(dataset.title if dataset.title else 'Tabbed Dataset')
|
||||
|
||||
dset_sheet(dataset, ws)
|
||||
|
||||
|
||||
stream = cStringIO.StringIO()
|
||||
wb.save(stream)
|
||||
return stream.getvalue()
|
||||
@@ -56,12 +63,23 @@ def dset_sheet(dataset, ws):
|
||||
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:
|
||||
ws.write(i, j, col, wrap)
|
||||
try:
|
||||
if '\n' in col:
|
||||
ws.write(i, j, col, wrap)
|
||||
else:
|
||||
ws.write(i, j, col)
|
||||
except TypeError:
|
||||
ws.write(i, j, col)
|
||||
|
||||
|
||||
|
||||
+17
-2
@@ -3,7 +3,11 @@
|
||||
""" Tablib - YAML Support.
|
||||
"""
|
||||
|
||||
import yaml
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
import tablib.packages.yaml as yaml
|
||||
|
||||
import tablib
|
||||
|
||||
|
||||
@@ -39,4 +43,15 @@ def import_book(dbook, in_stream):
|
||||
data = tablib.core.Dataset()
|
||||
data.title = sheet['title']
|
||||
data.dict = sheet['data']
|
||||
dbook.add_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
|
||||
@@ -0,0 +1,484 @@
|
||||
# This code is in the public domain, it comes
|
||||
# with absolutely no warranty and you can do
|
||||
# absolutely whatever you want with it.
|
||||
|
||||
__date__ = '17 May 2007'
|
||||
__version__ = '1.7'
|
||||
__doc__= """
|
||||
This is markup.py - a Python module that attempts to
|
||||
make it easier to generate HTML/XML from a Python program
|
||||
in an intuitive, lightweight, customizable and pythonic way.
|
||||
|
||||
The code is in the public domain.
|
||||
|
||||
Version: %s as of %s.
|
||||
|
||||
Documentation and further info is at http://markup.sourceforge.net/
|
||||
|
||||
Please send bug reports, feature requests, enhancement
|
||||
ideas or questions to nogradi at gmail dot com.
|
||||
|
||||
Installation: drop markup.py somewhere into your Python path.
|
||||
""" % ( __version__, __date__ )
|
||||
|
||||
import string
|
||||
|
||||
class element:
|
||||
"""This class handles the addition of a new element."""
|
||||
|
||||
def __init__( self, tag, case='lower', parent=None ):
|
||||
self.parent = parent
|
||||
|
||||
if case == 'lower':
|
||||
self.tag = tag.lower( )
|
||||
else:
|
||||
self.tag = tag.upper( )
|
||||
|
||||
def __call__( self, *args, **kwargs ):
|
||||
if len( args ) > 1:
|
||||
raise ArgumentError( self.tag )
|
||||
|
||||
# if class_ was defined in parent it should be added to every element
|
||||
if self.parent is not None and self.parent.class_ is not None:
|
||||
if 'class_' not in kwargs:
|
||||
kwargs['class_'] = self.parent.class_
|
||||
|
||||
if self.parent is None and len( args ) == 1:
|
||||
x = [ self.render( self.tag, False, myarg, mydict ) for myarg, mydict in _argsdicts( args, kwargs ) ]
|
||||
return '\n'.join( x )
|
||||
elif self.parent is None and len( args ) == 0:
|
||||
x = [ self.render( self.tag, True, myarg, mydict ) for myarg, mydict in _argsdicts( args, kwargs ) ]
|
||||
return '\n'.join( x )
|
||||
|
||||
if self.tag in self.parent.twotags:
|
||||
for myarg, mydict in _argsdicts( args, kwargs ):
|
||||
self.render( self.tag, False, myarg, mydict )
|
||||
elif self.tag in self.parent.onetags:
|
||||
if len( args ) == 0:
|
||||
for myarg, mydict in _argsdicts( args, kwargs ):
|
||||
self.render( self.tag, True, myarg, mydict ) # here myarg is always None, because len( args ) = 0
|
||||
else:
|
||||
raise ClosingError( self.tag )
|
||||
elif self.parent.mode == 'strict_html' and self.tag in self.parent.deptags:
|
||||
raise DeprecationError( self.tag )
|
||||
else:
|
||||
raise InvalidElementError( self.tag, self.parent.mode )
|
||||
|
||||
def render( self, tag, single, between, kwargs ):
|
||||
"""Append the actual tags to content."""
|
||||
|
||||
out = "<%s" % tag
|
||||
for key, value in kwargs.iteritems( ):
|
||||
if value is not None: # when value is None that means stuff like <... checked>
|
||||
key = key.strip('_') # strip this so class_ will mean class, etc.
|
||||
if key == 'http_equiv': # special cases, maybe change _ to - overall?
|
||||
key = 'http-equiv'
|
||||
elif key == 'accept_charset':
|
||||
key = 'accept-charset'
|
||||
out = "%s %s=\"%s\"" % ( out, key, escape( value ) )
|
||||
else:
|
||||
out = "%s %s" % ( out, key )
|
||||
if between is not None:
|
||||
out = "%s>%s</%s>" % ( out, between, tag )
|
||||
else:
|
||||
if single:
|
||||
out = "%s />" % out
|
||||
else:
|
||||
out = "%s>" % out
|
||||
if self.parent is not None:
|
||||
self.parent.content.append( out )
|
||||
else:
|
||||
return out
|
||||
|
||||
def close( self ):
|
||||
"""Append a closing tag unless element has only opening tag."""
|
||||
|
||||
if self.tag in self.parent.twotags:
|
||||
self.parent.content.append( "</%s>" % self.tag )
|
||||
elif self.tag in self.parent.onetags:
|
||||
raise ClosingError( self.tag )
|
||||
elif self.parent.mode == 'strict_html' and self.tag in self.parent.deptags:
|
||||
raise DeprecationError( self.tag )
|
||||
|
||||
def open( self, **kwargs ):
|
||||
"""Append an opening tag."""
|
||||
|
||||
if self.tag in self.parent.twotags or self.tag in self.parent.onetags:
|
||||
self.render( self.tag, False, None, kwargs )
|
||||
elif self.mode == 'strict_html' and self.tag in self.parent.deptags:
|
||||
raise DeprecationError( self.tag )
|
||||
|
||||
class page:
|
||||
"""This is our main class representing a document. Elements are added
|
||||
as attributes of an instance of this class."""
|
||||
|
||||
def __init__( self, mode='strict_html', case='lower', onetags=None, twotags=None, separator='\n', class_=None ):
|
||||
"""Stuff that effects the whole document.
|
||||
|
||||
mode -- 'strict_html' for HTML 4.01 (default)
|
||||
'html' alias for 'strict_html'
|
||||
'loose_html' to allow some deprecated elements
|
||||
'xml' to allow arbitrary elements
|
||||
|
||||
case -- 'lower' element names will be printed in lower case (default)
|
||||
'upper' they will be printed in upper case
|
||||
|
||||
onetags -- list or tuple of valid elements with opening tags only
|
||||
twotags -- list or tuple of valid elements with both opening and closing tags
|
||||
these two keyword arguments may be used to select
|
||||
the set of valid elements in 'xml' mode
|
||||
invalid elements will raise appropriate exceptions
|
||||
|
||||
separator -- string to place between added elements, defaults to newline
|
||||
|
||||
class_ -- a class that will be added to every element if defined"""
|
||||
|
||||
valid_onetags = [ "AREA", "BASE", "BR", "COL", "FRAME", "HR", "IMG", "INPUT", "LINK", "META", "PARAM" ]
|
||||
valid_twotags = [ "A", "ABBR", "ACRONYM", "ADDRESS", "B", "BDO", "BIG", "BLOCKQUOTE", "BODY", "BUTTON",
|
||||
"CAPTION", "CITE", "CODE", "COLGROUP", "DD", "DEL", "DFN", "DIV", "DL", "DT", "EM", "FIELDSET",
|
||||
"FORM", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEAD", "HTML", "I", "IFRAME", "INS",
|
||||
"KBD", "LABEL", "LEGEND", "LI", "MAP", "NOFRAMES", "NOSCRIPT", "OBJECT", "OL", "OPTGROUP",
|
||||
"OPTION", "P", "PRE", "Q", "SAMP", "SCRIPT", "SELECT", "SMALL", "SPAN", "STRONG", "STYLE",
|
||||
"SUB", "SUP", "TABLE", "TBODY", "TD", "TEXTAREA", "TFOOT", "TH", "THEAD", "TITLE", "TR",
|
||||
"TT", "UL", "VAR" ]
|
||||
deprecated_onetags = [ "BASEFONT", "ISINDEX" ]
|
||||
deprecated_twotags = [ "APPLET", "CENTER", "DIR", "FONT", "MENU", "S", "STRIKE", "U" ]
|
||||
|
||||
self.header = [ ]
|
||||
self.content = [ ]
|
||||
self.footer = [ ]
|
||||
self.case = case
|
||||
self.separator = separator
|
||||
|
||||
# init( ) sets it to True so we know that </body></html> has to be printed at the end
|
||||
self._full = False
|
||||
self.class_= class_
|
||||
|
||||
if mode == 'strict_html' or mode == 'html':
|
||||
self.onetags = valid_onetags
|
||||
self.onetags += map( string.lower, self.onetags )
|
||||
self.twotags = valid_twotags
|
||||
self.twotags += map( string.lower, self.twotags )
|
||||
self.deptags = deprecated_onetags + deprecated_twotags
|
||||
self.deptags += map( string.lower, self.deptags )
|
||||
self.mode = 'strict_html'
|
||||
elif mode == 'loose_html':
|
||||
self.onetags = valid_onetags + deprecated_onetags
|
||||
self.onetags += map( string.lower, self.onetags )
|
||||
self.twotags = valid_twotags + deprecated_twotags
|
||||
self.twotags += map( string.lower, self.twotags )
|
||||
self.mode = mode
|
||||
elif mode == 'xml':
|
||||
if onetags and twotags:
|
||||
self.onetags = onetags
|
||||
self.twotags = twotags
|
||||
elif ( onetags and not twotags ) or ( twotags and not onetags ):
|
||||
raise CustomizationError( )
|
||||
else:
|
||||
self.onetags = russell( )
|
||||
self.twotags = russell( )
|
||||
self.mode = mode
|
||||
else:
|
||||
raise ModeError( mode )
|
||||
|
||||
def __getattr__( self, attr ):
|
||||
if attr.startswith("__") and attr.endswith("__"):
|
||||
raise AttributeError, attr
|
||||
return element( attr, case=self.case, parent=self )
|
||||
|
||||
def __str__( self ):
|
||||
|
||||
if self._full and ( self.mode == 'strict_html' or self.mode == 'loose_html' ):
|
||||
end = [ '</body>', '</html>' ]
|
||||
else:
|
||||
end = [ ]
|
||||
|
||||
return self.separator.join( self.header + self.content + self.footer + end )
|
||||
|
||||
def __call__( self, escape=False ):
|
||||
"""Return the document as a string.
|
||||
|
||||
escape -- False print normally
|
||||
True replace < and > by < and >
|
||||
the default escape sequences in most browsers"""
|
||||
|
||||
if escape:
|
||||
return _escape( self.__str__( ) )
|
||||
else:
|
||||
return self.__str__( )
|
||||
|
||||
def add( self, text ):
|
||||
"""This is an alias to addcontent."""
|
||||
self.addcontent( text )
|
||||
|
||||
def addfooter( self, text ):
|
||||
"""Add some text to the bottom of the document"""
|
||||
self.footer.append( text )
|
||||
|
||||
def addheader( self, text ):
|
||||
"""Add some text to the top of the document"""
|
||||
self.header.append( text )
|
||||
|
||||
def addcontent( self, text ):
|
||||
"""Add some text to the main part of the document"""
|
||||
self.content.append( text )
|
||||
|
||||
|
||||
def init( self, lang='en', css=None, metainfo=None, title=None, header=None,
|
||||
footer=None, charset=None, encoding=None, doctype=None, bodyattrs=None, script=None ):
|
||||
"""This method is used for complete documents with appropriate
|
||||
doctype, encoding, title, etc information. For an HTML/XML snippet
|
||||
omit this method.
|
||||
|
||||
lang -- language, usually a two character string, will appear
|
||||
as <html lang='en'> in html mode (ignored in xml mode)
|
||||
|
||||
css -- Cascading Style Sheet filename as a string or a list of
|
||||
strings for multiple css files (ignored in xml mode)
|
||||
|
||||
metainfo -- a dictionary in the form { 'name':'content' } to be inserted
|
||||
into meta element(s) as <meta name='name' content='content'>
|
||||
(ignored in xml mode)
|
||||
|
||||
bodyattrs --a dictionary in the form { 'key':'value', ... } which will be added
|
||||
as attributes of the <body> element as <body key='value' ... >
|
||||
(ignored in xml mode)
|
||||
|
||||
script -- dictionary containing src:type pairs, <script type='text/type' src=src></script>
|
||||
|
||||
title -- the title of the document as a string to be inserted into
|
||||
a title element as <title>my title</title> (ignored in xml mode)
|
||||
|
||||
header -- some text to be inserted right after the <body> element
|
||||
(ignored in xml mode)
|
||||
|
||||
footer -- some text to be inserted right before the </body> element
|
||||
(ignored in xml mode)
|
||||
|
||||
charset -- a string defining the character set, will be inserted into a
|
||||
<meta http-equiv='Content-Type' content='text/html; charset=myset'>
|
||||
element (ignored in xml mode)
|
||||
|
||||
encoding -- a string defining the encoding, will be put into to first line of
|
||||
the document as <?xml version='1.0' encoding='myencoding' ?> in
|
||||
xml mode (ignored in html mode)
|
||||
|
||||
doctype -- the document type string, defaults to
|
||||
<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>
|
||||
in html mode (ignored in xml mode)"""
|
||||
|
||||
self._full = True
|
||||
|
||||
if self.mode == 'strict_html' or self.mode == 'loose_html':
|
||||
if doctype is None:
|
||||
doctype = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>"
|
||||
self.header.append( doctype )
|
||||
self.html( lang=lang )
|
||||
self.head( )
|
||||
if charset is not None:
|
||||
self.meta( http_equiv='Content-Type', content="text/html; charset=%s" % charset )
|
||||
if metainfo is not None:
|
||||
self.metainfo( metainfo )
|
||||
if css is not None:
|
||||
self.css( css )
|
||||
if title is not None:
|
||||
self.title( title )
|
||||
if script is not None:
|
||||
self.scripts( script )
|
||||
self.head.close()
|
||||
if bodyattrs is not None:
|
||||
self.body( **bodyattrs )
|
||||
else:
|
||||
self.body( )
|
||||
if header is not None:
|
||||
self.content.append( header )
|
||||
if footer is not None:
|
||||
self.footer.append( footer )
|
||||
|
||||
elif self.mode == 'xml':
|
||||
if doctype is None:
|
||||
if encoding is not None:
|
||||
doctype = "<?xml version='1.0' encoding='%s' ?>" % encoding
|
||||
else:
|
||||
doctype = "<?xml version='1.0' ?>"
|
||||
self.header.append( doctype )
|
||||
|
||||
def css( self, filelist ):
|
||||
"""This convenience function is only useful for html.
|
||||
It adds css stylesheet(s) to the document via the <link> element."""
|
||||
|
||||
if isinstance( filelist, basestring ):
|
||||
self.link( href=filelist, rel='stylesheet', type='text/css', media='all' )
|
||||
else:
|
||||
for file in filelist:
|
||||
self.link( href=file, rel='stylesheet', type='text/css', media='all' )
|
||||
|
||||
def metainfo( self, mydict ):
|
||||
"""This convenience function is only useful for html.
|
||||
It adds meta information via the <meta> element, the argument is
|
||||
a dictionary of the form { 'name':'content' }."""
|
||||
|
||||
if isinstance( mydict, dict ):
|
||||
for name, content in mydict.iteritems( ):
|
||||
self.meta( name=name, content=content )
|
||||
else:
|
||||
raise TypeError, "Metainfo should be called with a dictionary argument of name:content pairs."
|
||||
|
||||
def scripts( self, mydict ):
|
||||
"""Only useful in html, mydict is dictionary of src:type pairs will
|
||||
be rendered as <script type='text/type' src=src></script>"""
|
||||
|
||||
if isinstance( mydict, dict ):
|
||||
for src, type in mydict.iteritems( ):
|
||||
self.script( '', src=src, type='text/%s' % type )
|
||||
else:
|
||||
raise TypeError, "Script should be given a dictionary of src:type pairs."
|
||||
|
||||
|
||||
class _oneliner:
|
||||
"""An instance of oneliner returns a string corresponding to one element.
|
||||
This class can be used to write 'oneliners' that return a string
|
||||
immediately so there is no need to instantiate the page class."""
|
||||
|
||||
def __init__( self, case='lower' ):
|
||||
self.case = case
|
||||
|
||||
def __getattr__( self, attr ):
|
||||
if attr.startswith("__") and attr.endswith("__"):
|
||||
raise AttributeError, attr
|
||||
return element( attr, case=self.case, parent=None )
|
||||
|
||||
oneliner = _oneliner( case='lower' )
|
||||
upper_oneliner = _oneliner( case='upper' )
|
||||
|
||||
def _argsdicts( args, mydict ):
|
||||
"""A utility generator that pads argument list and dictionary values, will only be called with len( args ) = 0, 1."""
|
||||
|
||||
if len( args ) == 0:
|
||||
args = None,
|
||||
elif len( args ) == 1:
|
||||
args = _totuple( args[0] )
|
||||
else:
|
||||
raise Exception, "We should have never gotten here."
|
||||
|
||||
mykeys = mydict.keys( )
|
||||
myvalues = map( _totuple, mydict.values( ) )
|
||||
|
||||
maxlength = max( map( len, [ args ] + myvalues ) )
|
||||
|
||||
for i in xrange( maxlength ):
|
||||
thisdict = { }
|
||||
for key, value in zip( mykeys, myvalues ):
|
||||
try:
|
||||
thisdict[ key ] = value[i]
|
||||
except IndexError:
|
||||
thisdict[ key ] = value[-1]
|
||||
try:
|
||||
thisarg = args[i]
|
||||
except IndexError:
|
||||
thisarg = args[-1]
|
||||
|
||||
yield thisarg, thisdict
|
||||
|
||||
def _totuple( x ):
|
||||
"""Utility stuff to convert string, int, float, None or anything to a usable tuple."""
|
||||
|
||||
if isinstance( x, basestring ):
|
||||
out = x,
|
||||
elif isinstance( x, ( int, float ) ):
|
||||
out = str( x ),
|
||||
elif x is None:
|
||||
out = None,
|
||||
else:
|
||||
out = tuple( x )
|
||||
|
||||
return out
|
||||
|
||||
def escape( text, newline=False ):
|
||||
"""Escape special html characters."""
|
||||
|
||||
if isinstance( text, basestring ):
|
||||
if '&' in text:
|
||||
text = text.replace( '&', '&' )
|
||||
if '>' in text:
|
||||
text = text.replace( '>', '>' )
|
||||
if '<' in text:
|
||||
text = text.replace( '<', '<' )
|
||||
if '\"' in text:
|
||||
text = text.replace( '\"', '"' )
|
||||
if '\'' in text:
|
||||
text = text.replace( '\'', '"' )
|
||||
if newline:
|
||||
if '\n' in text:
|
||||
text = text.replace( '\n', '<br>' )
|
||||
|
||||
return text
|
||||
|
||||
_escape = escape
|
||||
|
||||
def unescape( text ):
|
||||
"""Inverse of escape."""
|
||||
|
||||
if isinstance( text, basestring ):
|
||||
if '&' in text:
|
||||
text = text.replace( '&', '&' )
|
||||
if '>' in text:
|
||||
text = text.replace( '>', '>' )
|
||||
if '<' in text:
|
||||
text = text.replace( '<', '<' )
|
||||
if '"' in text:
|
||||
text = text.replace( '"', '\"' )
|
||||
|
||||
return text
|
||||
|
||||
class dummy:
|
||||
"""A dummy class for attaching attributes."""
|
||||
pass
|
||||
|
||||
doctype = dummy( )
|
||||
doctype.frameset = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Frameset//EN' 'http://www.w3.org/TR/html4/frameset.dtd'>"
|
||||
doctype.strict = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>"
|
||||
doctype.loose = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN' 'http://www.w3.org/TR/html4/loose.dtd'>"
|
||||
|
||||
class russell:
|
||||
"""A dummy class that contains anything."""
|
||||
|
||||
def __contains__( self, item ):
|
||||
return True
|
||||
|
||||
|
||||
class MarkupError( Exception ):
|
||||
"""All our exceptions subclass this."""
|
||||
def __str__( self ):
|
||||
return self.message
|
||||
|
||||
class ClosingError( MarkupError ):
|
||||
def __init__( self, tag ):
|
||||
self.message = "The element '%s' does not accept non-keyword arguments (has no closing tag)." % tag
|
||||
|
||||
class OpeningError( MarkupError ):
|
||||
def __init__( self, tag ):
|
||||
self.message = "The element '%s' can not be opened." % tag
|
||||
|
||||
class ArgumentError( MarkupError ):
|
||||
def __init__( self, tag ):
|
||||
self.message = "The element '%s' was called with more than one non-keyword argument." % tag
|
||||
|
||||
class InvalidElementError( MarkupError ):
|
||||
def __init__( self, tag, mode ):
|
||||
self.message = "The element '%s' is not valid for your mode '%s'." % ( tag, mode )
|
||||
|
||||
class DeprecationError( MarkupError ):
|
||||
def __init__( self, tag ):
|
||||
self.message = "The element '%s' is deprecated, instantiate markup.page with mode='loose_html' to allow it." % tag
|
||||
|
||||
class ModeError( MarkupError ):
|
||||
def __init__( self, mode ):
|
||||
self.message = "Mode '%s' is invalid, possible values: strict_html, loose_html, xml." % mode
|
||||
|
||||
class CustomizationError( MarkupError ):
|
||||
def __init__( self ):
|
||||
self.message = "If you customize the allowed elements, you must define both types 'onetags' and 'twotags'."
|
||||
|
||||
if __name__ == '__main__':
|
||||
print __doc__
|
||||
@@ -0,0 +1,127 @@
|
||||
# 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.
|
||||
|
||||
from UserDict import DictMixin
|
||||
|
||||
class OrderedDict(dict, DictMixin):
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
if len(args) > 1:
|
||||
raise TypeError('expected at most 1 arguments, got %d' % len(args))
|
||||
try:
|
||||
self.__end
|
||||
except AttributeError:
|
||||
self.clear()
|
||||
self.update(*args, **kwds)
|
||||
|
||||
def clear(self):
|
||||
self.__end = end = []
|
||||
end += [None, end, end] # sentinel node for doubly linked list
|
||||
self.__map = {} # key --> [key, prev, next]
|
||||
dict.clear(self)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key not in self:
|
||||
end = self.__end
|
||||
curr = end[1]
|
||||
curr[2] = end[1] = self.__map[key] = [key, curr, end]
|
||||
dict.__setitem__(self, key, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
dict.__delitem__(self, key)
|
||||
key, prev, next = self.__map.pop(key)
|
||||
prev[2] = next
|
||||
next[1] = prev
|
||||
|
||||
def __iter__(self):
|
||||
end = self.__end
|
||||
curr = end[2]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[2]
|
||||
|
||||
def __reversed__(self):
|
||||
end = self.__end
|
||||
curr = end[1]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[1]
|
||||
|
||||
def popitem(self, last=True):
|
||||
if not self:
|
||||
raise KeyError('dictionary is empty')
|
||||
if last:
|
||||
key = reversed(self).next()
|
||||
else:
|
||||
key = iter(self).next()
|
||||
value = self.pop(key)
|
||||
return key, value
|
||||
|
||||
def __reduce__(self):
|
||||
items = [[k, self[k]] for k in self]
|
||||
tmp = self.__map, self.__end
|
||||
del self.__map, self.__end
|
||||
inst_dict = vars(self).copy()
|
||||
self.__map, self.__end = tmp
|
||||
if inst_dict:
|
||||
return (self.__class__, (items,), inst_dict)
|
||||
return self.__class__, (items,)
|
||||
|
||||
def keys(self):
|
||||
return list(self)
|
||||
|
||||
setdefault = DictMixin.setdefault
|
||||
update = DictMixin.update
|
||||
pop = DictMixin.pop
|
||||
values = DictMixin.values
|
||||
items = DictMixin.items
|
||||
iterkeys = DictMixin.iterkeys
|
||||
itervalues = DictMixin.itervalues
|
||||
iteritems = DictMixin.iteritems
|
||||
|
||||
def __repr__(self):
|
||||
if not self:
|
||||
return '%s()' % (self.__class__.__name__,)
|
||||
return '%s(%r)' % (self.__class__.__name__, self.items())
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self)
|
||||
|
||||
@classmethod
|
||||
def fromkeys(cls, iterable, value=None):
|
||||
d = cls()
|
||||
for key in iterable:
|
||||
d[key] = value
|
||||
return d
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, OrderedDict):
|
||||
if len(self) != len(other):
|
||||
return False
|
||||
for p, q in zip(self.items(), other.items()):
|
||||
if p != q:
|
||||
return False
|
||||
return True
|
||||
return dict.__eq__(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
@@ -0,0 +1,437 @@
|
||||
r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
|
||||
JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
|
||||
interchange format.
|
||||
|
||||
:mod:`simplejson` exposes an API familiar to users of the standard library
|
||||
:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
|
||||
version of the :mod:`json` library contained in Python 2.6, but maintains
|
||||
compatibility with Python 2.4 and Python 2.5 and (currently) has
|
||||
significant performance advantages, even without using the optional C
|
||||
extension for speedups.
|
||||
|
||||
Encoding basic Python object hierarchies::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
|
||||
'["foo", {"bar": ["baz", null, 1.0, 2]}]'
|
||||
>>> print json.dumps("\"foo\bar")
|
||||
"\"foo\bar"
|
||||
>>> print json.dumps(u'\u1234')
|
||||
"\u1234"
|
||||
>>> print json.dumps('\\')
|
||||
"\\"
|
||||
>>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
|
||||
{"a": 0, "b": 0, "c": 0}
|
||||
>>> from StringIO import StringIO
|
||||
>>> io = StringIO()
|
||||
>>> json.dump(['streaming API'], io)
|
||||
>>> io.getvalue()
|
||||
'["streaming API"]'
|
||||
|
||||
Compact encoding::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
|
||||
'[1,2,3,{"4":5,"6":7}]'
|
||||
|
||||
Pretty printing::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=' ')
|
||||
>>> print '\n'.join([l.rstrip() for l in s.splitlines()])
|
||||
{
|
||||
"4": 5,
|
||||
"6": 7
|
||||
}
|
||||
|
||||
Decoding JSON::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
|
||||
>>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
|
||||
True
|
||||
>>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
|
||||
True
|
||||
>>> from StringIO import StringIO
|
||||
>>> io = StringIO('["streaming API"]')
|
||||
>>> json.load(io)[0] == 'streaming API'
|
||||
True
|
||||
|
||||
Specializing JSON object decoding::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> def as_complex(dct):
|
||||
... if '__complex__' in dct:
|
||||
... return complex(dct['real'], dct['imag'])
|
||||
... return dct
|
||||
...
|
||||
>>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
|
||||
... object_hook=as_complex)
|
||||
(1+2j)
|
||||
>>> from decimal import Decimal
|
||||
>>> json.loads('1.1', parse_float=Decimal) == Decimal('1.1')
|
||||
True
|
||||
|
||||
Specializing JSON object encoding::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> def encode_complex(obj):
|
||||
... if isinstance(obj, complex):
|
||||
... return [obj.real, obj.imag]
|
||||
... raise TypeError(repr(o) + " is not JSON serializable")
|
||||
...
|
||||
>>> json.dumps(2 + 1j, default=encode_complex)
|
||||
'[2.0, 1.0]'
|
||||
>>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
|
||||
'[2.0, 1.0]'
|
||||
>>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
|
||||
'[2.0, 1.0]'
|
||||
|
||||
|
||||
Using simplejson.tool from the shell to validate and pretty-print::
|
||||
|
||||
$ echo '{"json":"obj"}' | python -m simplejson.tool
|
||||
{
|
||||
"json": "obj"
|
||||
}
|
||||
$ echo '{ 1.2:3.4}' | python -m simplejson.tool
|
||||
Expecting property name: line 1 column 2 (char 2)
|
||||
"""
|
||||
__version__ = '2.1.1'
|
||||
__all__ = [
|
||||
'dump', 'dumps', 'load', 'loads',
|
||||
'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
|
||||
'OrderedDict',
|
||||
]
|
||||
|
||||
__author__ = 'Bob Ippolito <bob@redivi.com>'
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from decoder import JSONDecoder, JSONDecodeError
|
||||
from encoder import JSONEncoder
|
||||
def _import_OrderedDict():
|
||||
import collections
|
||||
try:
|
||||
return collections.OrderedDict
|
||||
except AttributeError:
|
||||
import ordered_dict
|
||||
return ordered_dict.OrderedDict
|
||||
OrderedDict = _import_OrderedDict()
|
||||
|
||||
def _import_c_make_encoder():
|
||||
try:
|
||||
from simplejson._speedups import make_encoder
|
||||
return make_encoder
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
_default_encoder = JSONEncoder(
|
||||
skipkeys=False,
|
||||
ensure_ascii=True,
|
||||
check_circular=True,
|
||||
allow_nan=True,
|
||||
indent=None,
|
||||
separators=None,
|
||||
encoding='utf-8',
|
||||
default=None,
|
||||
use_decimal=False,
|
||||
)
|
||||
|
||||
def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
|
||||
allow_nan=True, cls=None, indent=None, separators=None,
|
||||
encoding='utf-8', default=None, use_decimal=False, **kw):
|
||||
"""Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
|
||||
``.write()``-supporting file-like object).
|
||||
|
||||
If ``skipkeys`` is true then ``dict`` keys that are not basic types
|
||||
(``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
|
||||
will be skipped instead of raising a ``TypeError``.
|
||||
|
||||
If ``ensure_ascii`` is false, then the some chunks written to ``fp``
|
||||
may be ``unicode`` instances, subject to normal Python ``str`` to
|
||||
``unicode`` coercion rules. Unless ``fp.write()`` explicitly
|
||||
understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
|
||||
to cause an error.
|
||||
|
||||
If ``check_circular`` is false, then the circular reference check
|
||||
for container types will be skipped and a circular reference will
|
||||
result in an ``OverflowError`` (or worse).
|
||||
|
||||
If ``allow_nan`` is false, then it will be a ``ValueError`` to
|
||||
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
|
||||
in strict compliance of the JSON specification, instead of using the
|
||||
JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
|
||||
|
||||
If *indent* is a string, then JSON array elements and object members
|
||||
will be pretty-printed with a newline followed by that string repeated
|
||||
for each level of nesting. ``None`` (the default) selects the most compact
|
||||
representation without any newlines. For backwards compatibility with
|
||||
versions of simplejson earlier than 2.1.0, an integer is also accepted
|
||||
and is converted to a string with that many spaces.
|
||||
|
||||
If ``separators`` is an ``(item_separator, dict_separator)`` tuple
|
||||
then it will be used instead of the default ``(', ', ': ')`` separators.
|
||||
``(',', ':')`` is the most compact JSON representation.
|
||||
|
||||
``encoding`` is the character encoding for str instances, default is UTF-8.
|
||||
|
||||
``default(obj)`` is a function that should return a serializable version
|
||||
of obj or raise TypeError. The default simply raises TypeError.
|
||||
|
||||
If *use_decimal* is true (default: ``False``) then decimal.Decimal
|
||||
will be natively serialized to JSON with full precision.
|
||||
|
||||
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
|
||||
``.default()`` method to serialize additional types), specify it with
|
||||
the ``cls`` kwarg.
|
||||
|
||||
"""
|
||||
# cached encoder
|
||||
if (not skipkeys and ensure_ascii and
|
||||
check_circular and allow_nan and
|
||||
cls is None and indent is None and separators is None and
|
||||
encoding == 'utf-8' and default is None and not kw):
|
||||
iterable = _default_encoder.iterencode(obj)
|
||||
else:
|
||||
if cls is None:
|
||||
cls = JSONEncoder
|
||||
iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
|
||||
check_circular=check_circular, allow_nan=allow_nan, indent=indent,
|
||||
separators=separators, encoding=encoding,
|
||||
default=default, use_decimal=use_decimal, **kw).iterencode(obj)
|
||||
# could accelerate with writelines in some versions of Python, at
|
||||
# a debuggability cost
|
||||
for chunk in iterable:
|
||||
fp.write(chunk)
|
||||
|
||||
|
||||
def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
|
||||
allow_nan=True, cls=None, indent=None, separators=None,
|
||||
encoding='utf-8', default=None, use_decimal=False, **kw):
|
||||
"""Serialize ``obj`` to a JSON formatted ``str``.
|
||||
|
||||
If ``skipkeys`` is false then ``dict`` keys that are not basic types
|
||||
(``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
|
||||
will be skipped instead of raising a ``TypeError``.
|
||||
|
||||
If ``ensure_ascii`` is false, then the return value will be a
|
||||
``unicode`` instance subject to normal Python ``str`` to ``unicode``
|
||||
coercion rules instead of being escaped to an ASCII ``str``.
|
||||
|
||||
If ``check_circular`` is false, then the circular reference check
|
||||
for container types will be skipped and a circular reference will
|
||||
result in an ``OverflowError`` (or worse).
|
||||
|
||||
If ``allow_nan`` is false, then it will be a ``ValueError`` to
|
||||
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
|
||||
strict compliance of the JSON specification, instead of using the
|
||||
JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
|
||||
|
||||
If ``indent`` is a string, then JSON array elements and object members
|
||||
will be pretty-printed with a newline followed by that string repeated
|
||||
for each level of nesting. ``None`` (the default) selects the most compact
|
||||
representation without any newlines. For backwards compatibility with
|
||||
versions of simplejson earlier than 2.1.0, an integer is also accepted
|
||||
and is converted to a string with that many spaces.
|
||||
|
||||
If ``separators`` is an ``(item_separator, dict_separator)`` tuple
|
||||
then it will be used instead of the default ``(', ', ': ')`` separators.
|
||||
``(',', ':')`` is the most compact JSON representation.
|
||||
|
||||
``encoding`` is the character encoding for str instances, default is UTF-8.
|
||||
|
||||
``default(obj)`` is a function that should return a serializable version
|
||||
of obj or raise TypeError. The default simply raises TypeError.
|
||||
|
||||
If *use_decimal* is true (default: ``False``) then decimal.Decimal
|
||||
will be natively serialized to JSON with full precision.
|
||||
|
||||
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
|
||||
``.default()`` method to serialize additional types), specify it with
|
||||
the ``cls`` kwarg.
|
||||
|
||||
"""
|
||||
# cached encoder
|
||||
if (not skipkeys and ensure_ascii and
|
||||
check_circular and allow_nan and
|
||||
cls is None and indent is None and separators is None and
|
||||
encoding == 'utf-8' and default is None and not use_decimal
|
||||
and not kw):
|
||||
return _default_encoder.encode(obj)
|
||||
if cls is None:
|
||||
cls = JSONEncoder
|
||||
return cls(
|
||||
skipkeys=skipkeys, ensure_ascii=ensure_ascii,
|
||||
check_circular=check_circular, allow_nan=allow_nan, indent=indent,
|
||||
separators=separators, encoding=encoding, default=default,
|
||||
use_decimal=use_decimal, **kw).encode(obj)
|
||||
|
||||
|
||||
_default_decoder = JSONDecoder(encoding=None, object_hook=None,
|
||||
object_pairs_hook=None)
|
||||
|
||||
|
||||
def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
|
||||
parse_int=None, parse_constant=None, object_pairs_hook=None,
|
||||
use_decimal=False, **kw):
|
||||
"""Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
|
||||
a JSON document) to a Python object.
|
||||
|
||||
*encoding* determines the encoding used to interpret any
|
||||
:class:`str` objects decoded by this instance (``'utf-8'`` by
|
||||
default). It has no effect when decoding :class:`unicode` objects.
|
||||
|
||||
Note that currently only encodings that are a superset of ASCII work,
|
||||
strings of other encodings should be passed in as :class:`unicode`.
|
||||
|
||||
*object_hook*, if specified, will be called with the result of every
|
||||
JSON object decoded and its return value will be used in place of the
|
||||
given :class:`dict`. This can be used to provide custom
|
||||
deserializations (e.g. to support JSON-RPC class hinting).
|
||||
|
||||
*object_pairs_hook* is an optional function that will be called with
|
||||
the result of any object literal decode with an ordered list of pairs.
|
||||
The return value of *object_pairs_hook* will be used instead of the
|
||||
:class:`dict`. This feature can be used to implement custom decoders
|
||||
that rely on the order that the key and value pairs are decoded (for
|
||||
example, :func:`collections.OrderedDict` will remember the order of
|
||||
insertion). If *object_hook* is also defined, the *object_pairs_hook*
|
||||
takes priority.
|
||||
|
||||
*parse_float*, if specified, will be called with the string of every
|
||||
JSON float to be decoded. By default, this is equivalent to
|
||||
``float(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON floats (e.g. :class:`decimal.Decimal`).
|
||||
|
||||
*parse_int*, if specified, will be called with the string of every
|
||||
JSON int to be decoded. By default, this is equivalent to
|
||||
``int(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON integers (e.g. :class:`float`).
|
||||
|
||||
*parse_constant*, if specified, will be called with one of the
|
||||
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
|
||||
can be used to raise an exception if invalid JSON numbers are
|
||||
encountered.
|
||||
|
||||
If *use_decimal* is true (default: ``False``) then it implies
|
||||
parse_float=decimal.Decimal for parity with ``dump``.
|
||||
|
||||
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
|
||||
kwarg.
|
||||
|
||||
"""
|
||||
return loads(fp.read(),
|
||||
encoding=encoding, cls=cls, object_hook=object_hook,
|
||||
parse_float=parse_float, parse_int=parse_int,
|
||||
parse_constant=parse_constant, object_pairs_hook=object_pairs_hook,
|
||||
use_decimal=use_decimal, **kw)
|
||||
|
||||
|
||||
def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
|
||||
parse_int=None, parse_constant=None, object_pairs_hook=None,
|
||||
use_decimal=False, **kw):
|
||||
"""Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
|
||||
document) to a Python object.
|
||||
|
||||
*encoding* determines the encoding used to interpret any
|
||||
:class:`str` objects decoded by this instance (``'utf-8'`` by
|
||||
default). It has no effect when decoding :class:`unicode` objects.
|
||||
|
||||
Note that currently only encodings that are a superset of ASCII work,
|
||||
strings of other encodings should be passed in as :class:`unicode`.
|
||||
|
||||
*object_hook*, if specified, will be called with the result of every
|
||||
JSON object decoded and its return value will be used in place of the
|
||||
given :class:`dict`. This can be used to provide custom
|
||||
deserializations (e.g. to support JSON-RPC class hinting).
|
||||
|
||||
*object_pairs_hook* is an optional function that will be called with
|
||||
the result of any object literal decode with an ordered list of pairs.
|
||||
The return value of *object_pairs_hook* will be used instead of the
|
||||
:class:`dict`. This feature can be used to implement custom decoders
|
||||
that rely on the order that the key and value pairs are decoded (for
|
||||
example, :func:`collections.OrderedDict` will remember the order of
|
||||
insertion). If *object_hook* is also defined, the *object_pairs_hook*
|
||||
takes priority.
|
||||
|
||||
*parse_float*, if specified, will be called with the string of every
|
||||
JSON float to be decoded. By default, this is equivalent to
|
||||
``float(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON floats (e.g. :class:`decimal.Decimal`).
|
||||
|
||||
*parse_int*, if specified, will be called with the string of every
|
||||
JSON int to be decoded. By default, this is equivalent to
|
||||
``int(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON integers (e.g. :class:`float`).
|
||||
|
||||
*parse_constant*, if specified, will be called with one of the
|
||||
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
|
||||
can be used to raise an exception if invalid JSON numbers are
|
||||
encountered.
|
||||
|
||||
If *use_decimal* is true (default: ``False``) then it implies
|
||||
parse_float=decimal.Decimal for parity with ``dump``.
|
||||
|
||||
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
|
||||
kwarg.
|
||||
|
||||
"""
|
||||
if (cls is None and encoding is None and object_hook is None and
|
||||
parse_int is None and parse_float is None and
|
||||
parse_constant is None and object_pairs_hook is None
|
||||
and not use_decimal and not kw):
|
||||
return _default_decoder.decode(s)
|
||||
if cls is None:
|
||||
cls = JSONDecoder
|
||||
if object_hook is not None:
|
||||
kw['object_hook'] = object_hook
|
||||
if object_pairs_hook is not None:
|
||||
kw['object_pairs_hook'] = object_pairs_hook
|
||||
if parse_float is not None:
|
||||
kw['parse_float'] = parse_float
|
||||
if parse_int is not None:
|
||||
kw['parse_int'] = parse_int
|
||||
if parse_constant is not None:
|
||||
kw['parse_constant'] = parse_constant
|
||||
if use_decimal:
|
||||
if parse_float is not None:
|
||||
raise TypeError("use_decimal=True implies parse_float=Decimal")
|
||||
kw['parse_float'] = Decimal
|
||||
return cls(encoding=encoding, **kw).decode(s)
|
||||
|
||||
|
||||
def _toggle_speedups(enabled):
|
||||
import simplejson.decoder as dec
|
||||
import simplejson.encoder as enc
|
||||
import simplejson.scanner as scan
|
||||
c_make_encoder = _import_c_make_encoder()
|
||||
if enabled:
|
||||
dec.scanstring = dec.c_scanstring or dec.py_scanstring
|
||||
enc.c_make_encoder = c_make_encoder
|
||||
enc.encode_basestring_ascii = (enc.c_encode_basestring_ascii or
|
||||
enc.py_encode_basestring_ascii)
|
||||
scan.make_scanner = scan.c_make_scanner or scan.py_make_scanner
|
||||
else:
|
||||
dec.scanstring = dec.py_scanstring
|
||||
enc.c_make_encoder = None
|
||||
enc.encode_basestring_ascii = enc.py_encode_basestring_ascii
|
||||
scan.make_scanner = scan.py_make_scanner
|
||||
dec.make_scanner = scan.make_scanner
|
||||
global _default_decoder
|
||||
_default_decoder = JSONDecoder(
|
||||
encoding=None,
|
||||
object_hook=None,
|
||||
object_pairs_hook=None,
|
||||
)
|
||||
global _default_encoder
|
||||
_default_encoder = JSONEncoder(
|
||||
skipkeys=False,
|
||||
ensure_ascii=True,
|
||||
check_circular=True,
|
||||
allow_nan=True,
|
||||
indent=None,
|
||||
separators=None,
|
||||
encoding='utf-8',
|
||||
default=None,
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,421 @@
|
||||
"""Implementation of JSONDecoder
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import struct
|
||||
|
||||
from simplejson.scanner import make_scanner
|
||||
def _import_c_scanstring():
|
||||
try:
|
||||
from simplejson._speedups import scanstring
|
||||
return scanstring
|
||||
except ImportError:
|
||||
return None
|
||||
c_scanstring = _import_c_scanstring()
|
||||
|
||||
__all__ = ['JSONDecoder']
|
||||
|
||||
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
|
||||
|
||||
def _floatconstants():
|
||||
_BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
|
||||
# The struct module in Python 2.4 would get frexp() out of range here
|
||||
# when an endian is specified in the format string. Fixed in Python 2.5+
|
||||
if sys.byteorder != 'big':
|
||||
_BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
|
||||
nan, inf = struct.unpack('dd', _BYTES)
|
||||
return nan, inf, -inf
|
||||
|
||||
NaN, PosInf, NegInf = _floatconstants()
|
||||
|
||||
|
||||
class JSONDecodeError(ValueError):
|
||||
"""Subclass of ValueError with the following additional properties:
|
||||
|
||||
msg: The unformatted error message
|
||||
doc: The JSON document being parsed
|
||||
pos: The start index of doc where parsing failed
|
||||
end: The end index of doc where parsing failed (may be None)
|
||||
lineno: The line corresponding to pos
|
||||
colno: The column corresponding to pos
|
||||
endlineno: The line corresponding to end (may be None)
|
||||
endcolno: The column corresponding to end (may be None)
|
||||
|
||||
"""
|
||||
def __init__(self, msg, doc, pos, end=None):
|
||||
ValueError.__init__(self, errmsg(msg, doc, pos, end=end))
|
||||
self.msg = msg
|
||||
self.doc = doc
|
||||
self.pos = pos
|
||||
self.end = end
|
||||
self.lineno, self.colno = linecol(doc, pos)
|
||||
if end is not None:
|
||||
self.endlineno, self.endcolno = linecol(doc, pos)
|
||||
else:
|
||||
self.endlineno, self.endcolno = None, None
|
||||
|
||||
|
||||
def linecol(doc, pos):
|
||||
lineno = doc.count('\n', 0, pos) + 1
|
||||
if lineno == 1:
|
||||
colno = pos
|
||||
else:
|
||||
colno = pos - doc.rindex('\n', 0, pos)
|
||||
return lineno, colno
|
||||
|
||||
|
||||
def errmsg(msg, doc, pos, end=None):
|
||||
# Note that this function is called from _speedups
|
||||
lineno, colno = linecol(doc, pos)
|
||||
if end is None:
|
||||
#fmt = '{0}: line {1} column {2} (char {3})'
|
||||
#return fmt.format(msg, lineno, colno, pos)
|
||||
fmt = '%s: line %d column %d (char %d)'
|
||||
return fmt % (msg, lineno, colno, pos)
|
||||
endlineno, endcolno = linecol(doc, end)
|
||||
#fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
|
||||
#return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
|
||||
fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
|
||||
return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
|
||||
|
||||
|
||||
_CONSTANTS = {
|
||||
'-Infinity': NegInf,
|
||||
'Infinity': PosInf,
|
||||
'NaN': NaN,
|
||||
}
|
||||
|
||||
STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
|
||||
BACKSLASH = {
|
||||
'"': u'"', '\\': u'\\', '/': u'/',
|
||||
'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
|
||||
}
|
||||
|
||||
DEFAULT_ENCODING = "utf-8"
|
||||
|
||||
def py_scanstring(s, end, encoding=None, strict=True,
|
||||
_b=BACKSLASH, _m=STRINGCHUNK.match):
|
||||
"""Scan the string s for a JSON string. End is the index of the
|
||||
character in s after the quote that started the JSON string.
|
||||
Unescapes all valid JSON string escape sequences and raises ValueError
|
||||
on attempt to decode an invalid string. If strict is False then literal
|
||||
control characters are allowed in the string.
|
||||
|
||||
Returns a tuple of the decoded string and the index of the character in s
|
||||
after the end quote."""
|
||||
if encoding is None:
|
||||
encoding = DEFAULT_ENCODING
|
||||
chunks = []
|
||||
_append = chunks.append
|
||||
begin = end - 1
|
||||
while 1:
|
||||
chunk = _m(s, end)
|
||||
if chunk is None:
|
||||
raise JSONDecodeError(
|
||||
"Unterminated string starting at", s, begin)
|
||||
end = chunk.end()
|
||||
content, terminator = chunk.groups()
|
||||
# Content is contains zero or more unescaped string characters
|
||||
if content:
|
||||
if not isinstance(content, unicode):
|
||||
content = unicode(content, encoding)
|
||||
_append(content)
|
||||
# Terminator is the end of string, a literal control character,
|
||||
# or a backslash denoting that an escape sequence follows
|
||||
if terminator == '"':
|
||||
break
|
||||
elif terminator != '\\':
|
||||
if strict:
|
||||
msg = "Invalid control character %r at" % (terminator,)
|
||||
#msg = "Invalid control character {0!r} at".format(terminator)
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
else:
|
||||
_append(terminator)
|
||||
continue
|
||||
try:
|
||||
esc = s[end]
|
||||
except IndexError:
|
||||
raise JSONDecodeError(
|
||||
"Unterminated string starting at", s, begin)
|
||||
# If not a unicode escape sequence, must be in the lookup table
|
||||
if esc != 'u':
|
||||
try:
|
||||
char = _b[esc]
|
||||
except KeyError:
|
||||
msg = "Invalid \\escape: " + repr(esc)
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
end += 1
|
||||
else:
|
||||
# Unicode escape sequence
|
||||
esc = s[end + 1:end + 5]
|
||||
next_end = end + 5
|
||||
if len(esc) != 4:
|
||||
msg = "Invalid \\uXXXX escape"
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
uni = int(esc, 16)
|
||||
# Check for surrogate pair on UCS-4 systems
|
||||
if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
|
||||
msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
|
||||
if not s[end + 5:end + 7] == '\\u':
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
esc2 = s[end + 7:end + 11]
|
||||
if len(esc2) != 4:
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
uni2 = int(esc2, 16)
|
||||
uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
|
||||
next_end += 6
|
||||
char = unichr(uni)
|
||||
end = next_end
|
||||
# Append the unescaped character
|
||||
_append(char)
|
||||
return u''.join(chunks), end
|
||||
|
||||
|
||||
# Use speedup if available
|
||||
scanstring = c_scanstring or py_scanstring
|
||||
|
||||
WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
|
||||
WHITESPACE_STR = ' \t\n\r'
|
||||
|
||||
def JSONObject((s, end), encoding, strict, scan_once, object_hook,
|
||||
object_pairs_hook, memo=None,
|
||||
_w=WHITESPACE.match, _ws=WHITESPACE_STR):
|
||||
# Backwards compatibility
|
||||
if memo is None:
|
||||
memo = {}
|
||||
memo_get = memo.setdefault
|
||||
pairs = []
|
||||
# Use a slice to prevent IndexError from being raised, the following
|
||||
# check will raise a more specific ValueError if the string is empty
|
||||
nextchar = s[end:end + 1]
|
||||
# Normally we expect nextchar == '"'
|
||||
if nextchar != '"':
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end).end()
|
||||
nextchar = s[end:end + 1]
|
||||
# Trivial empty object
|
||||
if nextchar == '}':
|
||||
if object_pairs_hook is not None:
|
||||
result = object_pairs_hook(pairs)
|
||||
return result, end
|
||||
pairs = {}
|
||||
if object_hook is not None:
|
||||
pairs = object_hook(pairs)
|
||||
return pairs, end + 1
|
||||
elif nextchar != '"':
|
||||
raise JSONDecodeError("Expecting property name", s, end)
|
||||
end += 1
|
||||
while True:
|
||||
key, end = scanstring(s, end, encoding, strict)
|
||||
key = memo_get(key, key)
|
||||
|
||||
# To skip some function call overhead we optimize the fast paths where
|
||||
# the JSON key separator is ": " or just ":".
|
||||
if s[end:end + 1] != ':':
|
||||
end = _w(s, end).end()
|
||||
if s[end:end + 1] != ':':
|
||||
raise JSONDecodeError("Expecting : delimiter", s, end)
|
||||
|
||||
end += 1
|
||||
|
||||
try:
|
||||
if s[end] in _ws:
|
||||
end += 1
|
||||
if s[end] in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
try:
|
||||
value, end = scan_once(s, end)
|
||||
except StopIteration:
|
||||
raise JSONDecodeError("Expecting object", s, end)
|
||||
pairs.append((key, value))
|
||||
|
||||
try:
|
||||
nextchar = s[end]
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
nextchar = s[end]
|
||||
except IndexError:
|
||||
nextchar = ''
|
||||
end += 1
|
||||
|
||||
if nextchar == '}':
|
||||
break
|
||||
elif nextchar != ',':
|
||||
raise JSONDecodeError("Expecting , delimiter", s, end - 1)
|
||||
|
||||
try:
|
||||
nextchar = s[end]
|
||||
if nextchar in _ws:
|
||||
end += 1
|
||||
nextchar = s[end]
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
nextchar = s[end]
|
||||
except IndexError:
|
||||
nextchar = ''
|
||||
|
||||
end += 1
|
||||
if nextchar != '"':
|
||||
raise JSONDecodeError("Expecting property name", s, end - 1)
|
||||
|
||||
if object_pairs_hook is not None:
|
||||
result = object_pairs_hook(pairs)
|
||||
return result, end
|
||||
pairs = dict(pairs)
|
||||
if object_hook is not None:
|
||||
pairs = object_hook(pairs)
|
||||
return pairs, end
|
||||
|
||||
def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
|
||||
values = []
|
||||
nextchar = s[end:end + 1]
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
nextchar = s[end:end + 1]
|
||||
# Look-ahead for trivial empty array
|
||||
if nextchar == ']':
|
||||
return values, end + 1
|
||||
_append = values.append
|
||||
while True:
|
||||
try:
|
||||
value, end = scan_once(s, end)
|
||||
except StopIteration:
|
||||
raise JSONDecodeError("Expecting object", s, end)
|
||||
_append(value)
|
||||
nextchar = s[end:end + 1]
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
nextchar = s[end:end + 1]
|
||||
end += 1
|
||||
if nextchar == ']':
|
||||
break
|
||||
elif nextchar != ',':
|
||||
raise JSONDecodeError("Expecting , delimiter", s, end)
|
||||
|
||||
try:
|
||||
if s[end] in _ws:
|
||||
end += 1
|
||||
if s[end] in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
return values, end
|
||||
|
||||
class JSONDecoder(object):
|
||||
"""Simple JSON <http://json.org> decoder
|
||||
|
||||
Performs the following translations in decoding by default:
|
||||
|
||||
+---------------+-------------------+
|
||||
| JSON | Python |
|
||||
+===============+===================+
|
||||
| object | dict |
|
||||
+---------------+-------------------+
|
||||
| array | list |
|
||||
+---------------+-------------------+
|
||||
| string | unicode |
|
||||
+---------------+-------------------+
|
||||
| number (int) | int, long |
|
||||
+---------------+-------------------+
|
||||
| number (real) | float |
|
||||
+---------------+-------------------+
|
||||
| true | True |
|
||||
+---------------+-------------------+
|
||||
| false | False |
|
||||
+---------------+-------------------+
|
||||
| null | None |
|
||||
+---------------+-------------------+
|
||||
|
||||
It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
|
||||
their corresponding ``float`` values, which is outside the JSON spec.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, encoding=None, object_hook=None, parse_float=None,
|
||||
parse_int=None, parse_constant=None, strict=True,
|
||||
object_pairs_hook=None):
|
||||
"""
|
||||
*encoding* determines the encoding used to interpret any
|
||||
:class:`str` objects decoded by this instance (``'utf-8'`` by
|
||||
default). It has no effect when decoding :class:`unicode` objects.
|
||||
|
||||
Note that currently only encodings that are a superset of ASCII work,
|
||||
strings of other encodings should be passed in as :class:`unicode`.
|
||||
|
||||
*object_hook*, if specified, will be called with the result of every
|
||||
JSON object decoded and its return value will be used in place of the
|
||||
given :class:`dict`. This can be used to provide custom
|
||||
deserializations (e.g. to support JSON-RPC class hinting).
|
||||
|
||||
*object_pairs_hook* is an optional function that will be called with
|
||||
the result of any object literal decode with an ordered list of pairs.
|
||||
The return value of *object_pairs_hook* will be used instead of the
|
||||
:class:`dict`. This feature can be used to implement custom decoders
|
||||
that rely on the order that the key and value pairs are decoded (for
|
||||
example, :func:`collections.OrderedDict` will remember the order of
|
||||
insertion). If *object_hook* is also defined, the *object_pairs_hook*
|
||||
takes priority.
|
||||
|
||||
*parse_float*, if specified, will be called with the string of every
|
||||
JSON float to be decoded. By default, this is equivalent to
|
||||
``float(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON floats (e.g. :class:`decimal.Decimal`).
|
||||
|
||||
*parse_int*, if specified, will be called with the string of every
|
||||
JSON int to be decoded. By default, this is equivalent to
|
||||
``int(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON integers (e.g. :class:`float`).
|
||||
|
||||
*parse_constant*, if specified, will be called with one of the
|
||||
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
|
||||
can be used to raise an exception if invalid JSON numbers are
|
||||
encountered.
|
||||
|
||||
*strict* controls the parser's behavior when it encounters an
|
||||
invalid control character in a string. The default setting of
|
||||
``True`` means that unescaped control characters are parse errors, if
|
||||
``False`` then control characters will be allowed in strings.
|
||||
|
||||
"""
|
||||
self.encoding = encoding
|
||||
self.object_hook = object_hook
|
||||
self.object_pairs_hook = object_pairs_hook
|
||||
self.parse_float = parse_float or float
|
||||
self.parse_int = parse_int or int
|
||||
self.parse_constant = parse_constant or _CONSTANTS.__getitem__
|
||||
self.strict = strict
|
||||
self.parse_object = JSONObject
|
||||
self.parse_array = JSONArray
|
||||
self.parse_string = scanstring
|
||||
self.memo = {}
|
||||
self.scan_once = make_scanner(self)
|
||||
|
||||
def decode(self, s, _w=WHITESPACE.match):
|
||||
"""Return the Python representation of ``s`` (a ``str`` or ``unicode``
|
||||
instance containing a JSON document)
|
||||
|
||||
"""
|
||||
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
|
||||
end = _w(s, end).end()
|
||||
if end != len(s):
|
||||
raise JSONDecodeError("Extra data", s, end, len(s))
|
||||
return obj
|
||||
|
||||
def raw_decode(self, s, idx=0):
|
||||
"""Decode a JSON document from ``s`` (a ``str`` or ``unicode``
|
||||
beginning with a JSON document) and return a 2-tuple of the Python
|
||||
representation and the index in ``s`` where the document ended.
|
||||
|
||||
This can be used to decode a JSON document from a string that may
|
||||
have extraneous data at the end.
|
||||
|
||||
"""
|
||||
try:
|
||||
obj, end = self.scan_once(s, idx)
|
||||
except StopIteration:
|
||||
raise JSONDecodeError("No JSON object could be decoded", s, idx)
|
||||
return obj, end
|
||||
@@ -0,0 +1,501 @@
|
||||
"""Implementation of JSONEncoder
|
||||
"""
|
||||
import re
|
||||
from decimal import Decimal
|
||||
|
||||
def _import_speedups():
|
||||
try:
|
||||
from simplejson import _speedups
|
||||
return _speedups.encode_basestring_ascii, _speedups.make_encoder
|
||||
except ImportError:
|
||||
return None, None
|
||||
c_encode_basestring_ascii, c_make_encoder = _import_speedups()
|
||||
|
||||
from simplejson.decoder import PosInf
|
||||
|
||||
ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
|
||||
ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
|
||||
HAS_UTF8 = re.compile(r'[\x80-\xff]')
|
||||
ESCAPE_DCT = {
|
||||
'\\': '\\\\',
|
||||
'"': '\\"',
|
||||
'\b': '\\b',
|
||||
'\f': '\\f',
|
||||
'\n': '\\n',
|
||||
'\r': '\\r',
|
||||
'\t': '\\t',
|
||||
}
|
||||
for i in range(0x20):
|
||||
#ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
|
||||
ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
|
||||
|
||||
FLOAT_REPR = repr
|
||||
|
||||
def encode_basestring(s):
|
||||
"""Return a JSON representation of a Python string
|
||||
|
||||
"""
|
||||
if isinstance(s, str) and HAS_UTF8.search(s) is not None:
|
||||
s = s.decode('utf-8')
|
||||
def replace(match):
|
||||
return ESCAPE_DCT[match.group(0)]
|
||||
return u'"' + ESCAPE.sub(replace, s) + u'"'
|
||||
|
||||
|
||||
def py_encode_basestring_ascii(s):
|
||||
"""Return an ASCII-only JSON representation of a Python string
|
||||
|
||||
"""
|
||||
if isinstance(s, str) and HAS_UTF8.search(s) is not None:
|
||||
s = s.decode('utf-8')
|
||||
def replace(match):
|
||||
s = match.group(0)
|
||||
try:
|
||||
return ESCAPE_DCT[s]
|
||||
except KeyError:
|
||||
n = ord(s)
|
||||
if n < 0x10000:
|
||||
#return '\\u{0:04x}'.format(n)
|
||||
return '\\u%04x' % (n,)
|
||||
else:
|
||||
# surrogate pair
|
||||
n -= 0x10000
|
||||
s1 = 0xd800 | ((n >> 10) & 0x3ff)
|
||||
s2 = 0xdc00 | (n & 0x3ff)
|
||||
#return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
|
||||
return '\\u%04x\\u%04x' % (s1, s2)
|
||||
return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
|
||||
|
||||
|
||||
encode_basestring_ascii = (
|
||||
c_encode_basestring_ascii or py_encode_basestring_ascii)
|
||||
|
||||
class JSONEncoder(object):
|
||||
"""Extensible JSON <http://json.org> encoder for Python data structures.
|
||||
|
||||
Supports the following objects and types by default:
|
||||
|
||||
+-------------------+---------------+
|
||||
| Python | JSON |
|
||||
+===================+===============+
|
||||
| dict | object |
|
||||
+-------------------+---------------+
|
||||
| list, tuple | array |
|
||||
+-------------------+---------------+
|
||||
| str, unicode | string |
|
||||
+-------------------+---------------+
|
||||
| int, long, float | number |
|
||||
+-------------------+---------------+
|
||||
| True | true |
|
||||
+-------------------+---------------+
|
||||
| False | false |
|
||||
+-------------------+---------------+
|
||||
| None | null |
|
||||
+-------------------+---------------+
|
||||
|
||||
To extend this to recognize other objects, subclass and implement a
|
||||
``.default()`` method with another method that returns a serializable
|
||||
object for ``o`` if possible, otherwise it should call the superclass
|
||||
implementation (to raise ``TypeError``).
|
||||
|
||||
"""
|
||||
item_separator = ', '
|
||||
key_separator = ': '
|
||||
def __init__(self, skipkeys=False, ensure_ascii=True,
|
||||
check_circular=True, allow_nan=True, sort_keys=False,
|
||||
indent=None, separators=None, encoding='utf-8', default=None,
|
||||
use_decimal=False):
|
||||
"""Constructor for JSONEncoder, with sensible defaults.
|
||||
|
||||
If skipkeys is false, then it is a TypeError to attempt
|
||||
encoding of keys that are not str, int, long, float or None. If
|
||||
skipkeys is True, such items are simply skipped.
|
||||
|
||||
If ensure_ascii is true, the output is guaranteed to be str
|
||||
objects with all incoming unicode characters escaped. If
|
||||
ensure_ascii is false, the output will be unicode object.
|
||||
|
||||
If check_circular is true, then lists, dicts, and custom encoded
|
||||
objects will be checked for circular references during encoding to
|
||||
prevent an infinite recursion (which would cause an OverflowError).
|
||||
Otherwise, no such check takes place.
|
||||
|
||||
If allow_nan is true, then NaN, Infinity, and -Infinity will be
|
||||
encoded as such. This behavior is not JSON specification compliant,
|
||||
but is consistent with most JavaScript based encoders and decoders.
|
||||
Otherwise, it will be a ValueError to encode such floats.
|
||||
|
||||
If sort_keys is true, then the output of dictionaries will be
|
||||
sorted by key; this is useful for regression tests to ensure
|
||||
that JSON serializations can be compared on a day-to-day basis.
|
||||
|
||||
If indent is a string, then JSON array elements and object members
|
||||
will be pretty-printed with a newline followed by that string repeated
|
||||
for each level of nesting. ``None`` (the default) selects the most compact
|
||||
representation without any newlines. For backwards compatibility with
|
||||
versions of simplejson earlier than 2.1.0, an integer is also accepted
|
||||
and is converted to a string with that many spaces.
|
||||
|
||||
If specified, separators should be a (item_separator, key_separator)
|
||||
tuple. The default is (', ', ': '). To get the most compact JSON
|
||||
representation you should specify (',', ':') to eliminate whitespace.
|
||||
|
||||
If specified, default is a function that gets called for objects
|
||||
that can't otherwise be serialized. It should return a JSON encodable
|
||||
version of the object or raise a ``TypeError``.
|
||||
|
||||
If encoding is not None, then all input strings will be
|
||||
transformed into unicode using that encoding prior to JSON-encoding.
|
||||
The default is UTF-8.
|
||||
|
||||
If use_decimal is true (not the default), ``decimal.Decimal`` will
|
||||
be supported directly by the encoder. For the inverse, decode JSON
|
||||
with ``parse_float=decimal.Decimal``.
|
||||
|
||||
"""
|
||||
|
||||
self.skipkeys = skipkeys
|
||||
self.ensure_ascii = ensure_ascii
|
||||
self.check_circular = check_circular
|
||||
self.allow_nan = allow_nan
|
||||
self.sort_keys = sort_keys
|
||||
self.use_decimal = use_decimal
|
||||
if isinstance(indent, (int, long)):
|
||||
indent = ' ' * indent
|
||||
self.indent = indent
|
||||
if separators is not None:
|
||||
self.item_separator, self.key_separator = separators
|
||||
if default is not None:
|
||||
self.default = default
|
||||
self.encoding = encoding
|
||||
|
||||
def default(self, o):
|
||||
"""Implement this method in a subclass such that it returns
|
||||
a serializable object for ``o``, or calls the base implementation
|
||||
(to raise a ``TypeError``).
|
||||
|
||||
For example, to support arbitrary iterators, you could
|
||||
implement default like this::
|
||||
|
||||
def default(self, o):
|
||||
try:
|
||||
iterable = iter(o)
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
return list(iterable)
|
||||
return JSONEncoder.default(self, o)
|
||||
|
||||
"""
|
||||
raise TypeError(repr(o) + " is not JSON serializable")
|
||||
|
||||
def encode(self, o):
|
||||
"""Return a JSON string representation of a Python data structure.
|
||||
|
||||
>>> from simplejson import JSONEncoder
|
||||
>>> JSONEncoder().encode({"foo": ["bar", "baz"]})
|
||||
'{"foo": ["bar", "baz"]}'
|
||||
|
||||
"""
|
||||
# This is for extremely simple cases and benchmarks.
|
||||
if isinstance(o, basestring):
|
||||
if isinstance(o, str):
|
||||
_encoding = self.encoding
|
||||
if (_encoding is not None
|
||||
and not (_encoding == 'utf-8')):
|
||||
o = o.decode(_encoding)
|
||||
if self.ensure_ascii:
|
||||
return encode_basestring_ascii(o)
|
||||
else:
|
||||
return encode_basestring(o)
|
||||
# This doesn't pass the iterator directly to ''.join() because the
|
||||
# exceptions aren't as detailed. The list call should be roughly
|
||||
# equivalent to the PySequence_Fast that ''.join() would do.
|
||||
chunks = self.iterencode(o, _one_shot=True)
|
||||
if not isinstance(chunks, (list, tuple)):
|
||||
chunks = list(chunks)
|
||||
if self.ensure_ascii:
|
||||
return ''.join(chunks)
|
||||
else:
|
||||
return u''.join(chunks)
|
||||
|
||||
def iterencode(self, o, _one_shot=False):
|
||||
"""Encode the given object and yield each string
|
||||
representation as available.
|
||||
|
||||
For example::
|
||||
|
||||
for chunk in JSONEncoder().iterencode(bigobject):
|
||||
mysocket.write(chunk)
|
||||
|
||||
"""
|
||||
if self.check_circular:
|
||||
markers = {}
|
||||
else:
|
||||
markers = None
|
||||
if self.ensure_ascii:
|
||||
_encoder = encode_basestring_ascii
|
||||
else:
|
||||
_encoder = encode_basestring
|
||||
if self.encoding != 'utf-8':
|
||||
def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
|
||||
if isinstance(o, str):
|
||||
o = o.decode(_encoding)
|
||||
return _orig_encoder(o)
|
||||
|
||||
def floatstr(o, allow_nan=self.allow_nan,
|
||||
_repr=FLOAT_REPR, _inf=PosInf, _neginf=-PosInf):
|
||||
# Check for specials. Note that this type of test is processor
|
||||
# and/or platform-specific, so do tests which don't depend on
|
||||
# the internals.
|
||||
|
||||
if o != o:
|
||||
text = 'NaN'
|
||||
elif o == _inf:
|
||||
text = 'Infinity'
|
||||
elif o == _neginf:
|
||||
text = '-Infinity'
|
||||
else:
|
||||
return _repr(o)
|
||||
|
||||
if not allow_nan:
|
||||
raise ValueError(
|
||||
"Out of range float values are not JSON compliant: " +
|
||||
repr(o))
|
||||
|
||||
return text
|
||||
|
||||
|
||||
key_memo = {}
|
||||
if (_one_shot and c_make_encoder is not None
|
||||
and not self.indent and not self.sort_keys):
|
||||
_iterencode = c_make_encoder(
|
||||
markers, self.default, _encoder, self.indent,
|
||||
self.key_separator, self.item_separator, self.sort_keys,
|
||||
self.skipkeys, self.allow_nan, key_memo, self.use_decimal)
|
||||
else:
|
||||
_iterencode = _make_iterencode(
|
||||
markers, self.default, _encoder, self.indent, floatstr,
|
||||
self.key_separator, self.item_separator, self.sort_keys,
|
||||
self.skipkeys, _one_shot, self.use_decimal)
|
||||
try:
|
||||
return _iterencode(o, 0)
|
||||
finally:
|
||||
key_memo.clear()
|
||||
|
||||
|
||||
class JSONEncoderForHTML(JSONEncoder):
|
||||
"""An encoder that produces JSON safe to embed in HTML.
|
||||
|
||||
To embed JSON content in, say, a script tag on a web page, the
|
||||
characters &, < and > should be escaped. They cannot be escaped
|
||||
with the usual entities (e.g. &) because they are not expanded
|
||||
within <script> tags.
|
||||
"""
|
||||
|
||||
def encode(self, o):
|
||||
# Override JSONEncoder.encode because it has hacks for
|
||||
# performance that make things more complicated.
|
||||
chunks = self.iterencode(o, True)
|
||||
if self.ensure_ascii:
|
||||
return ''.join(chunks)
|
||||
else:
|
||||
return u''.join(chunks)
|
||||
|
||||
def iterencode(self, o, _one_shot=False):
|
||||
chunks = super(JSONEncoderForHTML, self).iterencode(o, _one_shot)
|
||||
for chunk in chunks:
|
||||
chunk = chunk.replace('&', '\\u0026')
|
||||
chunk = chunk.replace('<', '\\u003c')
|
||||
chunk = chunk.replace('>', '\\u003e')
|
||||
yield chunk
|
||||
|
||||
|
||||
def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
|
||||
_key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
|
||||
_use_decimal,
|
||||
## HACK: hand-optimized bytecode; turn globals into locals
|
||||
False=False,
|
||||
True=True,
|
||||
ValueError=ValueError,
|
||||
basestring=basestring,
|
||||
Decimal=Decimal,
|
||||
dict=dict,
|
||||
float=float,
|
||||
id=id,
|
||||
int=int,
|
||||
isinstance=isinstance,
|
||||
list=list,
|
||||
long=long,
|
||||
str=str,
|
||||
tuple=tuple,
|
||||
):
|
||||
|
||||
def _iterencode_list(lst, _current_indent_level):
|
||||
if not lst:
|
||||
yield '[]'
|
||||
return
|
||||
if markers is not None:
|
||||
markerid = id(lst)
|
||||
if markerid in markers:
|
||||
raise ValueError("Circular reference detected")
|
||||
markers[markerid] = lst
|
||||
buf = '['
|
||||
if _indent is not None:
|
||||
_current_indent_level += 1
|
||||
newline_indent = '\n' + (_indent * _current_indent_level)
|
||||
separator = _item_separator + newline_indent
|
||||
buf += newline_indent
|
||||
else:
|
||||
newline_indent = None
|
||||
separator = _item_separator
|
||||
first = True
|
||||
for value in lst:
|
||||
if first:
|
||||
first = False
|
||||
else:
|
||||
buf = separator
|
||||
if isinstance(value, basestring):
|
||||
yield buf + _encoder(value)
|
||||
elif value is None:
|
||||
yield buf + 'null'
|
||||
elif value is True:
|
||||
yield buf + 'true'
|
||||
elif value is False:
|
||||
yield buf + 'false'
|
||||
elif isinstance(value, (int, long)):
|
||||
yield buf + str(value)
|
||||
elif isinstance(value, float):
|
||||
yield buf + _floatstr(value)
|
||||
elif _use_decimal and isinstance(value, Decimal):
|
||||
yield buf + str(value)
|
||||
else:
|
||||
yield buf
|
||||
if isinstance(value, (list, tuple)):
|
||||
chunks = _iterencode_list(value, _current_indent_level)
|
||||
elif isinstance(value, dict):
|
||||
chunks = _iterencode_dict(value, _current_indent_level)
|
||||
else:
|
||||
chunks = _iterencode(value, _current_indent_level)
|
||||
for chunk in chunks:
|
||||
yield chunk
|
||||
if newline_indent is not None:
|
||||
_current_indent_level -= 1
|
||||
yield '\n' + (_indent * _current_indent_level)
|
||||
yield ']'
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
|
||||
def _iterencode_dict(dct, _current_indent_level):
|
||||
if not dct:
|
||||
yield '{}'
|
||||
return
|
||||
if markers is not None:
|
||||
markerid = id(dct)
|
||||
if markerid in markers:
|
||||
raise ValueError("Circular reference detected")
|
||||
markers[markerid] = dct
|
||||
yield '{'
|
||||
if _indent is not None:
|
||||
_current_indent_level += 1
|
||||
newline_indent = '\n' + (_indent * _current_indent_level)
|
||||
item_separator = _item_separator + newline_indent
|
||||
yield newline_indent
|
||||
else:
|
||||
newline_indent = None
|
||||
item_separator = _item_separator
|
||||
first = True
|
||||
if _sort_keys:
|
||||
items = dct.items()
|
||||
items.sort(key=lambda kv: kv[0])
|
||||
else:
|
||||
items = dct.iteritems()
|
||||
for key, value in items:
|
||||
if isinstance(key, basestring):
|
||||
pass
|
||||
# JavaScript is weakly typed for these, so it makes sense to
|
||||
# also allow them. Many encoders seem to do something like this.
|
||||
elif isinstance(key, float):
|
||||
key = _floatstr(key)
|
||||
elif key is True:
|
||||
key = 'true'
|
||||
elif key is False:
|
||||
key = 'false'
|
||||
elif key is None:
|
||||
key = 'null'
|
||||
elif isinstance(key, (int, long)):
|
||||
key = str(key)
|
||||
elif _skipkeys:
|
||||
continue
|
||||
else:
|
||||
raise TypeError("key " + repr(key) + " is not a string")
|
||||
if first:
|
||||
first = False
|
||||
else:
|
||||
yield item_separator
|
||||
yield _encoder(key)
|
||||
yield _key_separator
|
||||
if isinstance(value, basestring):
|
||||
yield _encoder(value)
|
||||
elif value is None:
|
||||
yield 'null'
|
||||
elif value is True:
|
||||
yield 'true'
|
||||
elif value is False:
|
||||
yield 'false'
|
||||
elif isinstance(value, (int, long)):
|
||||
yield str(value)
|
||||
elif isinstance(value, float):
|
||||
yield _floatstr(value)
|
||||
elif _use_decimal and isinstance(value, Decimal):
|
||||
yield str(value)
|
||||
else:
|
||||
if isinstance(value, (list, tuple)):
|
||||
chunks = _iterencode_list(value, _current_indent_level)
|
||||
elif isinstance(value, dict):
|
||||
chunks = _iterencode_dict(value, _current_indent_level)
|
||||
else:
|
||||
chunks = _iterencode(value, _current_indent_level)
|
||||
for chunk in chunks:
|
||||
yield chunk
|
||||
if newline_indent is not None:
|
||||
_current_indent_level -= 1
|
||||
yield '\n' + (_indent * _current_indent_level)
|
||||
yield '}'
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
|
||||
def _iterencode(o, _current_indent_level):
|
||||
if isinstance(o, basestring):
|
||||
yield _encoder(o)
|
||||
elif o is None:
|
||||
yield 'null'
|
||||
elif o is True:
|
||||
yield 'true'
|
||||
elif o is False:
|
||||
yield 'false'
|
||||
elif isinstance(o, (int, long)):
|
||||
yield str(o)
|
||||
elif isinstance(o, float):
|
||||
yield _floatstr(o)
|
||||
elif isinstance(o, (list, tuple)):
|
||||
for chunk in _iterencode_list(o, _current_indent_level):
|
||||
yield chunk
|
||||
elif isinstance(o, dict):
|
||||
for chunk in _iterencode_dict(o, _current_indent_level):
|
||||
yield chunk
|
||||
elif _use_decimal and isinstance(o, Decimal):
|
||||
yield str(o)
|
||||
else:
|
||||
if markers is not None:
|
||||
markerid = id(o)
|
||||
if markerid in markers:
|
||||
raise ValueError("Circular reference detected")
|
||||
markers[markerid] = o
|
||||
o = _default(o)
|
||||
for chunk in _iterencode(o, _current_indent_level):
|
||||
yield chunk
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
|
||||
return _iterencode
|
||||
@@ -0,0 +1,119 @@
|
||||
"""Drop-in replacement for collections.OrderedDict by Raymond Hettinger
|
||||
|
||||
http://code.activestate.com/recipes/576693/
|
||||
|
||||
"""
|
||||
from UserDict import DictMixin
|
||||
|
||||
# Modified from original to support Python 2.4, see
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=53
|
||||
try:
|
||||
all
|
||||
except NameError:
|
||||
def all(seq):
|
||||
for elem in seq:
|
||||
if not elem:
|
||||
return False
|
||||
return True
|
||||
|
||||
class OrderedDict(dict, DictMixin):
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
if len(args) > 1:
|
||||
raise TypeError('expected at most 1 arguments, got %d' % len(args))
|
||||
try:
|
||||
self.__end
|
||||
except AttributeError:
|
||||
self.clear()
|
||||
self.update(*args, **kwds)
|
||||
|
||||
def clear(self):
|
||||
self.__end = end = []
|
||||
end += [None, end, end] # sentinel node for doubly linked list
|
||||
self.__map = {} # key --> [key, prev, next]
|
||||
dict.clear(self)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key not in self:
|
||||
end = self.__end
|
||||
curr = end[1]
|
||||
curr[2] = end[1] = self.__map[key] = [key, curr, end]
|
||||
dict.__setitem__(self, key, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
dict.__delitem__(self, key)
|
||||
key, prev, next = self.__map.pop(key)
|
||||
prev[2] = next
|
||||
next[1] = prev
|
||||
|
||||
def __iter__(self):
|
||||
end = self.__end
|
||||
curr = end[2]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[2]
|
||||
|
||||
def __reversed__(self):
|
||||
end = self.__end
|
||||
curr = end[1]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[1]
|
||||
|
||||
def popitem(self, last=True):
|
||||
if not self:
|
||||
raise KeyError('dictionary is empty')
|
||||
# Modified from original to support Python 2.4, see
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=53
|
||||
if last:
|
||||
key = reversed(self).next()
|
||||
else:
|
||||
key = iter(self).next()
|
||||
value = self.pop(key)
|
||||
return key, value
|
||||
|
||||
def __reduce__(self):
|
||||
items = [[k, self[k]] for k in self]
|
||||
tmp = self.__map, self.__end
|
||||
del self.__map, self.__end
|
||||
inst_dict = vars(self).copy()
|
||||
self.__map, self.__end = tmp
|
||||
if inst_dict:
|
||||
return (self.__class__, (items,), inst_dict)
|
||||
return self.__class__, (items,)
|
||||
|
||||
def keys(self):
|
||||
return list(self)
|
||||
|
||||
setdefault = DictMixin.setdefault
|
||||
update = DictMixin.update
|
||||
pop = DictMixin.pop
|
||||
values = DictMixin.values
|
||||
items = DictMixin.items
|
||||
iterkeys = DictMixin.iterkeys
|
||||
itervalues = DictMixin.itervalues
|
||||
iteritems = DictMixin.iteritems
|
||||
|
||||
def __repr__(self):
|
||||
if not self:
|
||||
return '%s()' % (self.__class__.__name__,)
|
||||
return '%s(%r)' % (self.__class__.__name__, self.items())
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self)
|
||||
|
||||
@classmethod
|
||||
def fromkeys(cls, iterable, value=None):
|
||||
d = cls()
|
||||
for key in iterable:
|
||||
d[key] = value
|
||||
return d
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, OrderedDict):
|
||||
return len(self)==len(other) and \
|
||||
all(p==q for p, q in zip(self.items(), other.items()))
|
||||
return dict.__eq__(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
@@ -0,0 +1,77 @@
|
||||
"""JSON token scanner
|
||||
"""
|
||||
import re
|
||||
def _import_c_make_scanner():
|
||||
try:
|
||||
from simplejson._speedups import make_scanner
|
||||
return make_scanner
|
||||
except ImportError:
|
||||
return None
|
||||
c_make_scanner = _import_c_make_scanner()
|
||||
|
||||
__all__ = ['make_scanner']
|
||||
|
||||
NUMBER_RE = re.compile(
|
||||
r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
|
||||
(re.VERBOSE | re.MULTILINE | re.DOTALL))
|
||||
|
||||
def py_make_scanner(context):
|
||||
parse_object = context.parse_object
|
||||
parse_array = context.parse_array
|
||||
parse_string = context.parse_string
|
||||
match_number = NUMBER_RE.match
|
||||
encoding = context.encoding
|
||||
strict = context.strict
|
||||
parse_float = context.parse_float
|
||||
parse_int = context.parse_int
|
||||
parse_constant = context.parse_constant
|
||||
object_hook = context.object_hook
|
||||
object_pairs_hook = context.object_pairs_hook
|
||||
memo = context.memo
|
||||
|
||||
def _scan_once(string, idx):
|
||||
try:
|
||||
nextchar = string[idx]
|
||||
except IndexError:
|
||||
raise StopIteration
|
||||
|
||||
if nextchar == '"':
|
||||
return parse_string(string, idx + 1, encoding, strict)
|
||||
elif nextchar == '{':
|
||||
return parse_object((string, idx + 1), encoding, strict,
|
||||
_scan_once, object_hook, object_pairs_hook, memo)
|
||||
elif nextchar == '[':
|
||||
return parse_array((string, idx + 1), _scan_once)
|
||||
elif nextchar == 'n' and string[idx:idx + 4] == 'null':
|
||||
return None, idx + 4
|
||||
elif nextchar == 't' and string[idx:idx + 4] == 'true':
|
||||
return True, idx + 4
|
||||
elif nextchar == 'f' and string[idx:idx + 5] == 'false':
|
||||
return False, idx + 5
|
||||
|
||||
m = match_number(string, idx)
|
||||
if m is not None:
|
||||
integer, frac, exp = m.groups()
|
||||
if frac or exp:
|
||||
res = parse_float(integer + (frac or '') + (exp or ''))
|
||||
else:
|
||||
res = parse_int(integer)
|
||||
return res, m.end()
|
||||
elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
|
||||
return parse_constant('NaN'), idx + 3
|
||||
elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
|
||||
return parse_constant('Infinity'), idx + 8
|
||||
elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
|
||||
return parse_constant('-Infinity'), idx + 9
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
def scan_once(string, idx):
|
||||
try:
|
||||
return _scan_once(string, idx)
|
||||
finally:
|
||||
memo.clear()
|
||||
|
||||
return scan_once
|
||||
|
||||
make_scanner = c_make_scanner or py_make_scanner
|
||||
@@ -0,0 +1,63 @@
|
||||
import unittest
|
||||
import doctest
|
||||
|
||||
|
||||
class OptionalExtensionTestSuite(unittest.TestSuite):
|
||||
def run(self, result):
|
||||
import simplejson
|
||||
run = unittest.TestSuite.run
|
||||
run(self, result)
|
||||
simplejson._toggle_speedups(False)
|
||||
run(self, result)
|
||||
simplejson._toggle_speedups(True)
|
||||
return result
|
||||
|
||||
|
||||
def additional_tests(suite=None):
|
||||
import simplejson
|
||||
import simplejson.encoder
|
||||
import simplejson.decoder
|
||||
if suite is None:
|
||||
suite = unittest.TestSuite()
|
||||
for mod in (simplejson, simplejson.encoder, simplejson.decoder):
|
||||
suite.addTest(doctest.DocTestSuite(mod))
|
||||
suite.addTest(doctest.DocFileSuite('../../index.rst'))
|
||||
return suite
|
||||
|
||||
|
||||
def all_tests_suite():
|
||||
suite = unittest.TestLoader().loadTestsFromNames([
|
||||
'simplejson.tests.test_check_circular',
|
||||
'simplejson.tests.test_decode',
|
||||
'simplejson.tests.test_default',
|
||||
'simplejson.tests.test_dump',
|
||||
'simplejson.tests.test_encode_basestring_ascii',
|
||||
'simplejson.tests.test_encode_for_html',
|
||||
'simplejson.tests.test_fail',
|
||||
'simplejson.tests.test_float',
|
||||
'simplejson.tests.test_indent',
|
||||
'simplejson.tests.test_pass1',
|
||||
'simplejson.tests.test_pass2',
|
||||
'simplejson.tests.test_pass3',
|
||||
'simplejson.tests.test_recursion',
|
||||
'simplejson.tests.test_scanstring',
|
||||
'simplejson.tests.test_separators',
|
||||
'simplejson.tests.test_speedups',
|
||||
'simplejson.tests.test_unicode',
|
||||
'simplejson.tests.test_decimal',
|
||||
])
|
||||
suite = additional_tests(suite)
|
||||
return OptionalExtensionTestSuite([suite])
|
||||
|
||||
|
||||
def main():
|
||||
runner = unittest.TextTestRunner()
|
||||
suite = all_tests_suite()
|
||||
runner.run(suite)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
main()
|
||||
@@ -0,0 +1,30 @@
|
||||
from unittest import TestCase
|
||||
import simplejson as json
|
||||
|
||||
def default_iterable(obj):
|
||||
return list(obj)
|
||||
|
||||
class TestCheckCircular(TestCase):
|
||||
def test_circular_dict(self):
|
||||
dct = {}
|
||||
dct['a'] = dct
|
||||
self.assertRaises(ValueError, json.dumps, dct)
|
||||
|
||||
def test_circular_list(self):
|
||||
lst = []
|
||||
lst.append(lst)
|
||||
self.assertRaises(ValueError, json.dumps, lst)
|
||||
|
||||
def test_circular_composite(self):
|
||||
dct2 = {}
|
||||
dct2['a'] = []
|
||||
dct2['a'].append(dct2)
|
||||
self.assertRaises(ValueError, json.dumps, dct2)
|
||||
|
||||
def test_circular_default(self):
|
||||
json.dumps([set()], default=default_iterable)
|
||||
self.assertRaises(TypeError, json.dumps, [set()])
|
||||
|
||||
def test_circular_off_default(self):
|
||||
json.dumps([set()], default=default_iterable, check_circular=False)
|
||||
self.assertRaises(TypeError, json.dumps, [set()], check_circular=False)
|
||||
@@ -0,0 +1,33 @@
|
||||
from decimal import Decimal
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
class TestDecimal(TestCase):
|
||||
NUMS = "1.0", "10.00", "1.1", "1234567890.1234567890", "500"
|
||||
def test_decimal_encode(self):
|
||||
for d in map(Decimal, self.NUMS):
|
||||
self.assertEquals(json.dumps(d, use_decimal=True), str(d))
|
||||
|
||||
def test_decimal_decode(self):
|
||||
for s in self.NUMS:
|
||||
self.assertEquals(json.loads(s, parse_float=Decimal), Decimal(s))
|
||||
|
||||
def test_decimal_roundtrip(self):
|
||||
for d in map(Decimal, self.NUMS):
|
||||
# The type might not be the same (int and Decimal) but they
|
||||
# should still compare equal.
|
||||
self.assertEquals(
|
||||
json.loads(
|
||||
json.dumps(d, use_decimal=True), parse_float=Decimal),
|
||||
d)
|
||||
self.assertEquals(
|
||||
json.loads(
|
||||
json.dumps([d], use_decimal=True), parse_float=Decimal),
|
||||
[d])
|
||||
|
||||
def test_decimal_defaults(self):
|
||||
d = Decimal(1)
|
||||
# use_decimal=False is the default
|
||||
self.assertRaises(TypeError, json.dumps, d, use_decimal=False)
|
||||
self.assertRaises(TypeError, json.dumps, d)
|
||||
@@ -0,0 +1,73 @@
|
||||
import decimal
|
||||
from unittest import TestCase
|
||||
from StringIO import StringIO
|
||||
|
||||
import simplejson as json
|
||||
from simplejson import OrderedDict
|
||||
|
||||
class TestDecode(TestCase):
|
||||
if not hasattr(TestCase, 'assertIs'):
|
||||
def assertIs(self, a, b):
|
||||
self.assertTrue(a is b, '%r is %r' % (a, b))
|
||||
|
||||
def test_decimal(self):
|
||||
rval = json.loads('1.1', parse_float=decimal.Decimal)
|
||||
self.assertTrue(isinstance(rval, decimal.Decimal))
|
||||
self.assertEquals(rval, decimal.Decimal('1.1'))
|
||||
|
||||
def test_float(self):
|
||||
rval = json.loads('1', parse_int=float)
|
||||
self.assertTrue(isinstance(rval, float))
|
||||
self.assertEquals(rval, 1.0)
|
||||
|
||||
def test_decoder_optimizations(self):
|
||||
# Several optimizations were made that skip over calls to
|
||||
# the whitespace regex, so this test is designed to try and
|
||||
# exercise the uncommon cases. The array cases are already covered.
|
||||
rval = json.loads('{ "key" : "value" , "k":"v" }')
|
||||
self.assertEquals(rval, {"key":"value", "k":"v"})
|
||||
|
||||
def test_empty_objects(self):
|
||||
s = '{}'
|
||||
self.assertEqual(json.loads(s), eval(s))
|
||||
s = '[]'
|
||||
self.assertEqual(json.loads(s), eval(s))
|
||||
s = '""'
|
||||
self.assertEqual(json.loads(s), eval(s))
|
||||
|
||||
def test_object_pairs_hook(self):
|
||||
s = '{"xkd":1, "kcw":2, "art":3, "hxm":4, "qrt":5, "pad":6, "hoy":7}'
|
||||
p = [("xkd", 1), ("kcw", 2), ("art", 3), ("hxm", 4),
|
||||
("qrt", 5), ("pad", 6), ("hoy", 7)]
|
||||
self.assertEqual(json.loads(s), eval(s))
|
||||
self.assertEqual(json.loads(s, object_pairs_hook=lambda x: x), p)
|
||||
self.assertEqual(json.load(StringIO(s),
|
||||
object_pairs_hook=lambda x: x), p)
|
||||
od = json.loads(s, object_pairs_hook=OrderedDict)
|
||||
self.assertEqual(od, OrderedDict(p))
|
||||
self.assertEqual(type(od), OrderedDict)
|
||||
# the object_pairs_hook takes priority over the object_hook
|
||||
self.assertEqual(json.loads(s,
|
||||
object_pairs_hook=OrderedDict,
|
||||
object_hook=lambda x: None),
|
||||
OrderedDict(p))
|
||||
|
||||
def check_keys_reuse(self, source, loads):
|
||||
rval = loads(source)
|
||||
(a, b), (c, d) = sorted(rval[0]), sorted(rval[1])
|
||||
self.assertIs(a, c)
|
||||
self.assertIs(b, d)
|
||||
|
||||
def test_keys_reuse_str(self):
|
||||
s = u'[{"a_key": 1, "b_\xe9": 2}, {"a_key": 3, "b_\xe9": 4}]'.encode('utf8')
|
||||
self.check_keys_reuse(s, json.loads)
|
||||
|
||||
def test_keys_reuse_unicode(self):
|
||||
s = u'[{"a_key": 1, "b_\xe9": 2}, {"a_key": 3, "b_\xe9": 4}]'
|
||||
self.check_keys_reuse(s, json.loads)
|
||||
|
||||
def test_empty_strings(self):
|
||||
self.assertEqual(json.loads('""'), "")
|
||||
self.assertEqual(json.loads(u'""'), u"")
|
||||
self.assertEqual(json.loads('[""]'), [""])
|
||||
self.assertEqual(json.loads(u'[""]'), [u""])
|
||||
@@ -0,0 +1,9 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
class TestDefault(TestCase):
|
||||
def test_default(self):
|
||||
self.assertEquals(
|
||||
json.dumps(type, default=repr),
|
||||
json.dumps(repr(type)))
|
||||
@@ -0,0 +1,27 @@
|
||||
from unittest import TestCase
|
||||
from cStringIO import StringIO
|
||||
|
||||
import simplejson as json
|
||||
|
||||
class TestDump(TestCase):
|
||||
def test_dump(self):
|
||||
sio = StringIO()
|
||||
json.dump({}, sio)
|
||||
self.assertEquals(sio.getvalue(), '{}')
|
||||
|
||||
def test_dumps(self):
|
||||
self.assertEquals(json.dumps({}), '{}')
|
||||
|
||||
def test_encode_truefalse(self):
|
||||
self.assertEquals(json.dumps(
|
||||
{True: False, False: True}, sort_keys=True),
|
||||
'{"false": true, "true": false}')
|
||||
self.assertEquals(json.dumps(
|
||||
{2: 3.0, 4.0: 5L, False: 1, 6L: True, "7": 0}, sort_keys=True),
|
||||
'{"false": 1, "2": 3.0, "4.0": 5, "6": true, "7": 0}')
|
||||
|
||||
def test_ordered_dict(self):
|
||||
# http://bugs.python.org/issue6105
|
||||
items = [('one', 1), ('two', 2), ('three', 3), ('four', 4), ('five', 5)]
|
||||
s = json.dumps(json.OrderedDict(items))
|
||||
self.assertEqual(s, '{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5}')
|
||||
@@ -0,0 +1,41 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson.encoder
|
||||
|
||||
CASES = [
|
||||
(u'/\\"\ucafe\ubabe\uab98\ufcde\ubcda\uef4a\x08\x0c\n\r\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?', '"/\\\\\\"\\ucafe\\ubabe\\uab98\\ufcde\\ubcda\\uef4a\\b\\f\\n\\r\\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?"'),
|
||||
(u'\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
|
||||
(u'controls', '"controls"'),
|
||||
(u'\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'),
|
||||
(u'{"object with 1 member":["array with 1 element"]}', '"{\\"object with 1 member\\":[\\"array with 1 element\\"]}"'),
|
||||
(u' s p a c e d ', '" s p a c e d "'),
|
||||
(u'\U0001d120', '"\\ud834\\udd20"'),
|
||||
(u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
|
||||
('\xce\xb1\xce\xa9', '"\\u03b1\\u03a9"'),
|
||||
(u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
|
||||
('\xce\xb1\xce\xa9', '"\\u03b1\\u03a9"'),
|
||||
(u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
|
||||
(u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
|
||||
(u"`1~!@#$%^&*()_+-={':[,]}|;.</>?", '"`1~!@#$%^&*()_+-={\':[,]}|;.</>?"'),
|
||||
(u'\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'),
|
||||
(u'\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
|
||||
]
|
||||
|
||||
class TestEncodeBaseStringAscii(TestCase):
|
||||
def test_py_encode_basestring_ascii(self):
|
||||
self._test_encode_basestring_ascii(simplejson.encoder.py_encode_basestring_ascii)
|
||||
|
||||
def test_c_encode_basestring_ascii(self):
|
||||
if not simplejson.encoder.c_encode_basestring_ascii:
|
||||
return
|
||||
self._test_encode_basestring_ascii(simplejson.encoder.c_encode_basestring_ascii)
|
||||
|
||||
def _test_encode_basestring_ascii(self, encode_basestring_ascii):
|
||||
fname = encode_basestring_ascii.__name__
|
||||
for input_string, expect in CASES:
|
||||
result = encode_basestring_ascii(input_string)
|
||||
#self.assertEquals(result, expect,
|
||||
# '{0!r} != {1!r} for {2}({3!r})'.format(
|
||||
# result, expect, fname, input_string))
|
||||
self.assertEquals(result, expect,
|
||||
'%r != %r for %s(%r)' % (result, expect, fname, input_string))
|
||||
@@ -0,0 +1,32 @@
|
||||
import unittest
|
||||
|
||||
import simplejson.decoder
|
||||
import simplejson.encoder
|
||||
|
||||
|
||||
class TestEncodeForHTML(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.decoder = simplejson.decoder.JSONDecoder()
|
||||
self.encoder = simplejson.encoder.JSONEncoderForHTML()
|
||||
|
||||
def test_basic_encode(self):
|
||||
self.assertEqual(r'"\u0026"', self.encoder.encode('&'))
|
||||
self.assertEqual(r'"\u003c"', self.encoder.encode('<'))
|
||||
self.assertEqual(r'"\u003e"', self.encoder.encode('>'))
|
||||
|
||||
def test_basic_roundtrip(self):
|
||||
for char in '&<>':
|
||||
self.assertEqual(
|
||||
char, self.decoder.decode(
|
||||
self.encoder.encode(char)))
|
||||
|
||||
def test_prevent_script_breakout(self):
|
||||
bad_string = '</script><script>alert("gotcha")</script>'
|
||||
self.assertEqual(
|
||||
r'"\u003c/script\u003e\u003cscript\u003e'
|
||||
r'alert(\"gotcha\")\u003c/script\u003e"',
|
||||
self.encoder.encode(bad_string))
|
||||
self.assertEqual(
|
||||
bad_string, self.decoder.decode(
|
||||
self.encoder.encode(bad_string)))
|
||||
@@ -0,0 +1,91 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
# Fri Dec 30 18:57:26 2005
|
||||
JSONDOCS = [
|
||||
# http://json.org/JSON_checker/test/fail1.json
|
||||
'"A JSON payload should be an object or array, not a string."',
|
||||
# http://json.org/JSON_checker/test/fail2.json
|
||||
'["Unclosed array"',
|
||||
# http://json.org/JSON_checker/test/fail3.json
|
||||
'{unquoted_key: "keys must be quoted}',
|
||||
# http://json.org/JSON_checker/test/fail4.json
|
||||
'["extra comma",]',
|
||||
# http://json.org/JSON_checker/test/fail5.json
|
||||
'["double extra comma",,]',
|
||||
# http://json.org/JSON_checker/test/fail6.json
|
||||
'[ , "<-- missing value"]',
|
||||
# http://json.org/JSON_checker/test/fail7.json
|
||||
'["Comma after the close"],',
|
||||
# http://json.org/JSON_checker/test/fail8.json
|
||||
'["Extra close"]]',
|
||||
# http://json.org/JSON_checker/test/fail9.json
|
||||
'{"Extra comma": true,}',
|
||||
# http://json.org/JSON_checker/test/fail10.json
|
||||
'{"Extra value after close": true} "misplaced quoted value"',
|
||||
# http://json.org/JSON_checker/test/fail11.json
|
||||
'{"Illegal expression": 1 + 2}',
|
||||
# http://json.org/JSON_checker/test/fail12.json
|
||||
'{"Illegal invocation": alert()}',
|
||||
# http://json.org/JSON_checker/test/fail13.json
|
||||
'{"Numbers cannot have leading zeroes": 013}',
|
||||
# http://json.org/JSON_checker/test/fail14.json
|
||||
'{"Numbers cannot be hex": 0x14}',
|
||||
# http://json.org/JSON_checker/test/fail15.json
|
||||
'["Illegal backslash escape: \\x15"]',
|
||||
# http://json.org/JSON_checker/test/fail16.json
|
||||
'["Illegal backslash escape: \\\'"]',
|
||||
# http://json.org/JSON_checker/test/fail17.json
|
||||
'["Illegal backslash escape: \\017"]',
|
||||
# http://json.org/JSON_checker/test/fail18.json
|
||||
'[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]',
|
||||
# http://json.org/JSON_checker/test/fail19.json
|
||||
'{"Missing colon" null}',
|
||||
# http://json.org/JSON_checker/test/fail20.json
|
||||
'{"Double colon":: null}',
|
||||
# http://json.org/JSON_checker/test/fail21.json
|
||||
'{"Comma instead of colon", null}',
|
||||
# http://json.org/JSON_checker/test/fail22.json
|
||||
'["Colon instead of comma": false]',
|
||||
# http://json.org/JSON_checker/test/fail23.json
|
||||
'["Bad value", truth]',
|
||||
# http://json.org/JSON_checker/test/fail24.json
|
||||
"['single quote']",
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=3
|
||||
u'["A\u001FZ control characters in string"]',
|
||||
]
|
||||
|
||||
SKIPS = {
|
||||
1: "why not have a string payload?",
|
||||
18: "spec doesn't specify any nesting limitations",
|
||||
}
|
||||
|
||||
class TestFail(TestCase):
|
||||
def test_failures(self):
|
||||
for idx, doc in enumerate(JSONDOCS):
|
||||
idx = idx + 1
|
||||
if idx in SKIPS:
|
||||
json.loads(doc)
|
||||
continue
|
||||
try:
|
||||
json.loads(doc)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
else:
|
||||
#self.fail("Expected failure for fail{0}.json: {1!r}".format(idx, doc))
|
||||
self.fail("Expected failure for fail%d.json: %r" % (idx, doc))
|
||||
|
||||
def test_array_decoder_issue46(self):
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=46
|
||||
for doc in [u'[,]', '[,]']:
|
||||
try:
|
||||
json.loads(doc)
|
||||
except json.JSONDecodeError, e:
|
||||
self.assertEquals(e.pos, 1)
|
||||
self.assertEquals(e.lineno, 1)
|
||||
self.assertEquals(e.colno, 1)
|
||||
except Exception, e:
|
||||
self.fail("Unexpected exception raised %r %s" % (e, e))
|
||||
else:
|
||||
self.fail("Unexpected success parsing '[,]'")
|
||||
@@ -0,0 +1,19 @@
|
||||
import math
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
class TestFloat(TestCase):
|
||||
def test_floats(self):
|
||||
for num in [1617161771.7650001, math.pi, math.pi**100,
|
||||
math.pi**-100, 3.1]:
|
||||
self.assertEquals(float(json.dumps(num)), num)
|
||||
self.assertEquals(json.loads(json.dumps(num)), num)
|
||||
self.assertEquals(json.loads(unicode(json.dumps(num))), num)
|
||||
|
||||
def test_ints(self):
|
||||
for num in [1, 1L, 1<<32, 1<<64]:
|
||||
self.assertEquals(json.dumps(num), str(num))
|
||||
self.assertEquals(int(json.dumps(num)), num)
|
||||
self.assertEquals(json.loads(json.dumps(num)), num)
|
||||
self.assertEquals(json.loads(unicode(json.dumps(num))), num)
|
||||
@@ -0,0 +1,53 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
import textwrap
|
||||
|
||||
class TestIndent(TestCase):
|
||||
def test_indent(self):
|
||||
h = [['blorpie'], ['whoops'], [], 'd-shtaeou', 'd-nthiouh',
|
||||
'i-vhbjkhnth',
|
||||
{'nifty': 87}, {'field': 'yes', 'morefield': False} ]
|
||||
|
||||
expect = textwrap.dedent("""\
|
||||
[
|
||||
\t[
|
||||
\t\t"blorpie"
|
||||
\t],
|
||||
\t[
|
||||
\t\t"whoops"
|
||||
\t],
|
||||
\t[],
|
||||
\t"d-shtaeou",
|
||||
\t"d-nthiouh",
|
||||
\t"i-vhbjkhnth",
|
||||
\t{
|
||||
\t\t"nifty": 87
|
||||
\t},
|
||||
\t{
|
||||
\t\t"field": "yes",
|
||||
\t\t"morefield": false
|
||||
\t}
|
||||
]""")
|
||||
|
||||
|
||||
d1 = json.dumps(h)
|
||||
d2 = json.dumps(h, indent='\t', sort_keys=True, separators=(',', ': '))
|
||||
d3 = json.dumps(h, indent=' ', sort_keys=True, separators=(',', ': '))
|
||||
d4 = json.dumps(h, indent=2, sort_keys=True, separators=(',', ': '))
|
||||
|
||||
h1 = json.loads(d1)
|
||||
h2 = json.loads(d2)
|
||||
h3 = json.loads(d3)
|
||||
h4 = json.loads(d4)
|
||||
|
||||
self.assertEquals(h1, h)
|
||||
self.assertEquals(h2, h)
|
||||
self.assertEquals(h3, h)
|
||||
self.assertEquals(h4, h)
|
||||
self.assertEquals(d3, expect.replace('\t', ' '))
|
||||
self.assertEquals(d4, expect.replace('\t', ' '))
|
||||
# NOTE: Python 2.4 textwrap.dedent converts tabs to spaces,
|
||||
# so the following is expected to fail. Python 2.4 is not a
|
||||
# supported platform in simplejson 2.1.0+.
|
||||
self.assertEquals(d2, expect)
|
||||
@@ -0,0 +1,76 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
# from http://json.org/JSON_checker/test/pass1.json
|
||||
JSON = r'''
|
||||
[
|
||||
"JSON Test Pattern pass1",
|
||||
{"object with 1 member":["array with 1 element"]},
|
||||
{},
|
||||
[],
|
||||
-42,
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
{
|
||||
"integer": 1234567890,
|
||||
"real": -9876.543210,
|
||||
"e": 0.123456789e-12,
|
||||
"E": 1.234567890E+34,
|
||||
"": 23456789012E666,
|
||||
"zero": 0,
|
||||
"one": 1,
|
||||
"space": " ",
|
||||
"quote": "\"",
|
||||
"backslash": "\\",
|
||||
"controls": "\b\f\n\r\t",
|
||||
"slash": "/ & \/",
|
||||
"alpha": "abcdefghijklmnopqrstuvwyz",
|
||||
"ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
|
||||
"digit": "0123456789",
|
||||
"special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
|
||||
"hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
|
||||
"true": true,
|
||||
"false": false,
|
||||
"null": null,
|
||||
"array":[ ],
|
||||
"object":{ },
|
||||
"address": "50 St. James Street",
|
||||
"url": "http://www.JSON.org/",
|
||||
"comment": "// /* <!-- --",
|
||||
"# -- --> */": " ",
|
||||
" s p a c e d " :[1,2 , 3
|
||||
|
||||
,
|
||||
|
||||
4 , 5 , 6 ,7 ],
|
||||
"compact": [1,2,3,4,5,6,7],
|
||||
"jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
|
||||
"quotes": "" \u0022 %22 0x22 034 "",
|
||||
"\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
|
||||
: "A key can be any string"
|
||||
},
|
||||
0.5 ,98.6
|
||||
,
|
||||
99.44
|
||||
,
|
||||
|
||||
1066
|
||||
|
||||
|
||||
,"rosebud"]
|
||||
'''
|
||||
|
||||
class TestPass1(TestCase):
|
||||
def test_parse(self):
|
||||
# test in/out equivalence and parsing
|
||||
res = json.loads(JSON)
|
||||
out = json.dumps(res)
|
||||
self.assertEquals(res, json.loads(out))
|
||||
try:
|
||||
json.dumps(res, allow_nan=False)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.fail("23456789012E666 should be out of range")
|
||||
@@ -0,0 +1,14 @@
|
||||
from unittest import TestCase
|
||||
import simplejson as json
|
||||
|
||||
# from http://json.org/JSON_checker/test/pass2.json
|
||||
JSON = r'''
|
||||
[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]
|
||||
'''
|
||||
|
||||
class TestPass2(TestCase):
|
||||
def test_parse(self):
|
||||
# test in/out equivalence and parsing
|
||||
res = json.loads(JSON)
|
||||
out = json.dumps(res)
|
||||
self.assertEquals(res, json.loads(out))
|
||||
@@ -0,0 +1,20 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
# from http://json.org/JSON_checker/test/pass3.json
|
||||
JSON = r'''
|
||||
{
|
||||
"JSON Test Pattern pass3": {
|
||||
"The outermost value": "must be an object or array.",
|
||||
"In this test": "It is an object."
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
class TestPass3(TestCase):
|
||||
def test_parse(self):
|
||||
# test in/out equivalence and parsing
|
||||
res = json.loads(JSON)
|
||||
out = json.dumps(res)
|
||||
self.assertEquals(res, json.loads(out))
|
||||
@@ -0,0 +1,67 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
class JSONTestObject:
|
||||
pass
|
||||
|
||||
|
||||
class RecursiveJSONEncoder(json.JSONEncoder):
|
||||
recurse = False
|
||||
def default(self, o):
|
||||
if o is JSONTestObject:
|
||||
if self.recurse:
|
||||
return [JSONTestObject]
|
||||
else:
|
||||
return 'JSONTestObject'
|
||||
return json.JSONEncoder.default(o)
|
||||
|
||||
|
||||
class TestRecursion(TestCase):
|
||||
def test_listrecursion(self):
|
||||
x = []
|
||||
x.append(x)
|
||||
try:
|
||||
json.dumps(x)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.fail("didn't raise ValueError on list recursion")
|
||||
x = []
|
||||
y = [x]
|
||||
x.append(y)
|
||||
try:
|
||||
json.dumps(x)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.fail("didn't raise ValueError on alternating list recursion")
|
||||
y = []
|
||||
x = [y, y]
|
||||
# ensure that the marker is cleared
|
||||
json.dumps(x)
|
||||
|
||||
def test_dictrecursion(self):
|
||||
x = {}
|
||||
x["test"] = x
|
||||
try:
|
||||
json.dumps(x)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.fail("didn't raise ValueError on dict recursion")
|
||||
x = {}
|
||||
y = {"a": x, "b": x}
|
||||
# ensure that the marker is cleared
|
||||
json.dumps(x)
|
||||
|
||||
def test_defaultrecursion(self):
|
||||
enc = RecursiveJSONEncoder()
|
||||
self.assertEquals(enc.encode(JSONTestObject), '"JSONTestObject"')
|
||||
enc.recurse = True
|
||||
try:
|
||||
enc.encode(JSONTestObject)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.fail("didn't raise ValueError on default recursion")
|
||||
@@ -0,0 +1,117 @@
|
||||
import sys
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
import simplejson.decoder
|
||||
|
||||
class TestScanString(TestCase):
|
||||
def test_py_scanstring(self):
|
||||
self._test_scanstring(simplejson.decoder.py_scanstring)
|
||||
|
||||
def test_c_scanstring(self):
|
||||
if not simplejson.decoder.c_scanstring:
|
||||
return
|
||||
self._test_scanstring(simplejson.decoder.c_scanstring)
|
||||
|
||||
def _test_scanstring(self, scanstring):
|
||||
self.assertEquals(
|
||||
scanstring('"z\\ud834\\udd20x"', 1, None, True),
|
||||
(u'z\U0001d120x', 16))
|
||||
|
||||
if sys.maxunicode == 65535:
|
||||
self.assertEquals(
|
||||
scanstring(u'"z\U0001d120x"', 1, None, True),
|
||||
(u'z\U0001d120x', 6))
|
||||
else:
|
||||
self.assertEquals(
|
||||
scanstring(u'"z\U0001d120x"', 1, None, True),
|
||||
(u'z\U0001d120x', 5))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('"\\u007b"', 1, None, True),
|
||||
(u'{', 8))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('"A JSON payload should be an object or array, not a string."', 1, None, True),
|
||||
(u'A JSON payload should be an object or array, not a string.', 60))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('["Unclosed array"', 2, None, True),
|
||||
(u'Unclosed array', 17))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('["extra comma",]', 2, None, True),
|
||||
(u'extra comma', 14))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('["double extra comma",,]', 2, None, True),
|
||||
(u'double extra comma', 21))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('["Comma after the close"],', 2, None, True),
|
||||
(u'Comma after the close', 24))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('["Extra close"]]', 2, None, True),
|
||||
(u'Extra close', 14))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Extra comma": true,}', 2, None, True),
|
||||
(u'Extra comma', 14))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Extra value after close": true} "misplaced quoted value"', 2, None, True),
|
||||
(u'Extra value after close', 26))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Illegal expression": 1 + 2}', 2, None, True),
|
||||
(u'Illegal expression', 21))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Illegal invocation": alert()}', 2, None, True),
|
||||
(u'Illegal invocation', 21))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Numbers cannot have leading zeroes": 013}', 2, None, True),
|
||||
(u'Numbers cannot have leading zeroes', 37))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Numbers cannot be hex": 0x14}', 2, None, True),
|
||||
(u'Numbers cannot be hex', 24))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]', 21, None, True),
|
||||
(u'Too deep', 30))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Missing colon" null}', 2, None, True),
|
||||
(u'Missing colon', 16))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Double colon":: null}', 2, None, True),
|
||||
(u'Double colon', 15))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('{"Comma instead of colon", null}', 2, None, True),
|
||||
(u'Comma instead of colon', 25))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('["Colon instead of comma": false]', 2, None, True),
|
||||
(u'Colon instead of comma', 25))
|
||||
|
||||
self.assertEquals(
|
||||
scanstring('["Bad value", truth]', 2, None, True),
|
||||
(u'Bad value', 12))
|
||||
|
||||
def test_issue3623(self):
|
||||
self.assertRaises(ValueError, json.decoder.scanstring, "xxx", 1,
|
||||
"xxx")
|
||||
self.assertRaises(UnicodeDecodeError,
|
||||
json.encoder.encode_basestring_ascii, "xx\xff")
|
||||
|
||||
def test_overflow(self):
|
||||
# Python 2.5 does not have maxsize
|
||||
maxsize = getattr(sys, 'maxsize', sys.maxint)
|
||||
self.assertRaises(OverflowError, json.decoder.scanstring, "xxx",
|
||||
maxsize + 1)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import textwrap
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
|
||||
class TestSeparators(TestCase):
|
||||
def test_separators(self):
|
||||
h = [['blorpie'], ['whoops'], [], 'd-shtaeou', 'd-nthiouh', 'i-vhbjkhnth',
|
||||
{'nifty': 87}, {'field': 'yes', 'morefield': False} ]
|
||||
|
||||
expect = textwrap.dedent("""\
|
||||
[
|
||||
[
|
||||
"blorpie"
|
||||
] ,
|
||||
[
|
||||
"whoops"
|
||||
] ,
|
||||
[] ,
|
||||
"d-shtaeou" ,
|
||||
"d-nthiouh" ,
|
||||
"i-vhbjkhnth" ,
|
||||
{
|
||||
"nifty" : 87
|
||||
} ,
|
||||
{
|
||||
"field" : "yes" ,
|
||||
"morefield" : false
|
||||
}
|
||||
]""")
|
||||
|
||||
|
||||
d1 = json.dumps(h)
|
||||
d2 = json.dumps(h, indent=' ', sort_keys=True, separators=(' ,', ' : '))
|
||||
|
||||
h1 = json.loads(d1)
|
||||
h2 = json.loads(d2)
|
||||
|
||||
self.assertEquals(h1, h)
|
||||
self.assertEquals(h2, h)
|
||||
self.assertEquals(d2, expect)
|
||||
@@ -0,0 +1,21 @@
|
||||
import decimal
|
||||
from unittest import TestCase
|
||||
|
||||
from simplejson import decoder, encoder, scanner
|
||||
|
||||
def has_speedups():
|
||||
return encoder.c_make_encoder is not None
|
||||
|
||||
class TestDecode(TestCase):
|
||||
def test_make_scanner(self):
|
||||
if not has_speedups():
|
||||
return
|
||||
self.assertRaises(AttributeError, scanner.c_make_scanner, 1)
|
||||
|
||||
def test_make_encoder(self):
|
||||
if not has_speedups():
|
||||
return
|
||||
self.assertRaises(TypeError, encoder.c_make_encoder,
|
||||
None,
|
||||
"\xCD\x7D\x3D\x4E\x12\x4C\xF9\x79\xD7\x52\xBA\x82\xF2\x27\x4A\x7D\xA0\xCA\x75",
|
||||
None)
|
||||
@@ -0,0 +1,99 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import simplejson as json
|
||||
|
||||
class TestUnicode(TestCase):
|
||||
def test_encoding1(self):
|
||||
encoder = json.JSONEncoder(encoding='utf-8')
|
||||
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
|
||||
s = u.encode('utf-8')
|
||||
ju = encoder.encode(u)
|
||||
js = encoder.encode(s)
|
||||
self.assertEquals(ju, js)
|
||||
|
||||
def test_encoding2(self):
|
||||
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
|
||||
s = u.encode('utf-8')
|
||||
ju = json.dumps(u, encoding='utf-8')
|
||||
js = json.dumps(s, encoding='utf-8')
|
||||
self.assertEquals(ju, js)
|
||||
|
||||
def test_encoding3(self):
|
||||
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
|
||||
j = json.dumps(u)
|
||||
self.assertEquals(j, '"\\u03b1\\u03a9"')
|
||||
|
||||
def test_encoding4(self):
|
||||
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
|
||||
j = json.dumps([u])
|
||||
self.assertEquals(j, '["\\u03b1\\u03a9"]')
|
||||
|
||||
def test_encoding5(self):
|
||||
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
|
||||
j = json.dumps(u, ensure_ascii=False)
|
||||
self.assertEquals(j, u'"' + u + u'"')
|
||||
|
||||
def test_encoding6(self):
|
||||
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
|
||||
j = json.dumps([u], ensure_ascii=False)
|
||||
self.assertEquals(j, u'["' + u + u'"]')
|
||||
|
||||
def test_big_unicode_encode(self):
|
||||
u = u'\U0001d120'
|
||||
self.assertEquals(json.dumps(u), '"\\ud834\\udd20"')
|
||||
self.assertEquals(json.dumps(u, ensure_ascii=False), u'"\U0001d120"')
|
||||
|
||||
def test_big_unicode_decode(self):
|
||||
u = u'z\U0001d120x'
|
||||
self.assertEquals(json.loads('"' + u + '"'), u)
|
||||
self.assertEquals(json.loads('"z\\ud834\\udd20x"'), u)
|
||||
|
||||
def test_unicode_decode(self):
|
||||
for i in range(0, 0xd7ff):
|
||||
u = unichr(i)
|
||||
#s = '"\\u{0:04x}"'.format(i)
|
||||
s = '"\\u%04x"' % (i,)
|
||||
self.assertEquals(json.loads(s), u)
|
||||
|
||||
def test_object_pairs_hook_with_unicode(self):
|
||||
s = u'{"xkd":1, "kcw":2, "art":3, "hxm":4, "qrt":5, "pad":6, "hoy":7}'
|
||||
p = [(u"xkd", 1), (u"kcw", 2), (u"art", 3), (u"hxm", 4),
|
||||
(u"qrt", 5), (u"pad", 6), (u"hoy", 7)]
|
||||
self.assertEqual(json.loads(s), eval(s))
|
||||
self.assertEqual(json.loads(s, object_pairs_hook=lambda x: x), p)
|
||||
od = json.loads(s, object_pairs_hook=json.OrderedDict)
|
||||
self.assertEqual(od, json.OrderedDict(p))
|
||||
self.assertEqual(type(od), json.OrderedDict)
|
||||
# the object_pairs_hook takes priority over the object_hook
|
||||
self.assertEqual(json.loads(s,
|
||||
object_pairs_hook=json.OrderedDict,
|
||||
object_hook=lambda x: None),
|
||||
json.OrderedDict(p))
|
||||
|
||||
|
||||
def test_default_encoding(self):
|
||||
self.assertEquals(json.loads(u'{"a": "\xe9"}'.encode('utf-8')),
|
||||
{'a': u'\xe9'})
|
||||
|
||||
def test_unicode_preservation(self):
|
||||
self.assertEquals(type(json.loads(u'""')), unicode)
|
||||
self.assertEquals(type(json.loads(u'"a"')), unicode)
|
||||
self.assertEquals(type(json.loads(u'["a"]')[0]), unicode)
|
||||
|
||||
def test_ensure_ascii_false_returns_unicode(self):
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=48
|
||||
self.assertEquals(type(json.dumps([], ensure_ascii=False)), unicode)
|
||||
self.assertEquals(type(json.dumps(0, ensure_ascii=False)), unicode)
|
||||
self.assertEquals(type(json.dumps({}, ensure_ascii=False)), unicode)
|
||||
self.assertEquals(type(json.dumps("", ensure_ascii=False)), unicode)
|
||||
|
||||
def test_ensure_ascii_false_bytestring_encoding(self):
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=48
|
||||
doc1 = {u'quux': 'Arr\xc3\xaat sur images'}
|
||||
doc2 = {u'quux': u'Arr\xeat sur images'}
|
||||
doc_ascii = '{"quux": "Arr\\u00eat sur images"}'
|
||||
doc_unicode = u'{"quux": "Arr\xeat sur images"}'
|
||||
self.assertEquals(json.dumps(doc1), doc_ascii)
|
||||
self.assertEquals(json.dumps(doc2), doc_ascii)
|
||||
self.assertEquals(json.dumps(doc1, ensure_ascii=False), doc_unicode)
|
||||
self.assertEquals(json.dumps(doc2, ensure_ascii=False), doc_unicode)
|
||||
@@ -0,0 +1,39 @@
|
||||
r"""Command-line tool to validate and pretty-print JSON
|
||||
|
||||
Usage::
|
||||
|
||||
$ echo '{"json":"obj"}' | python -m simplejson.tool
|
||||
{
|
||||
"json": "obj"
|
||||
}
|
||||
$ echo '{ 1.2:3.4}' | python -m simplejson.tool
|
||||
Expecting property name: line 1 column 2 (char 2)
|
||||
|
||||
"""
|
||||
import sys
|
||||
import simplejson as json
|
||||
|
||||
def main():
|
||||
if len(sys.argv) == 1:
|
||||
infile = sys.stdin
|
||||
outfile = sys.stdout
|
||||
elif len(sys.argv) == 2:
|
||||
infile = open(sys.argv[1], 'rb')
|
||||
outfile = sys.stdout
|
||||
elif len(sys.argv) == 3:
|
||||
infile = open(sys.argv[1], 'rb')
|
||||
outfile = open(sys.argv[2], 'wb')
|
||||
else:
|
||||
raise SystemExit(sys.argv[0] + " [infile [outfile]]")
|
||||
try:
|
||||
obj = json.load(infile,
|
||||
object_pairs_hook=json.OrderedDict,
|
||||
use_decimal=True)
|
||||
except ValueError, e:
|
||||
raise SystemExit(e)
|
||||
json.dump(obj, outfile, sort_keys=True, indent=' ', use_decimal=True)
|
||||
outfile.write('\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,262 @@
|
||||
# -*- coding: windows-1251 -*-
|
||||
|
||||
# Portions are Copyright (C) 2005 Roman V. Kiseliov
|
||||
# Portions are Copyright (c) 2004 Evgeny Filatov <fufff@users.sourceforge.net>
|
||||
# Portions are Copyright (c) 2002-2004 John McNamara (Perl Spreadsheet::WriteExcel)
|
||||
|
||||
from BIFFRecords import BiffRecord
|
||||
from struct import *
|
||||
|
||||
|
||||
def _size_col(sheet, col):
|
||||
return sheet.col_width(col)
|
||||
|
||||
|
||||
def _size_row(sheet, row):
|
||||
return sheet.row_height(row)
|
||||
|
||||
|
||||
def _position_image(sheet, row_start, col_start, x1, y1, width, height):
|
||||
"""Calculate the vertices that define the position of the image as required by
|
||||
the OBJ record.
|
||||
|
||||
+------------+------------+
|
||||
| A | B |
|
||||
+-----+------------+------------+
|
||||
| |(x1,y1) | |
|
||||
| 1 |(A1)._______|______ |
|
||||
| | | | |
|
||||
| | | | |
|
||||
+-----+----| BITMAP |-----+
|
||||
| | | | |
|
||||
| 2 | |______________. |
|
||||
| | | (B2)|
|
||||
| | | (x2,y2)|
|
||||
+---- +------------+------------+
|
||||
|
||||
Example of a bitmap that covers some of the area from cell A1 to cell B2.
|
||||
|
||||
Based on the width and height of the bitmap we need to calculate 8 vars:
|
||||
col_start, row_start, col_end, row_end, x1, y1, x2, y2.
|
||||
The width and height of the cells are also variable and have to be taken into
|
||||
account.
|
||||
The values of col_start and row_start are passed in from the calling
|
||||
function. The values of col_end and row_end are calculated by subtracting
|
||||
the width and height of the bitmap from the width and height of the
|
||||
underlying cells.
|
||||
The vertices are expressed as a percentage of the underlying cell width as
|
||||
follows (rhs values are in pixels):
|
||||
|
||||
x1 = X / W *1024
|
||||
y1 = Y / H *256
|
||||
x2 = (X-1) / W *1024
|
||||
y2 = (Y-1) / H *256
|
||||
|
||||
Where: X is distance from the left side of the underlying cell
|
||||
Y is distance from the top of the underlying cell
|
||||
W is the width of the cell
|
||||
H is the height of the cell
|
||||
|
||||
Note: the SDK incorrectly states that the height should be expressed as a
|
||||
percentage of 1024.
|
||||
|
||||
col_start - Col containing upper left corner of object
|
||||
row_start - Row containing top left corner of object
|
||||
x1 - Distance to left side of object
|
||||
y1 - Distance to top of object
|
||||
width - Width of image frame
|
||||
height - Height of image frame
|
||||
|
||||
"""
|
||||
# Adjust start column for offsets that are greater than the col width
|
||||
while x1 >= _size_col(sheet, col_start):
|
||||
x1 -= _size_col(sheet, col_start)
|
||||
col_start += 1
|
||||
# Adjust start row for offsets that are greater than the row height
|
||||
while y1 >= _size_row(sheet, row_start):
|
||||
y1 -= _size_row(sheet, row_start)
|
||||
row_start += 1
|
||||
# Initialise end cell to the same as the start cell
|
||||
row_end = row_start # Row containing bottom right corner of object
|
||||
col_end = col_start # Col containing lower right corner of object
|
||||
width = width + x1 - 1
|
||||
height = height + y1 - 1
|
||||
# Subtract the underlying cell widths to find the end cell of the image
|
||||
while (width >= _size_col(sheet, col_end)):
|
||||
width -= _size_col(sheet, col_end)
|
||||
col_end += 1
|
||||
# Subtract the underlying cell heights to find the end cell of the image
|
||||
while (height >= _size_row(sheet, row_end)):
|
||||
height -= _size_row(sheet, row_end)
|
||||
row_end += 1
|
||||
# Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
|
||||
# with zero height or width.
|
||||
if ((_size_col(sheet, col_start) == 0) or (_size_col(sheet, col_end) == 0)
|
||||
or (_size_row(sheet, row_start) == 0) or (_size_row(sheet, row_end) == 0)):
|
||||
return
|
||||
# Convert the pixel values to the percentage value expected by Excel
|
||||
x1 = int(float(x1) / _size_col(sheet, col_start) * 1024)
|
||||
y1 = int(float(y1) / _size_row(sheet, row_start) * 256)
|
||||
# Distance to right side of object
|
||||
x2 = int(float(width) / _size_col(sheet, col_end) * 1024)
|
||||
# Distance to bottom of object
|
||||
y2 = int(float(height) / _size_row(sheet, row_end) * 256)
|
||||
return (col_start, x1, row_start, y1, col_end, x2, row_end, y2)
|
||||
|
||||
|
||||
class ObjBmpRecord(BiffRecord):
|
||||
_REC_ID = 0x005D # Record identifier
|
||||
|
||||
def __init__(self, row, col, sheet, im_data_bmp, x, y, scale_x, scale_y):
|
||||
# Scale the frame of the image.
|
||||
width = im_data_bmp.width * scale_x
|
||||
height = im_data_bmp.height * scale_y
|
||||
|
||||
# Calculate the vertices of the image and write the OBJ record
|
||||
coordinates = _position_image(sheet, row, col, x, y, width, height)
|
||||
# print coordinates
|
||||
col_start, x1, row_start, y1, col_end, x2, row_end, y2 = coordinates
|
||||
|
||||
"""Store the OBJ record that precedes an IMDATA record. This could be generalise
|
||||
to support other Excel objects.
|
||||
|
||||
"""
|
||||
cObj = 0x0001 # Count of objects in file (set to 1)
|
||||
OT = 0x0008 # Object type. 8 = Picture
|
||||
id = 0x0001 # Object ID
|
||||
grbit = 0x0614 # Option flags
|
||||
colL = col_start # Col containing upper left corner of object
|
||||
dxL = x1 # Distance from left side of cell
|
||||
rwT = row_start # Row containing top left corner of object
|
||||
dyT = y1 # Distance from top of cell
|
||||
colR = col_end # Col containing lower right corner of object
|
||||
dxR = x2 # Distance from right of cell
|
||||
rwB = row_end # Row containing bottom right corner of object
|
||||
dyB = y2 # Distance from bottom of cell
|
||||
cbMacro = 0x0000 # Length of FMLA structure
|
||||
Reserved1 = 0x0000 # Reserved
|
||||
Reserved2 = 0x0000 # Reserved
|
||||
icvBack = 0x09 # Background colour
|
||||
icvFore = 0x09 # Foreground colour
|
||||
fls = 0x00 # Fill pattern
|
||||
fAuto = 0x00 # Automatic fill
|
||||
icv = 0x08 # Line colour
|
||||
lns = 0xff # Line style
|
||||
lnw = 0x01 # Line weight
|
||||
fAutoB = 0x00 # Automatic border
|
||||
frs = 0x0000 # Frame style
|
||||
cf = 0x0009 # Image format, 9 = bitmap
|
||||
Reserved3 = 0x0000 # Reserved
|
||||
cbPictFmla = 0x0000 # Length of FMLA structure
|
||||
Reserved4 = 0x0000 # Reserved
|
||||
grbit2 = 0x0001 # Option flags
|
||||
Reserved5 = 0x0000 # Reserved
|
||||
|
||||
data = pack("<L", cObj)
|
||||
data += pack("<H", OT)
|
||||
data += pack("<H", id)
|
||||
data += pack("<H", grbit)
|
||||
data += pack("<H", colL)
|
||||
data += pack("<H", dxL)
|
||||
data += pack("<H", rwT)
|
||||
data += pack("<H", dyT)
|
||||
data += pack("<H", colR)
|
||||
data += pack("<H", dxR)
|
||||
data += pack("<H", rwB)
|
||||
data += pack("<H", dyB)
|
||||
data += pack("<H", cbMacro)
|
||||
data += pack("<L", Reserved1)
|
||||
data += pack("<H", Reserved2)
|
||||
data += pack("<B", icvBack)
|
||||
data += pack("<B", icvFore)
|
||||
data += pack("<B", fls)
|
||||
data += pack("<B", fAuto)
|
||||
data += pack("<B", icv)
|
||||
data += pack("<B", lns)
|
||||
data += pack("<B", lnw)
|
||||
data += pack("<B", fAutoB)
|
||||
data += pack("<H", frs)
|
||||
data += pack("<L", cf)
|
||||
data += pack("<H", Reserved3)
|
||||
data += pack("<H", cbPictFmla)
|
||||
data += pack("<H", Reserved4)
|
||||
data += pack("<H", grbit2)
|
||||
data += pack("<L", Reserved5)
|
||||
|
||||
self._rec_data = data
|
||||
|
||||
def _process_bitmap(bitmap):
|
||||
"""Convert a 24 bit bitmap into the modified internal format used by Windows.
|
||||
This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
|
||||
MSDN library.
|
||||
|
||||
"""
|
||||
# Open file and binmode the data in case the platform needs it.
|
||||
fh = file(bitmap, "rb")
|
||||
try:
|
||||
# Slurp the file into a string.
|
||||
data = fh.read()
|
||||
finally:
|
||||
fh.close()
|
||||
# Check that the file is big enough to be a bitmap.
|
||||
if len(data) <= 0x36:
|
||||
raise Exception("bitmap doesn't contain enough data.")
|
||||
# The first 2 bytes are used to identify the bitmap.
|
||||
if (data[:2] != "BM"):
|
||||
raise Exception("bitmap doesn't appear to to be a valid bitmap image.")
|
||||
# Remove bitmap data: ID.
|
||||
data = data[2:]
|
||||
# Read and remove the bitmap size. This is more reliable than reading
|
||||
# the data size at offset 0x22.
|
||||
#
|
||||
size = unpack("<L", data[:4])[0]
|
||||
size -= 0x36 # Subtract size of bitmap header.
|
||||
size += 0x0C # Add size of BIFF header.
|
||||
data = data[4:]
|
||||
# Remove bitmap data: reserved, offset, header length.
|
||||
data = data[12:]
|
||||
# Read and remove the bitmap width and height. Verify the sizes.
|
||||
width, height = unpack("<LL", data[:8])
|
||||
data = data[8:]
|
||||
if (width > 0xFFFF):
|
||||
raise Exception("bitmap: largest image width supported is 65k.")
|
||||
if (height > 0xFFFF):
|
||||
raise Exception("bitmap: largest image height supported is 65k.")
|
||||
# Read and remove the bitmap planes and bpp data. Verify them.
|
||||
planes, bitcount = unpack("<HH", data[:4])
|
||||
data = data[4:]
|
||||
if (bitcount != 24):
|
||||
raise Exception("bitmap isn't a 24bit true color bitmap.")
|
||||
if (planes != 1):
|
||||
raise Exception("bitmap: only 1 plane supported in bitmap image.")
|
||||
# Read and remove the bitmap compression. Verify compression.
|
||||
compression = unpack("<L", data[:4])[0]
|
||||
data = data[4:]
|
||||
if (compression != 0):
|
||||
raise Exception("bitmap: compression not supported in bitmap image.")
|
||||
# Remove bitmap data: data size, hres, vres, colours, imp. colours.
|
||||
data = data[20:]
|
||||
# Add the BITMAPCOREHEADER data
|
||||
header = pack("<LHHHH", 0x000c, width, height, 0x01, 0x18)
|
||||
data = header + data
|
||||
return (width, height, size, data)
|
||||
|
||||
|
||||
class ImDataBmpRecord(BiffRecord):
|
||||
_REC_ID = 0x007F
|
||||
|
||||
def __init__(self, filename):
|
||||
"""Insert a 24bit bitmap image in a worksheet. The main record required is
|
||||
IMDATA but it must be proceeded by a OBJ record to define its position.
|
||||
|
||||
"""
|
||||
BiffRecord.__init__(self)
|
||||
|
||||
self.width, self.height, self.size, data = _process_bitmap(filename)
|
||||
# Write the IMDATA record to store the bitmap data
|
||||
cf = 0x09
|
||||
env = 0x01
|
||||
lcb = self.size
|
||||
self._rec_data = pack("<HHL", cf, env, lcb) + data
|
||||
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
from struct import unpack, pack
|
||||
import BIFFRecords
|
||||
|
||||
class StrCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "sst_idx"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, sst_idx):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
self.sst_idx = sst_idx
|
||||
|
||||
def get_biff_data(self):
|
||||
# return BIFFRecords.LabelSSTRecord(self.rowx, self.colx, self.xf_idx, self.sst_idx).get()
|
||||
return pack('<5HL', 0x00FD, 10, self.rowx, self.colx, self.xf_idx, self.sst_idx)
|
||||
|
||||
class BlankCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
|
||||
def get_biff_data(self):
|
||||
# return BIFFRecords.BlankRecord(self.rowx, self.colx, self.xf_idx).get()
|
||||
return pack('<5H', 0x0201, 6, self.rowx, self.colx, self.xf_idx)
|
||||
|
||||
class MulBlankCell(object):
|
||||
__slots__ = ["rowx", "colx1", "colx2", "xf_idx"]
|
||||
|
||||
def __init__(self, rowx, colx1, colx2, xf_idx):
|
||||
self.rowx = rowx
|
||||
self.colx1 = colx1
|
||||
self.colx2 = colx2
|
||||
self.xf_idx = xf_idx
|
||||
|
||||
def get_biff_data(self):
|
||||
return BIFFRecords.MulBlankRecord(self.rowx,
|
||||
self.colx1, self.colx2, self.xf_idx).get()
|
||||
|
||||
class NumberCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "number"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, number):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
self.number = float(number)
|
||||
|
||||
def get_encoded_data(self):
|
||||
rk_encoded = 0
|
||||
num = self.number
|
||||
|
||||
# The four possible kinds of RK encoding are *not* mutually exclusive.
|
||||
# The 30-bit integer variety picks up the most.
|
||||
# In the code below, the four varieties are checked in descending order
|
||||
# of bangs per buck, or not at all.
|
||||
# SJM 2007-10-01
|
||||
|
||||
if -0x20000000 <= num < 0x20000000: # fits in 30-bit *signed* int
|
||||
inum = int(num)
|
||||
if inum == num: # survives round-trip
|
||||
# print "30-bit integer RK", inum, hex(inum)
|
||||
rk_encoded = 2 | (inum << 2)
|
||||
return 1, rk_encoded
|
||||
|
||||
temp = num * 100
|
||||
|
||||
if -0x20000000 <= temp < 0x20000000:
|
||||
# That was step 1: the coded value will fit in
|
||||
# a 30-bit signed integer.
|
||||
itemp = int(round(temp, 0))
|
||||
# That was step 2: "itemp" is the best candidate coded value.
|
||||
# Now for step 3: simulate the decoding,
|
||||
# to check for round-trip correctness.
|
||||
if itemp / 100.0 == num:
|
||||
# print "30-bit integer RK*100", itemp, hex(itemp)
|
||||
rk_encoded = 3 | (itemp << 2)
|
||||
return 1, rk_encoded
|
||||
|
||||
if 0: # Cost of extra pack+unpack not justified by tiny yield.
|
||||
packed = pack('<d', num)
|
||||
w01, w23 = unpack('<2i', packed)
|
||||
if not w01 and not(w23 & 3):
|
||||
# 34 lsb are 0
|
||||
# print "float RK", w23, hex(w23)
|
||||
return 1, w23
|
||||
|
||||
packed100 = pack('<d', temp)
|
||||
w01, w23 = unpack('<2i', packed100)
|
||||
if not w01 and not(w23 & 3):
|
||||
# 34 lsb are 0
|
||||
# print "float RK*100", w23, hex(w23)
|
||||
return 1, w23 | 1
|
||||
|
||||
#print "Number"
|
||||
#print
|
||||
return 0, pack('<5Hd', 0x0203, 14, self.rowx, self.colx, self.xf_idx, num)
|
||||
|
||||
def get_biff_data(self):
|
||||
isRK, value = self.get_encoded_data()
|
||||
if isRK:
|
||||
return pack('<5Hi', 0x27E, 10, self.rowx, self.colx, self.xf_idx, value)
|
||||
return value # NUMBER record already packed
|
||||
|
||||
class BooleanCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "number"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, number):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
self.number = number
|
||||
|
||||
def get_biff_data(self):
|
||||
return BIFFRecords.BoolErrRecord(self.rowx,
|
||||
self.colx, self.xf_idx, self.number, 0).get()
|
||||
|
||||
error_code_map = {
|
||||
0x00: 0, # Intersection of two cell ranges is empty
|
||||
0x07: 7, # Division by zero
|
||||
0x0F: 15, # Wrong type of operand
|
||||
0x17: 23, # Illegal or deleted cell reference
|
||||
0x1D: 29, # Wrong function or range name
|
||||
0x24: 36, # Value range overflow
|
||||
0x2A: 42, # Argument or function not available
|
||||
'#NULL!' : 0, # Intersection of two cell ranges is empty
|
||||
'#DIV/0!': 7, # Division by zero
|
||||
'#VALUE!': 36, # Wrong type of operand
|
||||
'#REF!' : 23, # Illegal or deleted cell reference
|
||||
'#NAME?' : 29, # Wrong function or range name
|
||||
'#NUM!' : 36, # Value range overflow
|
||||
'#N/A!' : 42, # Argument or function not available
|
||||
}
|
||||
|
||||
class ErrorCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "number"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, error_string_or_code):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
try:
|
||||
self.number = error_code_map[error_string_or_code]
|
||||
except KeyError:
|
||||
raise Exception('Illegal error value (%r)' % error_string_or_code)
|
||||
|
||||
def get_biff_data(self):
|
||||
return BIFFRecords.BoolErrRecord(self.rowx,
|
||||
self.colx, self.xf_idx, self.number, 1).get()
|
||||
|
||||
class FormulaCell(object):
|
||||
__slots__ = ["rowx", "colx", "xf_idx", "frmla", "calc_flags"]
|
||||
|
||||
def __init__(self, rowx, colx, xf_idx, frmla, calc_flags=0):
|
||||
self.rowx = rowx
|
||||
self.colx = colx
|
||||
self.xf_idx = xf_idx
|
||||
self.frmla = frmla
|
||||
self.calc_flags = calc_flags
|
||||
|
||||
def get_biff_data(self):
|
||||
return BIFFRecords.FormulaRecord(self.rowx,
|
||||
self.colx, self.xf_idx, self.frmla.rpn(), self.calc_flags).get()
|
||||
|
||||
# module-level function for *internal* use by the Row module
|
||||
|
||||
def _get_cells_biff_data_mul(rowx, cell_items):
|
||||
# Return the BIFF data for all cell records in the row.
|
||||
# Adjacent BLANK|RK records are combined into MUL(BLANK|RK) records.
|
||||
pieces = []
|
||||
nitems = len(cell_items)
|
||||
i = 0
|
||||
while i < nitems:
|
||||
icolx, icell = cell_items[i]
|
||||
if isinstance(icell, NumberCell):
|
||||
isRK, value = icell.get_encoded_data()
|
||||
if not isRK:
|
||||
pieces.append(value) # pre-packed NUMBER record
|
||||
i += 1
|
||||
continue
|
||||
muldata = [(value, icell.xf_idx)]
|
||||
target = NumberCell
|
||||
elif isinstance(icell, BlankCell):
|
||||
muldata = [icell.xf_idx]
|
||||
target = BlankCell
|
||||
else:
|
||||
pieces.append(icell.get_biff_data())
|
||||
i += 1
|
||||
continue
|
||||
lastcolx = icolx
|
||||
j = i
|
||||
packed_record = ''
|
||||
for j in xrange(i+1, nitems):
|
||||
jcolx, jcell = cell_items[j]
|
||||
if jcolx != lastcolx + 1:
|
||||
nexti = j
|
||||
break
|
||||
if not isinstance(jcell, target):
|
||||
nexti = j
|
||||
break
|
||||
if target == NumberCell:
|
||||
isRK, value = jcell.get_encoded_data()
|
||||
if not isRK:
|
||||
packed_record = value
|
||||
nexti = j + 1
|
||||
break
|
||||
muldata.append((value, jcell.xf_idx))
|
||||
else:
|
||||
muldata.append(jcell.xf_idx)
|
||||
lastcolx = jcolx
|
||||
else:
|
||||
nexti = j + 1
|
||||
if target == NumberCell:
|
||||
if lastcolx == icolx:
|
||||
# RK record
|
||||
value, xf_idx = muldata[0]
|
||||
pieces.append(pack('<5Hi', 0x027E, 10, rowx, icolx, xf_idx, value))
|
||||
else:
|
||||
# MULRK record
|
||||
nc = lastcolx - icolx + 1
|
||||
pieces.append(pack('<4H', 0x00BD, 6 * nc + 6, rowx, icolx))
|
||||
pieces.append(''.join([pack('<Hi', xf_idx, value) for value, xf_idx in muldata]))
|
||||
pieces.append(pack('<H', lastcolx))
|
||||
else:
|
||||
if lastcolx == icolx:
|
||||
# BLANK record
|
||||
xf_idx = muldata[0]
|
||||
pieces.append(pack('<5H', 0x0201, 6, rowx, icolx, xf_idx))
|
||||
else:
|
||||
# MULBLANK record
|
||||
nc = lastcolx - icolx + 1
|
||||
pieces.append(pack('<4H', 0x00BE, 2 * nc + 6, rowx, icolx))
|
||||
pieces.append(''.join([pack('<H', xf_idx) for xf_idx in muldata]))
|
||||
pieces.append(pack('<H', lastcolx))
|
||||
if packed_record:
|
||||
pieces.append(packed_record)
|
||||
i = nexti
|
||||
return ''.join(pieces)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
from BIFFRecords import ColInfoRecord
|
||||
|
||||
class Column(object):
|
||||
def __init__(self, colx, parent_sheet):
|
||||
if not(isinstance(colx, int) and 0 <= colx <= 255):
|
||||
raise ValueError("column index (%r) not an int in range(256)" % colx)
|
||||
self._index = colx
|
||||
self._parent = parent_sheet
|
||||
self._parent_wb = parent_sheet.get_parent()
|
||||
self._xf_index = 0x0F
|
||||
|
||||
self.width = 0x0B92
|
||||
self.hidden = 0
|
||||
self.level = 0
|
||||
self.collapse = 0
|
||||
|
||||
def set_style(self, style):
|
||||
self._xf_index = self._parent_wb.add_style(style)
|
||||
|
||||
def width_in_pixels(self):
|
||||
# *** Approximation ****
|
||||
return int(round(self.width * 0.0272 + 0.446, 0))
|
||||
|
||||
def get_biff_record(self):
|
||||
options = (self.hidden & 0x01) << 0
|
||||
options |= (self.level & 0x07) << 8
|
||||
options |= (self.collapse & 0x01) << 12
|
||||
|
||||
return ColInfoRecord(self._index, self._index, self.width, self._xf_index, options).get()
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,516 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
import sys
|
||||
import struct
|
||||
|
||||
class Reader:
|
||||
def __init__(self, filename, dump = False):
|
||||
self.dump = dump
|
||||
self.STREAMS = {}
|
||||
|
||||
doc = file(filename, 'rb').read()
|
||||
self.header, self.data = doc[0:512], doc[512:]
|
||||
del doc
|
||||
|
||||
self.__build_header()
|
||||
self.__build_MSAT()
|
||||
self.__build_SAT()
|
||||
self.__build_directory()
|
||||
self.__build_short_sectors_data()
|
||||
|
||||
if len(self.short_sectors_data) > 0:
|
||||
self.__build_SSAT()
|
||||
else:
|
||||
if self.dump and (self.total_ssat_sectors != 0 or self.ssat_start_sid != -2):
|
||||
print 'NOTE: header says that must be', self.total_ssat_sectors, 'short sectors'
|
||||
print 'NOTE: starting at', self.ssat_start_sid, 'sector'
|
||||
print 'NOTE: but file does not contains data in short sectors'
|
||||
self.ssat_start_sid = -2
|
||||
self.total_ssat_sectors = 0
|
||||
self.SSAT = [-2]
|
||||
|
||||
for dentry in self.dir_entry_list[1:]:
|
||||
(did,
|
||||
sz, name,
|
||||
t, c,
|
||||
did_left, did_right, did_root,
|
||||
dentry_start_sid,
|
||||
stream_size
|
||||
) = dentry
|
||||
stream_data = ''
|
||||
if stream_size > 0:
|
||||
if stream_size >= self.min_stream_size:
|
||||
args = (self.data, self.SAT, dentry_start_sid, self.sect_size)
|
||||
else:
|
||||
args = (self.short_sectors_data, self.SSAT, dentry_start_sid, self.short_sect_size)
|
||||
stream_data = self.get_stream_data(*args)
|
||||
|
||||
if name != '':
|
||||
# BAD IDEA: names may be equal. NEED use full paths...
|
||||
self.STREAMS[name] = stream_data
|
||||
|
||||
|
||||
def __build_header(self):
|
||||
self.doc_magic = self.header[0:8]
|
||||
|
||||
if self.doc_magic != '\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1':
|
||||
raise Exception, 'Not an OLE file.'
|
||||
|
||||
self.file_uid = self.header[8:24]
|
||||
self.rev_num = self.header[24:26]
|
||||
self.ver_num = self.header[26:28]
|
||||
self.byte_order = self.header[28:30]
|
||||
self.log2_sect_size, = struct.unpack('<H', self.header[30:32])
|
||||
self.log2_short_sect_size, = struct.unpack('<H', self.header[32:34])
|
||||
self.total_sat_sectors, = struct.unpack('<L', self.header[44:48])
|
||||
self.dir_start_sid, = struct.unpack('<l', self.header[48:52])
|
||||
self.min_stream_size, = struct.unpack('<L', self.header[56:60])
|
||||
self.ssat_start_sid, = struct.unpack('<l', self.header[60:64])
|
||||
self.total_ssat_sectors, = struct.unpack('<L', self.header[64:68])
|
||||
self.msat_start_sid, = struct.unpack('<l', self.header[68:72])
|
||||
self.total_msat_sectors, = struct.unpack('<L', self.header[72:76])
|
||||
|
||||
self.sect_size = 1 << self.log2_sect_size
|
||||
self.short_sect_size = 1 << self.log2_short_sect_size
|
||||
|
||||
if self.dump:
|
||||
print 'file magic: '
|
||||
print_bin_data(self.doc_magic)
|
||||
|
||||
print 'file uid: '
|
||||
print_bin_data(self.file_uid)
|
||||
|
||||
print 'revision number: '
|
||||
print_bin_data(self.rev_num)
|
||||
|
||||
print 'version number: '
|
||||
print_bin_data(self.ver_num)
|
||||
|
||||
print 'byte order: '
|
||||
print_bin_data(self.byte_order)
|
||||
|
||||
print 'sector size :', hex(self.sect_size), self.sect_size
|
||||
#print 'total sectors in file :', hex(self.total_sectors), self.total_sectors
|
||||
print 'short sector size :', hex(self.short_sect_size), self.short_sect_size
|
||||
print 'Total number of sectors used for the SAT :', hex(self.total_sat_sectors), self.total_sat_sectors
|
||||
print 'SID of first sector of the directory stream:', hex(self.dir_start_sid), self.dir_start_sid
|
||||
print 'Minimum size of a standard stream :', hex(self.min_stream_size), self.min_stream_size
|
||||
print 'SID of first sector of the SSAT :', hex(self.ssat_start_sid), self.ssat_start_sid
|
||||
print 'Total number of sectors used for the SSAT :', hex(self.total_ssat_sectors), self.total_ssat_sectors
|
||||
print 'SID of first additional sector of the MSAT :', hex(self.msat_start_sid), self.msat_start_sid
|
||||
print 'Total number of sectors used for the MSAT :', hex(self.total_msat_sectors), self.total_msat_sectors
|
||||
|
||||
|
||||
def __build_MSAT(self):
|
||||
self.MSAT = list(struct.unpack('<109l', self.header[76:]))
|
||||
|
||||
next = self.msat_start_sid
|
||||
while next > 0:
|
||||
msat_sector = struct.unpack('<128l', self.data[next*self.sect_size:(next+1)*self.sect_size])
|
||||
self.MSAT.extend(msat_sector[:127])
|
||||
next = msat_sector[-1]
|
||||
|
||||
if self.dump:
|
||||
print 'MSAT (header part): \n', self.MSAT[:109]
|
||||
print 'additional MSAT sectors: \n', self.MSAT[109:]
|
||||
|
||||
|
||||
def __build_SAT(self):
|
||||
sat_stream = ''.join([self.data[i*self.sect_size:(i+1)*self.sect_size] for i in self.MSAT if i >= 0])
|
||||
|
||||
sat_sids_count = len(sat_stream) >> 2
|
||||
self.SAT = struct.unpack('<%dl' % sat_sids_count, sat_stream) # SIDs tuple
|
||||
|
||||
if self.dump:
|
||||
print 'SAT sid count:\n', sat_sids_count
|
||||
print 'SAT content:\n', self.SAT
|
||||
|
||||
|
||||
def __build_SSAT(self):
|
||||
ssat_stream = self.get_stream_data(self.data, self.SAT, self.ssat_start_sid, self.sect_size)
|
||||
|
||||
ssids_count = len(ssat_stream) >> 2
|
||||
self.SSAT = struct.unpack('<%dl' % ssids_count, ssat_stream)
|
||||
|
||||
if self.dump:
|
||||
print 'SSID count:', ssids_count
|
||||
print 'SSAT content:\n', self.SSAT
|
||||
|
||||
|
||||
def __build_directory(self):
|
||||
dir_stream = self.get_stream_data(self.data, self.SAT, self.dir_start_sid, self.sect_size)
|
||||
|
||||
self.dir_entry_list = []
|
||||
|
||||
i = 0
|
||||
while i < len(dir_stream):
|
||||
dentry = dir_stream[i:i+128] # 128 -- dir entry size
|
||||
i += 128
|
||||
|
||||
did = len(self.dir_entry_list)
|
||||
sz, = struct.unpack('<H', dentry[64:66])
|
||||
if sz > 0 :
|
||||
name = dentry[0:sz-2].decode('utf_16_le', 'replace')
|
||||
else:
|
||||
name = u''
|
||||
t, = struct.unpack('B', dentry[66])
|
||||
c, = struct.unpack('B', dentry[67])
|
||||
did_left , = struct.unpack('<l', dentry[68:72])
|
||||
did_right , = struct.unpack('<l', dentry[72:76])
|
||||
did_root , = struct.unpack('<l', dentry[76:80])
|
||||
dentry_start_sid , = struct.unpack('<l', dentry[116:120])
|
||||
stream_size , = struct.unpack('<L', dentry[120:124])
|
||||
|
||||
self.dir_entry_list.extend([(did, sz, name, t, c,
|
||||
did_left, did_right, did_root,
|
||||
dentry_start_sid, stream_size)])
|
||||
|
||||
if self.dump:
|
||||
dentry_types = {
|
||||
0x00: 'Empty',
|
||||
0x01: 'User storage',
|
||||
0x02: 'User stream',
|
||||
0x03: 'LockBytes',
|
||||
0x04: 'Property',
|
||||
0x05: 'Root storage'
|
||||
}
|
||||
node_colours = {
|
||||
0x00: 'Red',
|
||||
0x01: 'Black'
|
||||
}
|
||||
print 'total directory entries:', len(self.dir_entry_list)
|
||||
|
||||
for dentry in self.dir_entry_list:
|
||||
(did, sz, name, t, c,
|
||||
did_left, did_right, did_root,
|
||||
dentry_start_sid, stream_size) = dentry
|
||||
print 'DID', did
|
||||
print 'Size of the used area of the character buffer of the name:', sz
|
||||
print 'dir entry name:', repr(name)
|
||||
print 'type of entry:', t, dentry_types[t]
|
||||
print 'entry colour:', c, node_colours[c]
|
||||
print 'left child DID :', did_left
|
||||
print 'right child DID:', did_right
|
||||
print 'root DID :', did_root
|
||||
print 'start SID :', dentry_start_sid
|
||||
print 'stream size :', stream_size
|
||||
if stream_size == 0:
|
||||
print 'stream is empty'
|
||||
elif stream_size >= self.min_stream_size:
|
||||
print 'stream stored as normal stream'
|
||||
else:
|
||||
print 'stream stored as short-stream'
|
||||
|
||||
|
||||
def __build_short_sectors_data(self):
|
||||
(did, sz, name, t, c,
|
||||
did_left, did_right, did_root,
|
||||
dentry_start_sid, stream_size) = self.dir_entry_list[0]
|
||||
assert t == 0x05 # Short-Stream Container Stream (SSCS) resides in Root Storage
|
||||
if stream_size == 0:
|
||||
self.short_sectors_data = ''
|
||||
else:
|
||||
self.short_sectors_data = self.get_stream_data(self.data, self.SAT, dentry_start_sid, self.sect_size)
|
||||
|
||||
|
||||
def get_stream_data(self, data, SAT, start_sid, sect_size):
|
||||
sid = start_sid
|
||||
chunks = [(sid, sid)]
|
||||
stream_data = ''
|
||||
|
||||
while SAT[sid] >= 0:
|
||||
next_in_chain = SAT[sid]
|
||||
last_chunk_start, last_chunk_finish = chunks[-1]
|
||||
if next_in_chain == last_chunk_finish + 1:
|
||||
chunks[-1] = last_chunk_start, next_in_chain
|
||||
else:
|
||||
chunks.extend([(next_in_chain, next_in_chain)])
|
||||
sid = next_in_chain
|
||||
for s, f in chunks:
|
||||
stream_data += data[s*sect_size:(f+1)*sect_size]
|
||||
#print chunks
|
||||
return stream_data
|
||||
|
||||
|
||||
def print_bin_data(data):
|
||||
i = 0
|
||||
while i < len(data):
|
||||
j = 0
|
||||
while (i < len(data)) and (j < 16):
|
||||
c = '0x%02X' % ord(data[i])
|
||||
sys.stdout.write(c)
|
||||
sys.stdout.write(' ')
|
||||
i += 1
|
||||
j += 1
|
||||
print
|
||||
if i == 0:
|
||||
print '<NO DATA>'
|
||||
|
||||
|
||||
|
||||
# This implementation writes only 'Root Entry', 'Workbook' streams
|
||||
# and 2 empty streams for aligning directory stream on sector boundary
|
||||
#
|
||||
# LAYOUT:
|
||||
# 0 header
|
||||
# 76 MSAT (1st part: 109 SID)
|
||||
# 512 workbook stream
|
||||
# ... additional MSAT sectors if streams' size > about 7 Mb == (109*512 * 128)
|
||||
# ... SAT
|
||||
# ... directory stream
|
||||
#
|
||||
# NOTE: this layout is "ad hoc". It can be more general. RTFM
|
||||
|
||||
class XlsDoc:
|
||||
SECTOR_SIZE = 0x0200
|
||||
MIN_LIMIT = 0x1000
|
||||
|
||||
SID_FREE_SECTOR = -1
|
||||
SID_END_OF_CHAIN = -2
|
||||
SID_USED_BY_SAT = -3
|
||||
SID_USED_BY_MSAT = -4
|
||||
|
||||
def __init__(self):
|
||||
#self.book_stream = '' # padded
|
||||
self.book_stream_sect = []
|
||||
|
||||
self.dir_stream = ''
|
||||
self.dir_stream_sect = []
|
||||
|
||||
self.packed_SAT = ''
|
||||
self.SAT_sect = []
|
||||
|
||||
self.packed_MSAT_1st = ''
|
||||
self.packed_MSAT_2nd = ''
|
||||
self.MSAT_sect_2nd = []
|
||||
|
||||
self.header = ''
|
||||
|
||||
def __build_directory(self): # align on sector boundary
|
||||
self.dir_stream = ''
|
||||
|
||||
dentry_name = '\x00'.join('Root Entry\x00') + '\x00'
|
||||
dentry_name_sz = len(dentry_name)
|
||||
dentry_name_pad = '\x00'*(64 - dentry_name_sz)
|
||||
dentry_type = 0x05 # root storage
|
||||
dentry_colour = 0x01 # black
|
||||
dentry_did_left = -1
|
||||
dentry_did_right = -1
|
||||
dentry_did_root = 1
|
||||
dentry_start_sid = -2
|
||||
dentry_stream_sz = 0
|
||||
|
||||
self.dir_stream += struct.pack('<64s H 2B 3l 9L l L L',
|
||||
dentry_name + dentry_name_pad,
|
||||
dentry_name_sz,
|
||||
dentry_type,
|
||||
dentry_colour,
|
||||
dentry_did_left,
|
||||
dentry_did_right,
|
||||
dentry_did_root,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
dentry_start_sid,
|
||||
dentry_stream_sz,
|
||||
0
|
||||
)
|
||||
|
||||
dentry_name = '\x00'.join('Workbook\x00') + '\x00'
|
||||
dentry_name_sz = len(dentry_name)
|
||||
dentry_name_pad = '\x00'*(64 - dentry_name_sz)
|
||||
dentry_type = 0x02 # user stream
|
||||
dentry_colour = 0x01 # black
|
||||
dentry_did_left = -1
|
||||
dentry_did_right = -1
|
||||
dentry_did_root = -1
|
||||
dentry_start_sid = 0
|
||||
dentry_stream_sz = self.book_stream_len
|
||||
|
||||
self.dir_stream += struct.pack('<64s H 2B 3l 9L l L L',
|
||||
dentry_name + dentry_name_pad,
|
||||
dentry_name_sz,
|
||||
dentry_type,
|
||||
dentry_colour,
|
||||
dentry_did_left,
|
||||
dentry_did_right,
|
||||
dentry_did_root,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
dentry_start_sid,
|
||||
dentry_stream_sz,
|
||||
0
|
||||
)
|
||||
|
||||
# padding
|
||||
dentry_name = ''
|
||||
dentry_name_sz = len(dentry_name)
|
||||
dentry_name_pad = '\x00'*(64 - dentry_name_sz)
|
||||
dentry_type = 0x00 # empty
|
||||
dentry_colour = 0x01 # black
|
||||
dentry_did_left = -1
|
||||
dentry_did_right = -1
|
||||
dentry_did_root = -1
|
||||
dentry_start_sid = -2
|
||||
dentry_stream_sz = 0
|
||||
|
||||
self.dir_stream += struct.pack('<64s H 2B 3l 9L l L L',
|
||||
dentry_name + dentry_name_pad,
|
||||
dentry_name_sz,
|
||||
dentry_type,
|
||||
dentry_colour,
|
||||
dentry_did_left,
|
||||
dentry_did_right,
|
||||
dentry_did_root,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
dentry_start_sid,
|
||||
dentry_stream_sz,
|
||||
0
|
||||
) * 2
|
||||
|
||||
def __build_sat(self):
|
||||
# Build SAT
|
||||
book_sect_count = self.book_stream_len >> 9
|
||||
dir_sect_count = len(self.dir_stream) >> 9
|
||||
|
||||
total_sect_count = book_sect_count + dir_sect_count
|
||||
SAT_sect_count = 0
|
||||
MSAT_sect_count = 0
|
||||
SAT_sect_count_limit = 109
|
||||
while total_sect_count > 128*SAT_sect_count or SAT_sect_count > SAT_sect_count_limit:
|
||||
SAT_sect_count += 1
|
||||
total_sect_count += 1
|
||||
if SAT_sect_count > SAT_sect_count_limit:
|
||||
MSAT_sect_count += 1
|
||||
total_sect_count += 1
|
||||
SAT_sect_count_limit += 127
|
||||
|
||||
|
||||
SAT = [self.SID_FREE_SECTOR]*128*SAT_sect_count
|
||||
|
||||
sect = 0
|
||||
while sect < book_sect_count - 1:
|
||||
self.book_stream_sect.append(sect)
|
||||
SAT[sect] = sect + 1
|
||||
sect += 1
|
||||
self.book_stream_sect.append(sect)
|
||||
SAT[sect] = self.SID_END_OF_CHAIN
|
||||
sect += 1
|
||||
|
||||
while sect < book_sect_count + MSAT_sect_count:
|
||||
self.MSAT_sect_2nd.append(sect)
|
||||
SAT[sect] = self.SID_USED_BY_MSAT
|
||||
sect += 1
|
||||
|
||||
while sect < book_sect_count + MSAT_sect_count + SAT_sect_count:
|
||||
self.SAT_sect.append(sect)
|
||||
SAT[sect] = self.SID_USED_BY_SAT
|
||||
sect += 1
|
||||
|
||||
while sect < book_sect_count + MSAT_sect_count + SAT_sect_count + dir_sect_count - 1:
|
||||
self.dir_stream_sect.append(sect)
|
||||
SAT[sect] = sect + 1
|
||||
sect += 1
|
||||
self.dir_stream_sect.append(sect)
|
||||
SAT[sect] = self.SID_END_OF_CHAIN
|
||||
sect += 1
|
||||
|
||||
self.packed_SAT = struct.pack('<%dl' % (SAT_sect_count*128), *SAT)
|
||||
|
||||
MSAT_1st = [self.SID_FREE_SECTOR]*109
|
||||
for i, SAT_sect_num in zip(range(0, 109), self.SAT_sect):
|
||||
MSAT_1st[i] = SAT_sect_num
|
||||
self.packed_MSAT_1st = struct.pack('<109l', *MSAT_1st)
|
||||
|
||||
MSAT_2nd = [self.SID_FREE_SECTOR]*128*MSAT_sect_count
|
||||
if MSAT_sect_count > 0:
|
||||
MSAT_2nd[- 1] = self.SID_END_OF_CHAIN
|
||||
|
||||
i = 109
|
||||
msat_sect = 0
|
||||
sid_num = 0
|
||||
while i < SAT_sect_count:
|
||||
if (sid_num + 1) % 128 == 0:
|
||||
#print 'link: ',
|
||||
msat_sect += 1
|
||||
if msat_sect < len(self.MSAT_sect_2nd):
|
||||
MSAT_2nd[sid_num] = self.MSAT_sect_2nd[msat_sect]
|
||||
else:
|
||||
#print 'sid: ',
|
||||
MSAT_2nd[sid_num] = self.SAT_sect[i]
|
||||
i += 1
|
||||
#print sid_num, MSAT_2nd[sid_num]
|
||||
sid_num += 1
|
||||
|
||||
self.packed_MSAT_2nd = struct.pack('<%dl' % (MSAT_sect_count*128), *MSAT_2nd)
|
||||
|
||||
#print vars()
|
||||
#print zip(range(0, sect), SAT)
|
||||
#print self.book_stream_sect
|
||||
#print self.MSAT_sect_2nd
|
||||
#print MSAT_2nd
|
||||
#print self.SAT_sect
|
||||
#print self.dir_stream_sect
|
||||
|
||||
|
||||
def __build_header(self):
|
||||
doc_magic = '\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1'
|
||||
file_uid = '\x00'*16
|
||||
rev_num = '\x3E\x00'
|
||||
ver_num = '\x03\x00'
|
||||
byte_order = '\xFE\xFF'
|
||||
log_sect_size = struct.pack('<H', 9)
|
||||
log_short_sect_size = struct.pack('<H', 6)
|
||||
not_used0 = '\x00'*10
|
||||
total_sat_sectors = struct.pack('<L', len(self.SAT_sect))
|
||||
dir_start_sid = struct.pack('<l', self.dir_stream_sect[0])
|
||||
not_used1 = '\x00'*4
|
||||
min_stream_size = struct.pack('<L', 0x1000)
|
||||
ssat_start_sid = struct.pack('<l', -2)
|
||||
total_ssat_sectors = struct.pack('<L', 0)
|
||||
|
||||
if len(self.MSAT_sect_2nd) == 0:
|
||||
msat_start_sid = struct.pack('<l', -2)
|
||||
else:
|
||||
msat_start_sid = struct.pack('<l', self.MSAT_sect_2nd[0])
|
||||
|
||||
total_msat_sectors = struct.pack('<L', len(self.MSAT_sect_2nd))
|
||||
|
||||
self.header = ''.join([ doc_magic,
|
||||
file_uid,
|
||||
rev_num,
|
||||
ver_num,
|
||||
byte_order,
|
||||
log_sect_size,
|
||||
log_short_sect_size,
|
||||
not_used0,
|
||||
total_sat_sectors,
|
||||
dir_start_sid,
|
||||
not_used1,
|
||||
min_stream_size,
|
||||
ssat_start_sid,
|
||||
total_ssat_sectors,
|
||||
msat_start_sid,
|
||||
total_msat_sectors
|
||||
])
|
||||
|
||||
|
||||
def save(self, file_name_or_filelike_obj, stream):
|
||||
# 1. Align stream on 0x1000 boundary (and therefore on sector boundary)
|
||||
padding = '\x00' * (0x1000 - (len(stream) % 0x1000))
|
||||
self.book_stream_len = len(stream) + len(padding)
|
||||
|
||||
self.__build_directory()
|
||||
self.__build_sat()
|
||||
self.__build_header()
|
||||
|
||||
f = file_name_or_filelike_obj
|
||||
we_own_it = not hasattr(f, 'write')
|
||||
if we_own_it:
|
||||
f = open(file_name_or_filelike_obj, 'wb')
|
||||
f.write(self.header)
|
||||
f.write(self.packed_MSAT_1st)
|
||||
f.write(stream)
|
||||
f.write(padding)
|
||||
f.write(self.packed_MSAT_2nd)
|
||||
f.write(self.packed_SAT)
|
||||
f.write(self.dir_stream)
|
||||
if we_own_it:
|
||||
f.close()
|
||||
@@ -0,0 +1,43 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
import ExcelFormulaParser, ExcelFormulaLexer
|
||||
import struct
|
||||
from antlr import ANTLRException
|
||||
|
||||
|
||||
class Formula(object):
|
||||
__slots__ = ["__init__", "__s", "__parser", "__sheet_refs", "__xcall_refs"]
|
||||
|
||||
|
||||
def __init__(self, s):
|
||||
try:
|
||||
self.__s = s
|
||||
lexer = ExcelFormulaLexer.Lexer(s)
|
||||
self.__parser = ExcelFormulaParser.Parser(lexer)
|
||||
self.__parser.formula()
|
||||
self.__sheet_refs = self.__parser.sheet_references
|
||||
self.__xcall_refs = self.__parser.xcall_references
|
||||
except ANTLRException, e:
|
||||
# print e
|
||||
raise ExcelFormulaParser.FormulaParseException, "can't parse formula " + s
|
||||
|
||||
def get_references(self):
|
||||
return self.__sheet_refs, self.__xcall_refs
|
||||
|
||||
def patch_references(self, patches):
|
||||
for offset, idx in patches:
|
||||
self.__parser.rpn = self.__parser.rpn[:offset] + struct.pack('<H', idx) + self.__parser.rpn[offset+2:]
|
||||
|
||||
def text(self):
|
||||
return self.__s
|
||||
|
||||
def rpn(self):
|
||||
'''
|
||||
Offset Size Contents
|
||||
0 2 Size of the following formula data (sz)
|
||||
2 sz Formula data (RPN token array)
|
||||
[2+sz] var. (optional) Additional data for specific tokens
|
||||
|
||||
'''
|
||||
return struct.pack("<H", len(self.__parser.rpn)) + self.__parser.rpn
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
import sys
|
||||
from antlr import EOF, CommonToken as Tok, TokenStream, TokenStreamException
|
||||
import struct
|
||||
import ExcelFormulaParser
|
||||
from re import compile as recompile, match, LOCALE, UNICODE, IGNORECASE, VERBOSE
|
||||
|
||||
|
||||
int_const_pattern = r"\d+\b"
|
||||
flt_const_pattern = r"""
|
||||
(?:
|
||||
(?: \d* \. \d+ ) # .1 .12 .123 etc 9.1 etc 98.1 etc
|
||||
|
|
||||
(?: \d+ \. ) # 1. 12. 123. etc
|
||||
)
|
||||
# followed by optional exponent part
|
||||
(?: [Ee] [+-]? \d+ ) ?
|
||||
"""
|
||||
str_const_pattern = r'"(?:[^"]|"")*"'
|
||||
#range2d_pattern = recompile(r"\$?[A-I]?[A-Z]\$?\d+:\$?[A-I]?[A-Z]\$?\d+"
|
||||
ref2d_r1c1_pattern = r"[Rr]0*[1-9][0-9]*[Cc]0*[1-9][0-9]*"
|
||||
ref2d_pattern = r"\$?[A-I]?[A-Z]\$?0*[1-9][0-9]*"
|
||||
true_pattern = r"TRUE\b"
|
||||
false_pattern = r"FALSE\b"
|
||||
if_pattern = r"IF\b"
|
||||
choose_pattern = r"CHOOSE\b"
|
||||
name_pattern = r"\w[\.\w]*"
|
||||
quotename_pattern = r"'(?:[^']|'')*'" #### It's essential that this bracket be non-grouping.
|
||||
ne_pattern = r"<>"
|
||||
ge_pattern = r">="
|
||||
le_pattern = r"<="
|
||||
|
||||
pattern_type_tuples = (
|
||||
(flt_const_pattern, ExcelFormulaParser.NUM_CONST),
|
||||
(int_const_pattern, ExcelFormulaParser.INT_CONST),
|
||||
(str_const_pattern, ExcelFormulaParser.STR_CONST),
|
||||
# (range2d_pattern , ExcelFormulaParser.RANGE2D),
|
||||
(ref2d_r1c1_pattern, ExcelFormulaParser.REF2D_R1C1),
|
||||
(ref2d_pattern , ExcelFormulaParser.REF2D),
|
||||
(true_pattern , ExcelFormulaParser.TRUE_CONST),
|
||||
(false_pattern , ExcelFormulaParser.FALSE_CONST),
|
||||
(if_pattern , ExcelFormulaParser.FUNC_IF),
|
||||
(choose_pattern , ExcelFormulaParser.FUNC_CHOOSE),
|
||||
(name_pattern , ExcelFormulaParser.NAME),
|
||||
(quotename_pattern, ExcelFormulaParser.QUOTENAME),
|
||||
(ne_pattern, ExcelFormulaParser.NE),
|
||||
(ge_pattern, ExcelFormulaParser.GE),
|
||||
(le_pattern, ExcelFormulaParser.LE),
|
||||
)
|
||||
|
||||
_re = recompile(
|
||||
'(' + ')|('.join([i[0] for i in pattern_type_tuples]) + ')',
|
||||
VERBOSE+LOCALE+IGNORECASE)
|
||||
|
||||
_toktype = [None] + [i[1] for i in pattern_type_tuples]
|
||||
# need dummy at start because re.MatchObject.lastindex counts from 1
|
||||
|
||||
single_char_lookup = {
|
||||
'=': ExcelFormulaParser.EQ,
|
||||
'<': ExcelFormulaParser.LT,
|
||||
'>': ExcelFormulaParser.GT,
|
||||
'+': ExcelFormulaParser.ADD,
|
||||
'-': ExcelFormulaParser.SUB,
|
||||
'*': ExcelFormulaParser.MUL,
|
||||
'/': ExcelFormulaParser.DIV,
|
||||
':': ExcelFormulaParser.COLON,
|
||||
';': ExcelFormulaParser.SEMICOLON,
|
||||
',': ExcelFormulaParser.COMMA,
|
||||
'(': ExcelFormulaParser.LP,
|
||||
')': ExcelFormulaParser.RP,
|
||||
'&': ExcelFormulaParser.CONCAT,
|
||||
'%': ExcelFormulaParser.PERCENT,
|
||||
'^': ExcelFormulaParser.POWER,
|
||||
'!': ExcelFormulaParser.BANG,
|
||||
}
|
||||
|
||||
class Lexer(TokenStream):
|
||||
def __init__(self, text):
|
||||
self._text = text[:]
|
||||
self._pos = 0
|
||||
self._line = 0
|
||||
|
||||
def isEOF(self):
|
||||
return len(self._text) <= self._pos
|
||||
|
||||
def curr_ch(self):
|
||||
return self._text[self._pos]
|
||||
|
||||
def next_ch(self, n = 1):
|
||||
self._pos += n
|
||||
|
||||
def is_whitespace(self):
|
||||
return self.curr_ch() in " \t\n\r\f\v"
|
||||
|
||||
def match_pattern(self):
|
||||
m = _re.match(self._text, self._pos)
|
||||
if not m:
|
||||
return None
|
||||
self._pos = m.end(0)
|
||||
return Tok(type = _toktype[m.lastindex], text = m.group(0), col = m.start(0) + 1)
|
||||
|
||||
def nextToken(self):
|
||||
# skip whitespace
|
||||
while not self.isEOF() and self.is_whitespace():
|
||||
self.next_ch()
|
||||
if self.isEOF():
|
||||
return Tok(type = EOF)
|
||||
# first, try to match token with 2 or more chars
|
||||
t = self.match_pattern()
|
||||
if t:
|
||||
return t
|
||||
# second, we want 1-char tokens
|
||||
te = self.curr_ch()
|
||||
try:
|
||||
ty = single_char_lookup[te]
|
||||
except KeyError:
|
||||
raise TokenStreamException(
|
||||
"Unexpected char %r in column %u." % (self.curr_ch(), self._pos))
|
||||
self.next_ch()
|
||||
return Tok(type=ty, text=te, col=self._pos)
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
for t in Lexer(""" 1.23 456 "abcd" R2C2 a1 iv65536 true false if choose a_name 'qname' <> >= <= """):
|
||||
print t
|
||||
except TokenStreamException, e:
|
||||
print "error:", e
|
||||
@@ -0,0 +1,677 @@
|
||||
### $ANTLR 2.7.7 (20060930): "xlwt/excel-formula.g" -> "ExcelFormulaParser.py"$
|
||||
### import antlr and other modules ..
|
||||
import sys
|
||||
import antlr
|
||||
|
||||
version = sys.version.split()[0]
|
||||
if version < '2.2.1':
|
||||
False = 0
|
||||
if version < '2.3':
|
||||
True = not False
|
||||
### header action >>>
|
||||
import struct
|
||||
import Utils
|
||||
from UnicodeUtils import upack1
|
||||
from ExcelMagic import *
|
||||
|
||||
_RVAdelta = {"R": 0, "V": 0x20, "A": 0x40}
|
||||
_RVAdeltaRef = {"R": 0, "V": 0x20, "A": 0x40, "D": 0x20}
|
||||
_RVAdeltaArea = {"R": 0, "V": 0x20, "A": 0x40, "D": 0}
|
||||
|
||||
|
||||
class FormulaParseException(Exception):
|
||||
"""
|
||||
An exception indicating that a Formula could not be successfully parsed.
|
||||
"""
|
||||
### header action <<<
|
||||
### preamble action>>>
|
||||
|
||||
### preamble action <<<
|
||||
|
||||
### import antlr.Token
|
||||
from antlr import Token
|
||||
### >>>The Known Token Types <<<
|
||||
SKIP = antlr.SKIP
|
||||
INVALID_TYPE = antlr.INVALID_TYPE
|
||||
EOF_TYPE = antlr.EOF_TYPE
|
||||
EOF = antlr.EOF
|
||||
NULL_TREE_LOOKAHEAD = antlr.NULL_TREE_LOOKAHEAD
|
||||
MIN_USER_TYPE = antlr.MIN_USER_TYPE
|
||||
TRUE_CONST = 4
|
||||
FALSE_CONST = 5
|
||||
STR_CONST = 6
|
||||
NUM_CONST = 7
|
||||
INT_CONST = 8
|
||||
FUNC_IF = 9
|
||||
FUNC_CHOOSE = 10
|
||||
NAME = 11
|
||||
QUOTENAME = 12
|
||||
EQ = 13
|
||||
NE = 14
|
||||
GT = 15
|
||||
LT = 16
|
||||
GE = 17
|
||||
LE = 18
|
||||
ADD = 19
|
||||
SUB = 20
|
||||
MUL = 21
|
||||
DIV = 22
|
||||
POWER = 23
|
||||
PERCENT = 24
|
||||
LP = 25
|
||||
RP = 26
|
||||
LB = 27
|
||||
RB = 28
|
||||
COLON = 29
|
||||
COMMA = 30
|
||||
SEMICOLON = 31
|
||||
REF2D = 32
|
||||
REF2D_R1C1 = 33
|
||||
BANG = 34
|
||||
CONCAT = 35
|
||||
|
||||
class Parser(antlr.LLkParser):
|
||||
### user action >>>
|
||||
### user action <<<
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
antlr.LLkParser.__init__(self, *args, **kwargs)
|
||||
self.tokenNames = _tokenNames
|
||||
### __init__ header action >>>
|
||||
self.rpn = ""
|
||||
self.sheet_references = []
|
||||
self.xcall_references = []
|
||||
### __init__ header action <<<
|
||||
|
||||
def formula(self):
|
||||
|
||||
pass
|
||||
self.expr("V")
|
||||
|
||||
def expr(self,
|
||||
arg_type
|
||||
):
|
||||
|
||||
pass
|
||||
self.prec0_expr(arg_type)
|
||||
while True:
|
||||
if ((self.LA(1) >= EQ and self.LA(1) <= LE)):
|
||||
pass
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [EQ]:
|
||||
pass
|
||||
self.match(EQ)
|
||||
op = struct.pack('B', ptgEQ)
|
||||
elif la1 and la1 in [NE]:
|
||||
pass
|
||||
self.match(NE)
|
||||
op = struct.pack('B', ptgNE)
|
||||
elif la1 and la1 in [GT]:
|
||||
pass
|
||||
self.match(GT)
|
||||
op = struct.pack('B', ptgGT)
|
||||
elif la1 and la1 in [LT]:
|
||||
pass
|
||||
self.match(LT)
|
||||
op = struct.pack('B', ptgLT)
|
||||
elif la1 and la1 in [GE]:
|
||||
pass
|
||||
self.match(GE)
|
||||
op = struct.pack('B', ptgGE)
|
||||
elif la1 and la1 in [LE]:
|
||||
pass
|
||||
self.match(LE)
|
||||
op = struct.pack('B', ptgLE)
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
self.prec0_expr(arg_type)
|
||||
self.rpn += op
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def prec0_expr(self,
|
||||
arg_type
|
||||
):
|
||||
|
||||
pass
|
||||
self.prec1_expr(arg_type)
|
||||
while True:
|
||||
if (self.LA(1)==CONCAT):
|
||||
pass
|
||||
pass
|
||||
self.match(CONCAT)
|
||||
op = struct.pack('B', ptgConcat)
|
||||
self.prec1_expr(arg_type)
|
||||
self.rpn += op
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def prec1_expr(self,
|
||||
arg_type
|
||||
):
|
||||
|
||||
pass
|
||||
self.prec2_expr(arg_type)
|
||||
while True:
|
||||
if (self.LA(1)==ADD or self.LA(1)==SUB):
|
||||
pass
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [ADD]:
|
||||
pass
|
||||
self.match(ADD)
|
||||
op = struct.pack('B', ptgAdd)
|
||||
elif la1 and la1 in [SUB]:
|
||||
pass
|
||||
self.match(SUB)
|
||||
op = struct.pack('B', ptgSub)
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
self.prec2_expr(arg_type)
|
||||
self.rpn += op;
|
||||
# print "**prec1_expr4 %s" % arg_type
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def prec2_expr(self,
|
||||
arg_type
|
||||
):
|
||||
|
||||
pass
|
||||
self.prec3_expr(arg_type)
|
||||
while True:
|
||||
if (self.LA(1)==MUL or self.LA(1)==DIV):
|
||||
pass
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [MUL]:
|
||||
pass
|
||||
self.match(MUL)
|
||||
op = struct.pack('B', ptgMul)
|
||||
elif la1 and la1 in [DIV]:
|
||||
pass
|
||||
self.match(DIV)
|
||||
op = struct.pack('B', ptgDiv)
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
self.prec3_expr(arg_type)
|
||||
self.rpn += op
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def prec3_expr(self,
|
||||
arg_type
|
||||
):
|
||||
|
||||
pass
|
||||
self.prec4_expr(arg_type)
|
||||
while True:
|
||||
if (self.LA(1)==POWER):
|
||||
pass
|
||||
pass
|
||||
self.match(POWER)
|
||||
op = struct.pack('B', ptgPower)
|
||||
self.prec4_expr(arg_type)
|
||||
self.rpn += op
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def prec4_expr(self,
|
||||
arg_type
|
||||
):
|
||||
|
||||
pass
|
||||
self.prec5_expr(arg_type)
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [PERCENT]:
|
||||
pass
|
||||
self.match(PERCENT)
|
||||
self.rpn += struct.pack('B', ptgPercent)
|
||||
elif la1 and la1 in [EOF,EQ,NE,GT,LT,GE,LE,ADD,SUB,MUL,DIV,POWER,RP,COMMA,SEMICOLON,CONCAT]:
|
||||
pass
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
|
||||
def prec5_expr(self,
|
||||
arg_type
|
||||
):
|
||||
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [TRUE_CONST,FALSE_CONST,STR_CONST,NUM_CONST,INT_CONST,FUNC_IF,FUNC_CHOOSE,NAME,QUOTENAME,LP,REF2D]:
|
||||
pass
|
||||
self.primary(arg_type)
|
||||
elif la1 and la1 in [SUB]:
|
||||
pass
|
||||
self.match(SUB)
|
||||
self.primary(arg_type)
|
||||
self.rpn += struct.pack('B', ptgUminus)
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
|
||||
def primary(self,
|
||||
arg_type
|
||||
):
|
||||
|
||||
str_tok = None
|
||||
int_tok = None
|
||||
num_tok = None
|
||||
ref2d_tok = None
|
||||
ref2d1_tok = None
|
||||
ref2d2_tok = None
|
||||
ref3d_ref2d = None
|
||||
ref3d_ref2d2 = None
|
||||
name_tok = None
|
||||
func_tok = None
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [TRUE_CONST]:
|
||||
pass
|
||||
self.match(TRUE_CONST)
|
||||
self.rpn += struct.pack("2B", ptgBool, 1)
|
||||
elif la1 and la1 in [FALSE_CONST]:
|
||||
pass
|
||||
self.match(FALSE_CONST)
|
||||
self.rpn += struct.pack("2B", ptgBool, 0)
|
||||
elif la1 and la1 in [STR_CONST]:
|
||||
pass
|
||||
str_tok = self.LT(1)
|
||||
self.match(STR_CONST)
|
||||
self.rpn += struct.pack("B", ptgStr) + upack1(str_tok.text[1:-1].replace("\"\"", "\""))
|
||||
elif la1 and la1 in [NUM_CONST]:
|
||||
pass
|
||||
num_tok = self.LT(1)
|
||||
self.match(NUM_CONST)
|
||||
self.rpn += struct.pack("<Bd", ptgNum, float(num_tok.text))
|
||||
elif la1 and la1 in [FUNC_IF]:
|
||||
pass
|
||||
self.match(FUNC_IF)
|
||||
self.match(LP)
|
||||
self.expr("V")
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [SEMICOLON]:
|
||||
pass
|
||||
self.match(SEMICOLON)
|
||||
elif la1 and la1 in [COMMA]:
|
||||
pass
|
||||
self.match(COMMA)
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
self.rpn += struct.pack("<BBH", ptgAttr, 0x02, 0) # tAttrIf
|
||||
pos0 = len(self.rpn) - 2
|
||||
self.expr(arg_type)
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [SEMICOLON]:
|
||||
pass
|
||||
self.match(SEMICOLON)
|
||||
elif la1 and la1 in [COMMA]:
|
||||
pass
|
||||
self.match(COMMA)
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
self.rpn += struct.pack("<BBH", ptgAttr, 0x08, 0) # tAttrSkip
|
||||
pos1 = len(self.rpn) - 2
|
||||
self.rpn = self.rpn[:pos0] + struct.pack("<H", pos1-pos0) + self.rpn[pos0+2:]
|
||||
self.expr(arg_type)
|
||||
self.match(RP)
|
||||
self.rpn += struct.pack("<BBH", ptgAttr, 0x08, 3) # tAttrSkip
|
||||
self.rpn += struct.pack("<BBH", ptgFuncVarR, 3, 1) # 3 = nargs, 1 = IF func
|
||||
pos2 = len(self.rpn)
|
||||
self.rpn = self.rpn[:pos1] + struct.pack("<H", pos2-(pos1+2)-1) + self.rpn[pos1+2:]
|
||||
elif la1 and la1 in [FUNC_CHOOSE]:
|
||||
pass
|
||||
self.match(FUNC_CHOOSE)
|
||||
arg_type = "R"
|
||||
rpn_chunks = []
|
||||
self.match(LP)
|
||||
self.expr("V")
|
||||
rpn_start = len(self.rpn)
|
||||
ref_markers = [len(self.sheet_references)]
|
||||
while True:
|
||||
if (self.LA(1)==COMMA or self.LA(1)==SEMICOLON):
|
||||
pass
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [SEMICOLON]:
|
||||
pass
|
||||
self.match(SEMICOLON)
|
||||
elif la1 and la1 in [COMMA]:
|
||||
pass
|
||||
self.match(COMMA)
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
mark = len(self.rpn)
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [TRUE_CONST,FALSE_CONST,STR_CONST,NUM_CONST,INT_CONST,FUNC_IF,FUNC_CHOOSE,NAME,QUOTENAME,SUB,LP,REF2D]:
|
||||
pass
|
||||
self.expr(arg_type)
|
||||
elif la1 and la1 in [RP,COMMA,SEMICOLON]:
|
||||
pass
|
||||
self.rpn += struct.pack("B", ptgMissArg)
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
rpn_chunks.append(self.rpn[mark:])
|
||||
ref_markers.append(len(self.sheet_references))
|
||||
else:
|
||||
break
|
||||
|
||||
self.match(RP)
|
||||
self.rpn = self.rpn[:rpn_start]
|
||||
nc = len(rpn_chunks)
|
||||
chunklens = [len(chunk) for chunk in rpn_chunks]
|
||||
skiplens = [0] * nc
|
||||
skiplens[-1] = 3
|
||||
for ic in xrange(nc-1, 0, -1):
|
||||
skiplens[ic-1] = skiplens[ic] + chunklens[ic] + 4
|
||||
jump_pos = [2 * nc + 2]
|
||||
for ic in xrange(nc):
|
||||
jump_pos.append(jump_pos[-1] + chunklens[ic] + 4)
|
||||
chunk_shift = 2 * nc + 6 # size of tAttrChoose
|
||||
for ic in xrange(nc):
|
||||
for refx in xrange(ref_markers[ic], ref_markers[ic+1]):
|
||||
ref = self.sheet_references[refx]
|
||||
self.sheet_references[refx] = (ref[0], ref[1], ref[2] + chunk_shift)
|
||||
chunk_shift += 4 # size of tAttrSkip
|
||||
choose_rpn = []
|
||||
choose_rpn.append(struct.pack("<BBH", ptgAttr, 0x04, nc)) # 0x04 is tAttrChoose
|
||||
choose_rpn.append(struct.pack("<%dH" % (nc+1), *jump_pos))
|
||||
for ic in xrange(nc):
|
||||
choose_rpn.append(rpn_chunks[ic])
|
||||
choose_rpn.append(struct.pack("<BBH", ptgAttr, 0x08, skiplens[ic])) # 0x08 is tAttrSkip
|
||||
choose_rpn.append(struct.pack("<BBH", ptgFuncVarV, nc+1, 100)) # 100 is CHOOSE fn
|
||||
self.rpn += "".join(choose_rpn)
|
||||
elif la1 and la1 in [LP]:
|
||||
pass
|
||||
self.match(LP)
|
||||
self.expr(arg_type)
|
||||
self.match(RP)
|
||||
self.rpn += struct.pack("B", ptgParen)
|
||||
else:
|
||||
if (self.LA(1)==INT_CONST) and (_tokenSet_0.member(self.LA(2))):
|
||||
pass
|
||||
int_tok = self.LT(1)
|
||||
self.match(INT_CONST)
|
||||
# print "**int_const", int_tok.text
|
||||
int_value = int(int_tok.text)
|
||||
if int_value <= 65535:
|
||||
self.rpn += struct.pack("<BH", ptgInt, int_value)
|
||||
else:
|
||||
self.rpn += struct.pack("<Bd", ptgNum, float(int_value))
|
||||
elif (self.LA(1)==REF2D) and (_tokenSet_0.member(self.LA(2))):
|
||||
pass
|
||||
ref2d_tok = self.LT(1)
|
||||
self.match(REF2D)
|
||||
# print "**ref2d %s %s" % (ref2d_tok.text, arg_type)
|
||||
r, c = Utils.cell_to_packed_rowcol(ref2d_tok.text)
|
||||
ptg = ptgRefR + _RVAdeltaRef[arg_type]
|
||||
self.rpn += struct.pack("<B2H", ptg, r, c)
|
||||
elif (self.LA(1)==REF2D) and (self.LA(2)==COLON):
|
||||
pass
|
||||
ref2d1_tok = self.LT(1)
|
||||
self.match(REF2D)
|
||||
self.match(COLON)
|
||||
ref2d2_tok = self.LT(1)
|
||||
self.match(REF2D)
|
||||
r1, c1 = Utils.cell_to_packed_rowcol(ref2d1_tok.text)
|
||||
r2, c2 = Utils.cell_to_packed_rowcol(ref2d2_tok.text)
|
||||
ptg = ptgAreaR + _RVAdeltaArea[arg_type]
|
||||
self.rpn += struct.pack("<B4H", ptg, r1, r2, c1, c2)
|
||||
elif (self.LA(1)==INT_CONST or self.LA(1)==NAME or self.LA(1)==QUOTENAME) and (self.LA(2)==COLON or self.LA(2)==BANG):
|
||||
pass
|
||||
sheet1=self.sheet()
|
||||
sheet2 = sheet1
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [COLON]:
|
||||
pass
|
||||
self.match(COLON)
|
||||
sheet2=self.sheet()
|
||||
elif la1 and la1 in [BANG]:
|
||||
pass
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
self.match(BANG)
|
||||
ref3d_ref2d = self.LT(1)
|
||||
self.match(REF2D)
|
||||
ptg = ptgRef3dR + _RVAdeltaRef[arg_type]
|
||||
rpn_ref2d = ""
|
||||
r1, c1 = Utils.cell_to_packed_rowcol(ref3d_ref2d.text)
|
||||
rpn_ref2d = struct.pack("<3H", 0x0000, r1, c1)
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [COLON]:
|
||||
pass
|
||||
self.match(COLON)
|
||||
ref3d_ref2d2 = self.LT(1)
|
||||
self.match(REF2D)
|
||||
ptg = ptgArea3dR + _RVAdeltaArea[arg_type]
|
||||
r2, c2 = Utils.cell_to_packed_rowcol(ref3d_ref2d2.text)
|
||||
rpn_ref2d = struct.pack("<5H", 0x0000, r1, r2, c1, c2)
|
||||
elif la1 and la1 in [EOF,EQ,NE,GT,LT,GE,LE,ADD,SUB,MUL,DIV,POWER,PERCENT,RP,COMMA,SEMICOLON,CONCAT]:
|
||||
pass
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
self.rpn += struct.pack("<B", ptg)
|
||||
self.sheet_references.append((sheet1, sheet2, len(self.rpn)))
|
||||
self.rpn += rpn_ref2d
|
||||
elif (self.LA(1)==NAME) and (_tokenSet_0.member(self.LA(2))):
|
||||
pass
|
||||
name_tok = self.LT(1)
|
||||
self.match(NAME)
|
||||
raise Exception("[formula] found unexpected NAME token (%r)" % name_tok.txt)
|
||||
# #### TODO: handle references to defined names here
|
||||
elif (self.LA(1)==NAME) and (self.LA(2)==LP):
|
||||
pass
|
||||
func_tok = self.LT(1)
|
||||
self.match(NAME)
|
||||
func_toku = func_tok.text.upper()
|
||||
if func_toku in all_funcs_by_name:
|
||||
(opcode,
|
||||
min_argc,
|
||||
max_argc,
|
||||
func_type,
|
||||
arg_type_str) = all_funcs_by_name[func_toku]
|
||||
arg_type_list = list(arg_type_str)
|
||||
else:
|
||||
raise Exception("[formula] unknown function (%s)" % func_tok.text)
|
||||
# print "**func_tok1 %s %s" % (func_toku, func_type)
|
||||
xcall = opcode < 0
|
||||
if xcall:
|
||||
# The name of the add-in function is passed as the 1st arg
|
||||
# of the hidden XCALL function
|
||||
self.xcall_references.append((func_toku, len(self.rpn) + 1))
|
||||
self.rpn += struct.pack("<BHHH",
|
||||
ptgNameXR,
|
||||
0xadde, # ##PATCHME## index to REF entry in EXTERNSHEET record
|
||||
0xefbe, # ##PATCHME## one-based index to EXTERNNAME record
|
||||
0x0000) # unused
|
||||
self.match(LP)
|
||||
arg_count=self.expr_list(arg_type_list, min_argc, max_argc)
|
||||
self.match(RP)
|
||||
if arg_count > max_argc or arg_count < min_argc:
|
||||
raise Exception, "%d parameters for function: %s" % (arg_count, func_tok.text)
|
||||
if xcall:
|
||||
func_ptg = ptgFuncVarR + _RVAdelta[func_type]
|
||||
self.rpn += struct.pack("<2BH", func_ptg, arg_count + 1, 255) # 255 is magic XCALL function
|
||||
elif min_argc == max_argc:
|
||||
func_ptg = ptgFuncR + _RVAdelta[func_type]
|
||||
self.rpn += struct.pack("<BH", func_ptg, opcode)
|
||||
elif arg_count == 1 and func_tok.text.upper() == "SUM":
|
||||
self.rpn += struct.pack("<BBH", ptgAttr, 0x10, 0) # tAttrSum
|
||||
else:
|
||||
func_ptg = ptgFuncVarR + _RVAdelta[func_type]
|
||||
self.rpn += struct.pack("<2BH", func_ptg, arg_count, opcode)
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
|
||||
def sheet(self):
|
||||
ref = None
|
||||
|
||||
sheet_ref_name = None
|
||||
sheet_ref_int = None
|
||||
sheet_ref_quote = None
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [NAME]:
|
||||
pass
|
||||
sheet_ref_name = self.LT(1)
|
||||
self.match(NAME)
|
||||
ref = sheet_ref_name.text
|
||||
elif la1 and la1 in [INT_CONST]:
|
||||
pass
|
||||
sheet_ref_int = self.LT(1)
|
||||
self.match(INT_CONST)
|
||||
ref = sheet_ref_int.text
|
||||
elif la1 and la1 in [QUOTENAME]:
|
||||
pass
|
||||
sheet_ref_quote = self.LT(1)
|
||||
self.match(QUOTENAME)
|
||||
ref = sheet_ref_quote.text[1:-1].replace("''", "'")
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
return ref
|
||||
|
||||
def expr_list(self,
|
||||
arg_type_list, min_argc, max_argc
|
||||
):
|
||||
arg_cnt = None
|
||||
|
||||
arg_cnt = 0
|
||||
arg_type = arg_type_list[arg_cnt]
|
||||
# print "**expr_list1[%d] req=%s" % (arg_cnt, arg_type)
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [TRUE_CONST,FALSE_CONST,STR_CONST,NUM_CONST,INT_CONST,FUNC_IF,FUNC_CHOOSE,NAME,QUOTENAME,SUB,LP,REF2D]:
|
||||
pass
|
||||
self.expr(arg_type)
|
||||
arg_cnt += 1
|
||||
while True:
|
||||
if (self.LA(1)==COMMA or self.LA(1)==SEMICOLON):
|
||||
pass
|
||||
if arg_cnt < len(arg_type_list):
|
||||
arg_type = arg_type_list[arg_cnt]
|
||||
else:
|
||||
arg_type = arg_type_list[-1]
|
||||
if arg_type == "+":
|
||||
arg_type = arg_type_list[-2]
|
||||
# print "**expr_list2[%d] req=%s" % (arg_cnt, arg_type)
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [SEMICOLON]:
|
||||
pass
|
||||
self.match(SEMICOLON)
|
||||
elif la1 and la1 in [COMMA]:
|
||||
pass
|
||||
self.match(COMMA)
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
la1 = self.LA(1)
|
||||
if False:
|
||||
pass
|
||||
elif la1 and la1 in [TRUE_CONST,FALSE_CONST,STR_CONST,NUM_CONST,INT_CONST,FUNC_IF,FUNC_CHOOSE,NAME,QUOTENAME,SUB,LP,REF2D]:
|
||||
pass
|
||||
self.expr(arg_type)
|
||||
elif la1 and la1 in [RP,COMMA,SEMICOLON]:
|
||||
pass
|
||||
self.rpn += struct.pack("B", ptgMissArg)
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
arg_cnt += 1
|
||||
else:
|
||||
break
|
||||
|
||||
elif la1 and la1 in [RP]:
|
||||
pass
|
||||
else:
|
||||
raise antlr.NoViableAltException(self.LT(1), self.getFilename())
|
||||
|
||||
return arg_cnt
|
||||
|
||||
|
||||
_tokenNames = [
|
||||
"<0>",
|
||||
"EOF",
|
||||
"<2>",
|
||||
"NULL_TREE_LOOKAHEAD",
|
||||
"TRUE_CONST",
|
||||
"FALSE_CONST",
|
||||
"STR_CONST",
|
||||
"NUM_CONST",
|
||||
"INT_CONST",
|
||||
"FUNC_IF",
|
||||
"FUNC_CHOOSE",
|
||||
"NAME",
|
||||
"QUOTENAME",
|
||||
"EQ",
|
||||
"NE",
|
||||
"GT",
|
||||
"LT",
|
||||
"GE",
|
||||
"LE",
|
||||
"ADD",
|
||||
"SUB",
|
||||
"MUL",
|
||||
"DIV",
|
||||
"POWER",
|
||||
"PERCENT",
|
||||
"LP",
|
||||
"RP",
|
||||
"LB",
|
||||
"RB",
|
||||
"COLON",
|
||||
"COMMA",
|
||||
"SEMICOLON",
|
||||
"REF2D",
|
||||
"REF2D_R1C1",
|
||||
"BANG",
|
||||
"CONCAT"
|
||||
]
|
||||
|
||||
|
||||
### generate bit set
|
||||
def mk_tokenSet_0():
|
||||
### var1
|
||||
data = [ 37681618946L, 0L]
|
||||
return data
|
||||
_tokenSet_0 = antlr.BitSet(mk_tokenSet_0())
|
||||
|
||||
@@ -0,0 +1,862 @@
|
||||
# -*- coding: ascii -*-
|
||||
"""
|
||||
lots of Excel Magic Numbers
|
||||
"""
|
||||
|
||||
# Boundaries BIFF8+
|
||||
|
||||
MAX_ROW = 65536
|
||||
MAX_COL = 256
|
||||
|
||||
|
||||
biff_records = {
|
||||
0x0000: "DIMENSIONS",
|
||||
0x0001: "BLANK",
|
||||
0x0002: "INTEGER",
|
||||
0x0003: "NUMBER",
|
||||
0x0004: "LABEL",
|
||||
0x0005: "BOOLERR",
|
||||
0x0006: "FORMULA",
|
||||
0x0007: "STRING",
|
||||
0x0008: "ROW",
|
||||
0x0009: "BOF",
|
||||
0x000A: "EOF",
|
||||
0x000B: "INDEX",
|
||||
0x000C: "CALCCOUNT",
|
||||
0x000D: "CALCMODE",
|
||||
0x000E: "PRECISION",
|
||||
0x000F: "REFMODE",
|
||||
0x0010: "DELTA",
|
||||
0x0011: "ITERATION",
|
||||
0x0012: "PROTECT",
|
||||
0x0013: "PASSWORD",
|
||||
0x0014: "HEADER",
|
||||
0x0015: "FOOTER",
|
||||
0x0016: "EXTERNCOUNT",
|
||||
0x0017: "EXTERNSHEET",
|
||||
0x0018: "NAME",
|
||||
0x0019: "WINDOWPROTECT",
|
||||
0x001A: "VERTICALPAGEBREAKS",
|
||||
0x001B: "HORIZONTALPAGEBREAKS",
|
||||
0x001C: "NOTE",
|
||||
0x001D: "SELECTION",
|
||||
0x001E: "FORMAT",
|
||||
0x001F: "FORMATCOUNT",
|
||||
0x0020: "COLUMNDEFAULT",
|
||||
0x0021: "ARRAY",
|
||||
0x0022: "1904",
|
||||
0x0023: "EXTERNNAME",
|
||||
0x0024: "COLWIDTH",
|
||||
0x0025: "DEFAULTROWHEIGHT",
|
||||
0x0026: "LEFTMARGIN",
|
||||
0x0027: "RIGHTMARGIN",
|
||||
0x0028: "TOPMARGIN",
|
||||
0x0029: "BOTTOMMARGIN",
|
||||
0x002A: "PRINTHEADERS",
|
||||
0x002B: "PRINTGRIDLINES",
|
||||
0x002F: "FILEPASS",
|
||||
0x0031: "FONT",
|
||||
0x0036: "TABLE",
|
||||
0x003C: "CONTINUE",
|
||||
0x003D: "WINDOW1",
|
||||
0x003E: "WINDOW2",
|
||||
0x0040: "BACKUP",
|
||||
0x0041: "PANE",
|
||||
0x0042: "CODEPAGE",
|
||||
0x0043: "XF",
|
||||
0x0044: "IXFE",
|
||||
0x0045: "EFONT",
|
||||
0x004D: "PLS",
|
||||
0x0050: "DCON",
|
||||
0x0051: "DCONREF",
|
||||
0x0053: "DCONNAME",
|
||||
0x0055: "DEFCOLWIDTH",
|
||||
0x0056: "BUILTINFMTCNT",
|
||||
0x0059: "XCT",
|
||||
0x005A: "CRN",
|
||||
0x005B: "FILESHARING",
|
||||
0x005C: "WRITEACCESS",
|
||||
0x005D: "OBJ",
|
||||
0x005E: "UNCALCED",
|
||||
0x005F: "SAFERECALC",
|
||||
0x0060: "TEMPLATE",
|
||||
0x0063: "OBJPROTECT",
|
||||
0x007D: "COLINFO",
|
||||
0x007E: "RK",
|
||||
0x007F: "IMDATA",
|
||||
0x0080: "GUTS",
|
||||
0x0081: "WSBOOL",
|
||||
0x0082: "GRIDSET",
|
||||
0x0083: "HCENTER",
|
||||
0x0084: "VCENTER",
|
||||
0x0085: "BOUNDSHEET",
|
||||
0x0086: "WRITEPROT",
|
||||
0x0087: "ADDIN",
|
||||
0x0088: "EDG",
|
||||
0x0089: "PUB",
|
||||
0x008C: "COUNTRY",
|
||||
0x008D: "HIDEOBJ",
|
||||
0x008E: "BUNDLESOFFSET",
|
||||
0x008F: "BUNDLEHEADER",
|
||||
0x0090: "SORT",
|
||||
0x0091: "SUB",
|
||||
0x0092: "PALETTE",
|
||||
0x0093: "STYLE",
|
||||
0x0094: "LHRECORD",
|
||||
0x0095: "LHNGRAPH",
|
||||
0x0096: "SOUND",
|
||||
0x0098: "LPR",
|
||||
0x0099: "STANDARDWIDTH",
|
||||
0x009A: "FNGROUPNAME",
|
||||
0x009B: "FILTERMODE",
|
||||
0x009C: "FNGROUPCOUNT",
|
||||
0x009D: "AUTOFILTERINFO",
|
||||
0x009E: "AUTOFILTER",
|
||||
0x00A0: "SCL",
|
||||
0x00A1: "SETUP",
|
||||
0x00A9: "COORDLIST",
|
||||
0x00AB: "GCW",
|
||||
0x00AE: "SCENMAN",
|
||||
0x00AF: "SCENARIO",
|
||||
0x00B0: "SXVIEW",
|
||||
0x00B1: "SXVD",
|
||||
0x00B2: "SXVI",
|
||||
0x00B4: "SXIVD",
|
||||
0x00B5: "SXLI",
|
||||
0x00B6: "SXPI",
|
||||
0x00B8: "DOCROUTE",
|
||||
0x00B9: "RECIPNAME",
|
||||
0x00BC: "SHRFMLA",
|
||||
0x00BD: "MULRK",
|
||||
0x00BE: "MULBLANK",
|
||||
0x00C1: "MMS",
|
||||
0x00C2: "ADDMENU",
|
||||
0x00C3: "DELMENU",
|
||||
0x00C5: "SXDI",
|
||||
0x00C6: "SXDB",
|
||||
0x00C7: "SXFIELD",
|
||||
0x00C8: "SXINDEXLIST",
|
||||
0x00C9: "SXDOUBLE",
|
||||
0x00CD: "SXSTRING",
|
||||
0x00CE: "SXDATETIME",
|
||||
0x00D0: "SXTBL",
|
||||
0x00D1: "SXTBRGITEM",
|
||||
0x00D2: "SXTBPG",
|
||||
0x00D3: "OBPROJ",
|
||||
0x00D5: "SXIDSTM",
|
||||
0x00D6: "RSTRING",
|
||||
0x00D7: "DBCELL",
|
||||
0x00DA: "BOOKBOOL",
|
||||
0x00DC: "SXEXT|PARAMQRY",
|
||||
0x00DD: "SCENPROTECT",
|
||||
0x00DE: "OLESIZE",
|
||||
0x00DF: "UDDESC",
|
||||
0x00E0: "XF",
|
||||
0x00E1: "INTERFACEHDR",
|
||||
0x00E2: "INTERFACEEND",
|
||||
0x00E3: "SXVS",
|
||||
0x00E5: "MERGEDCELLS",
|
||||
0x00E9: "BITMAP",
|
||||
0x00EB: "MSODRAWINGGROUP",
|
||||
0x00EC: "MSODRAWING",
|
||||
0x00ED: "MSODRAWINGSELECTION",
|
||||
0x00F0: "SXRULE",
|
||||
0x00F1: "SXEX",
|
||||
0x00F2: "SXFILT",
|
||||
0x00F6: "SXNAME",
|
||||
0x00F7: "SXSELECT",
|
||||
0x00F8: "SXPAIR",
|
||||
0x00F9: "SXFMLA",
|
||||
0x00FB: "SXFORMAT",
|
||||
0x00FC: "SST",
|
||||
0x00FD: "LABELSST",
|
||||
0x00FF: "EXTSST",
|
||||
0x0100: "SXVDEX",
|
||||
0x0103: "SXFORMULA",
|
||||
0x0122: "SXDBEX",
|
||||
0x0137: "CHTRINSERT",
|
||||
0x0138: "CHTRINFO",
|
||||
0x013B: "CHTRCELLCONTENT",
|
||||
0x013D: "TABID",
|
||||
0x0140: "CHTRMOVERANGE",
|
||||
0x014D: "CHTRINSERTTAB",
|
||||
0x015F: "LABELRANGES",
|
||||
0x0160: "USESELFS",
|
||||
0x0161: "DSF",
|
||||
0x0162: "XL5MODIFY",
|
||||
0x0196: "CHTRHEADER",
|
||||
0x01A9: "USERBVIEW",
|
||||
0x01AA: "USERSVIEWBEGIN",
|
||||
0x01AB: "USERSVIEWEND",
|
||||
0x01AD: "QSI",
|
||||
0x01AE: "SUPBOOK",
|
||||
0x01AF: "PROT4REV",
|
||||
0x01B0: "CONDFMT",
|
||||
0x01B1: "CF",
|
||||
0x01B2: "DVAL",
|
||||
0x01B5: "DCONBIN",
|
||||
0x01B6: "TXO",
|
||||
0x01B7: "REFRESHALL",
|
||||
0x01B8: "HLINK",
|
||||
0x01BA: "CODENAME",
|
||||
0x01BB: "SXFDBTYPE",
|
||||
0x01BC: "PROT4REVPASS",
|
||||
0x01BE: "DV",
|
||||
0x01C0: "XL9FILE",
|
||||
0x01C1: "RECALCID",
|
||||
0x0200: "DIMENSIONS",
|
||||
0x0201: "BLANK",
|
||||
0x0203: "NUMBER",
|
||||
0x0204: "LABEL",
|
||||
0x0205: "BOOLERR",
|
||||
0x0206: "FORMULA",
|
||||
0x0207: "STRING",
|
||||
0x0208: "ROW",
|
||||
0x0209: "BOF",
|
||||
0x020B: "INDEX",
|
||||
0x0218: "NAME",
|
||||
0x0221: "ARRAY",
|
||||
0x0223: "EXTERNNAME",
|
||||
0x0225: "DEFAULTROWHEIGHT",
|
||||
0x0231: "FONT",
|
||||
0x0236: "TABLE",
|
||||
0x023E: "WINDOW2",
|
||||
0x0243: "XF",
|
||||
0x027E: "RK",
|
||||
0x0293: "STYLE",
|
||||
0x0406: "FORMULA",
|
||||
0x0409: "BOF",
|
||||
0x041E: "FORMAT",
|
||||
0x0443: "XF",
|
||||
0x04BC: "SHRFMLA",
|
||||
0x0800: "SCREENTIP",
|
||||
0x0803: "WEBQRYSETTINGS",
|
||||
0x0804: "WEBQRYTABLES",
|
||||
0x0809: "BOF",
|
||||
0x0862: "SHEETLAYOUT",
|
||||
0x0867: "SHEETPROTECTION",
|
||||
0x1001: "UNITS",
|
||||
0x1002: "ChartChart",
|
||||
0x1003: "ChartSeries",
|
||||
0x1006: "ChartDataformat",
|
||||
0x1007: "ChartLineformat",
|
||||
0x1009: "ChartMarkerformat",
|
||||
0x100A: "ChartAreaformat",
|
||||
0x100B: "ChartPieformat",
|
||||
0x100C: "ChartAttachedlabel",
|
||||
0x100D: "ChartSeriestext",
|
||||
0x1014: "ChartChartformat",
|
||||
0x1015: "ChartLegend",
|
||||
0x1016: "ChartSerieslist",
|
||||
0x1017: "ChartBar",
|
||||
0x1018: "ChartLine",
|
||||
0x1019: "ChartPie",
|
||||
0x101A: "ChartArea",
|
||||
0x101B: "ChartScatter",
|
||||
0x101C: "ChartChartline",
|
||||
0x101D: "ChartAxis",
|
||||
0x101E: "ChartTick",
|
||||
0x101F: "ChartValuerange",
|
||||
0x1020: "ChartCatserrange",
|
||||
0x1021: "ChartAxislineformat",
|
||||
0x1022: "ChartFormatlink",
|
||||
0x1024: "ChartDefaulttext",
|
||||
0x1025: "ChartText",
|
||||
0x1026: "ChartFontx",
|
||||
0x1027: "ChartObjectLink",
|
||||
0x1032: "ChartFrame",
|
||||
0x1033: "BEGIN",
|
||||
0x1034: "END",
|
||||
0x1035: "ChartPlotarea",
|
||||
0x103A: "Chart3D",
|
||||
0x103C: "ChartPicf",
|
||||
0x103D: "ChartDropbar",
|
||||
0x103E: "ChartRadar",
|
||||
0x103F: "ChartSurface",
|
||||
0x1040: "ChartRadararea",
|
||||
0x1041: "ChartAxisparent",
|
||||
0x1043: "ChartLegendxn",
|
||||
0x1044: "ChartShtprops",
|
||||
0x1045: "ChartSertocrt",
|
||||
0x1046: "ChartAxesused",
|
||||
0x1048: "ChartSbaseref",
|
||||
0x104A: "ChartSerparent",
|
||||
0x104B: "ChartSerauxtrend",
|
||||
0x104E: "ChartIfmt",
|
||||
0x104F: "ChartPos",
|
||||
0x1050: "ChartAlruns",
|
||||
0x1051: "ChartAI",
|
||||
0x105B: "ChartSerauxerrbar",
|
||||
0x105D: "ChartSerfmt",
|
||||
0x105F: "Chart3DDataFormat",
|
||||
0x1060: "ChartFbi",
|
||||
0x1061: "ChartBoppop",
|
||||
0x1062: "ChartAxcext",
|
||||
0x1063: "ChartDat",
|
||||
0x1064: "ChartPlotgrowth",
|
||||
0x1065: "ChartSiindex",
|
||||
0x1066: "ChartGelframe",
|
||||
0x1067: "ChartBoppcustom",
|
||||
0xFFFF: ""
|
||||
}
|
||||
|
||||
|
||||
all_funcs_by_name = {
|
||||
# Includes Analysis ToolPak aka ATP aka add-in aka xcall functions,
|
||||
# distinguished by -ve opcode.
|
||||
# name: (opcode, min # args, max # args, func return type, func arg types)
|
||||
# + in func arg types means more of the same.
|
||||
'ABS' : ( 24, 1, 1, 'V', 'V'),
|
||||
'ACCRINT' : ( -1, 6, 7, 'V', 'VVVVVVV'),
|
||||
'ACCRINTM' : ( -1, 3, 5, 'V', 'VVVVV'),
|
||||
'ACOS' : ( 99, 1, 1, 'V', 'V'),
|
||||
'ACOSH' : (233, 1, 1, 'V', 'V'),
|
||||
'ADDRESS' : (219, 2, 5, 'V', 'VVVVV'),
|
||||
'AMORDEGRC' : ( -1, 7, 7, 'V', 'VVVVVVV'),
|
||||
'AMORLINC' : ( -1, 7, 7, 'V', 'VVVVVVV'),
|
||||
'AND' : ( 36, 1, 30, 'V', 'D+'),
|
||||
'AREAS' : ( 75, 1, 1, 'V', 'R'),
|
||||
'ASC' : (214, 1, 1, 'V', 'V'),
|
||||
'ASIN' : ( 98, 1, 1, 'V', 'V'),
|
||||
'ASINH' : (232, 1, 1, 'V', 'V'),
|
||||
'ATAN' : ( 18, 1, 1, 'V', 'V'),
|
||||
'ATAN2' : ( 97, 2, 2, 'V', 'VV'),
|
||||
'ATANH' : (234, 1, 1, 'V', 'V'),
|
||||
'AVEDEV' : (269, 1, 30, 'V', 'D+'),
|
||||
'AVERAGE' : ( 5, 1, 30, 'V', 'D+'),
|
||||
'AVERAGEA' : (361, 1, 30, 'V', 'D+'),
|
||||
'BAHTTEXT' : (368, 1, 1, 'V', 'V'),
|
||||
'BESSELI' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'BESSELJ' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'BESSELK' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'BESSELY' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'BETADIST' : (270, 3, 5, 'V', 'VVVVV'),
|
||||
'BETAINV' : (272, 3, 5, 'V', 'VVVVV'),
|
||||
'BIN2DEC' : ( -1, 1, 1, 'V', 'V'),
|
||||
'BIN2HEX' : ( -1, 1, 2, 'V', 'VV'),
|
||||
'BIN2OCT' : ( -1, 1, 2, 'V', 'VV'),
|
||||
'BINOMDIST' : (273, 4, 4, 'V', 'VVVV'),
|
||||
'CEILING' : (288, 2, 2, 'V', 'VV'),
|
||||
'CELL' : (125, 1, 2, 'V', 'VR'),
|
||||
'CHAR' : (111, 1, 1, 'V', 'V'),
|
||||
'CHIDIST' : (274, 2, 2, 'V', 'VV'),
|
||||
'CHIINV' : (275, 2, 2, 'V', 'VV'),
|
||||
'CHITEST' : (306, 2, 2, 'V', 'AA'),
|
||||
'CHOOSE' : (100, 2, 30, 'R', 'VR+'),
|
||||
'CLEAN' : (162, 1, 1, 'V', 'V'),
|
||||
'CODE' : (121, 1, 1, 'V', 'V'),
|
||||
'COLUMN' : ( 9, 0, 1, 'V', 'R'),
|
||||
'COLUMNS' : ( 77, 1, 1, 'V', 'R'),
|
||||
'COMBIN' : (276, 2, 2, 'V', 'VV'),
|
||||
'COMPLEX' : ( -1, 2, 3, 'V', 'VVV'),
|
||||
'CONCATENATE' : (336, 1, 30, 'V', 'V+'),
|
||||
'CONFIDENCE' : (277, 3, 3, 'V', 'VVV'),
|
||||
'CONVERT' : ( -1, 3, 3, 'V', 'VVV'),
|
||||
'CORREL' : (307, 2, 2, 'V', 'AA'),
|
||||
'COS' : ( 16, 1, 1, 'V', 'V'),
|
||||
'COSH' : (230, 1, 1, 'V', 'V'),
|
||||
'COUNT' : ( 0, 1, 30, 'V', 'D+'),
|
||||
'COUNTA' : (169, 1, 30, 'V', 'D+'),
|
||||
'COUNTBLANK' : (347, 1, 1, 'V', 'R'),
|
||||
'COUNTIF' : (346, 2, 2, 'V', 'RV'),
|
||||
'COUPDAYBS' : ( -1, 3, 5, 'V', 'VVVVV'),
|
||||
'COUPDAYS' : ( -1, 3, 5, 'V', 'VVVVV'),
|
||||
'COUPDAYSNC' : ( -1, 3, 5, 'V', 'VVVVV'),
|
||||
'COUPNCD' : ( -1, 3, 5, 'V', 'VVVVV'),
|
||||
'COUPNUM' : ( -1, 3, 5, 'V', 'VVVVV'),
|
||||
'COUPPCD' : ( -1, 3, 5, 'V', 'VVVVV'),
|
||||
'COVAR' : (308, 2, 2, 'V', 'AA'),
|
||||
'CRITBINOM' : (278, 3, 3, 'V', 'VVV'),
|
||||
'CUMIPMT' : ( -1, 6, 6, 'V', 'VVVVVV'),
|
||||
'CUMPRINC' : ( -1, 6, 6, 'V', 'VVVVVV'),
|
||||
'DATE' : ( 65, 3, 3, 'V', 'VVV'),
|
||||
'DATEDIF' : (351, 3, 3, 'V', 'VVV'),
|
||||
'DATEVALUE' : (140, 1, 1, 'V', 'V'),
|
||||
'DAVERAGE' : ( 42, 3, 3, 'V', 'RRR'),
|
||||
'DAY' : ( 67, 1, 1, 'V', 'V'),
|
||||
'DAYS360' : (220, 2, 3, 'V', 'VVV'),
|
||||
'DB' : (247, 4, 5, 'V', 'VVVVV'),
|
||||
'DBCS' : (215, 1, 1, 'V', 'V'),
|
||||
'DCOUNT' : ( 40, 3, 3, 'V', 'RRR'),
|
||||
'DCOUNTA' : (199, 3, 3, 'V', 'RRR'),
|
||||
'DDB' : (144, 4, 5, 'V', 'VVVVV'),
|
||||
'DEC2BIN' : ( -1, 1, 2, 'V', 'VV'),
|
||||
'DEC2HEX' : ( -1, 1, 2, 'V', 'VV'),
|
||||
'DEC2OCT' : ( -1, 1, 2, 'V', 'VV'),
|
||||
'DEGREES' : (343, 1, 1, 'V', 'V'),
|
||||
'DELTA' : ( -1, 1, 2, 'V', 'VV'),
|
||||
'DEVSQ' : (318, 1, 30, 'V', 'D+'),
|
||||
'DGET' : (235, 3, 3, 'V', 'RRR'),
|
||||
'DISC' : ( -1, 4, 5, 'V', 'VVVVV'),
|
||||
'DMAX' : ( 44, 3, 3, 'V', 'RRR'),
|
||||
'DMIN' : ( 43, 3, 3, 'V', 'RRR'),
|
||||
'DOLLAR' : ( 13, 1, 2, 'V', 'VV'),
|
||||
'DOLLARDE' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'DOLLARFR' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'DPRODUCT' : (189, 3, 3, 'V', 'RRR'),
|
||||
'DSTDEV' : ( 45, 3, 3, 'V', 'RRR'),
|
||||
'DSTDEVP' : (195, 3, 3, 'V', 'RRR'),
|
||||
'DSUM' : ( 41, 3, 3, 'V', 'RRR'),
|
||||
'DURATION' : ( -1, 5, 6, 'V', 'VVVVVV'),
|
||||
'DVAR' : ( 47, 3, 3, 'V', 'RRR'),
|
||||
'DVARP' : (196, 3, 3, 'V', 'RRR'),
|
||||
'EDATE' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'EFFECT' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'EOMONTH' : ( -1, 1, 2, 'V', 'VV'),
|
||||
'ERF' : ( -1, 1, 2, 'V', 'VV'),
|
||||
'ERFC' : ( -1, 1, 1, 'V', 'V'),
|
||||
'ERROR.TYPE' : (261, 1, 1, 'V', 'V'),
|
||||
'EVEN' : (279, 1, 1, 'V', 'V'),
|
||||
'EXACT' : (117, 2, 2, 'V', 'VV'),
|
||||
'EXP' : ( 21, 1, 1, 'V', 'V'),
|
||||
'EXPONDIST' : (280, 3, 3, 'V', 'VVV'),
|
||||
'FACT' : (184, 1, 1, 'V', 'V'),
|
||||
'FACTDOUBLE' : ( -1, 1, 1, 'V', 'V'),
|
||||
'FALSE' : ( 35, 0, 0, 'V', '-'),
|
||||
'FDIST' : (281, 3, 3, 'V', 'VVV'),
|
||||
'FIND' : (124, 2, 3, 'V', 'VVV'),
|
||||
'FINDB' : (205, 2, 3, 'V', 'VVV'),
|
||||
'FINV' : (282, 3, 3, 'V', 'VVV'),
|
||||
'FISHER' : (283, 1, 1, 'V', 'V'),
|
||||
'FISHERINV' : (284, 1, 1, 'V', 'V'),
|
||||
'FIXED' : ( 14, 2, 3, 'V', 'VVV'),
|
||||
'FLOOR' : (285, 2, 2, 'V', 'VV'),
|
||||
'FORECAST' : (309, 3, 3, 'V', 'VAA'),
|
||||
'FREQUENCY' : (252, 2, 2, 'A', 'RR'),
|
||||
'FTEST' : (310, 2, 2, 'V', 'AA'),
|
||||
'FV' : ( 57, 3, 5, 'V', 'VVVVV'),
|
||||
'FVSCHEDULE' : ( -1, 2, 2, 'V', 'VA'),
|
||||
'GAMMADIST' : (286, 4, 4, 'V', 'VVVV'),
|
||||
'GAMMAINV' : (287, 3, 3, 'V', 'VVV'),
|
||||
'GAMMALN' : (271, 1, 1, 'V', 'V'),
|
||||
'GCD' : ( -1, 1, 29, 'V', 'V+'),
|
||||
'GEOMEAN' : (319, 1, 30, 'V', 'D+'),
|
||||
'GESTEP' : ( -1, 1, 2, 'V', 'VV'),
|
||||
'GETPIVOTDATA': (358, 2, 30, 'A', 'VAV+'),
|
||||
'GROWTH' : ( 52, 1, 4, 'A', 'RRRV'),
|
||||
'HARMEAN' : (320, 1, 30, 'V', 'D+'),
|
||||
'HEX2BIN' : ( -1, 1, 2, 'V', 'VV'),
|
||||
'HEX2DEC' : ( -1, 1, 1, 'V', 'V'),
|
||||
'HEX2OCT' : ( -1, 1, 2, 'V', 'VV'),
|
||||
'HLOOKUP' : (101, 3, 4, 'V', 'VRRV'),
|
||||
'HOUR' : ( 71, 1, 1, 'V', 'V'),
|
||||
'HYPERLINK' : (359, 1, 2, 'V', 'VV'),
|
||||
'HYPGEOMDIST' : (289, 4, 4, 'V', 'VVVV'),
|
||||
'IF' : ( 1, 2, 3, 'R', 'VRR'),
|
||||
'IMABS' : ( -1, 1, 1, 'V', 'V'),
|
||||
'IMAGINARY' : ( -1, 1, 1, 'V', 'V'),
|
||||
'IMARGUMENT' : ( -1, 1, 1, 'V', 'V'),
|
||||
'IMCONJUGATE' : ( -1, 1, 1, 'V', 'V'),
|
||||
'IMCOS' : ( -1, 1, 1, 'V', 'V'),
|
||||
'IMDIV' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'IMEXP' : ( -1, 1, 1, 'V', 'V'),
|
||||
'IMLN' : ( -1, 1, 1, 'V', 'V'),
|
||||
'IMLOG10' : ( -1, 1, 1, 'V', 'V'),
|
||||
'IMLOG2' : ( -1, 1, 1, 'V', 'V'),
|
||||
'IMPOWER' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'IMPRODUCT' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'IMREAL' : ( -1, 1, 1, 'V', 'V'),
|
||||
'IMSIN' : ( -1, 1, 1, 'V', 'V'),
|
||||
'IMSQRT' : ( -1, 1, 1, 'V', 'V'),
|
||||
'IMSUB' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'IMSUM' : ( -1, 1, 29, 'V', 'V+'),
|
||||
'INDEX' : ( 29, 2, 4, 'R', 'RVVV'),
|
||||
'INDIRECT' : (148, 1, 2, 'R', 'VV'),
|
||||
'INFO' : (244, 1, 1, 'V', 'V'),
|
||||
'INT' : ( 25, 1, 1, 'V', 'V'),
|
||||
'INTERCEPT' : (311, 2, 2, 'V', 'AA'),
|
||||
'INTRATE' : ( -1, 4, 5, 'V', 'VVVVV'),
|
||||
'IPMT' : (167, 4, 6, 'V', 'VVVVVV'),
|
||||
'IRR' : ( 62, 1, 2, 'V', 'RV'),
|
||||
'ISBLANK' : (129, 1, 1, 'V', 'V'),
|
||||
'ISERR' : (126, 1, 1, 'V', 'V'),
|
||||
'ISERROR' : ( 3, 1, 1, 'V', 'V'),
|
||||
'ISEVEN' : ( -1, 1, 1, 'V', 'V'),
|
||||
'ISLOGICAL' : (198, 1, 1, 'V', 'V'),
|
||||
'ISNA' : ( 2, 1, 1, 'V', 'V'),
|
||||
'ISNONTEXT' : (190, 1, 1, 'V', 'V'),
|
||||
'ISNUMBER' : (128, 1, 1, 'V', 'V'),
|
||||
'ISODD' : ( -1, 1, 1, 'V', 'V'),
|
||||
'ISPMT' : (350, 4, 4, 'V', 'VVVV'),
|
||||
'ISREF' : (105, 1, 1, 'V', 'R'),
|
||||
'ISTEXT' : (127, 1, 1, 'V', 'V'),
|
||||
'KURT' : (322, 1, 30, 'V', 'D+'),
|
||||
'LARGE' : (325, 2, 2, 'V', 'RV'),
|
||||
'LCM' : ( -1, 1, 29, 'V', 'V+'),
|
||||
'LEFT' : (115, 1, 2, 'V', 'VV'),
|
||||
'LEFTB' : (208, 1, 2, 'V', 'VV'),
|
||||
'LEN' : ( 32, 1, 1, 'V', 'V'),
|
||||
'LENB' : (211, 1, 1, 'V', 'V'),
|
||||
'LINEST' : ( 49, 1, 4, 'A', 'RRVV'),
|
||||
'LN' : ( 22, 1, 1, 'V', 'V'),
|
||||
'LOG' : (109, 1, 2, 'V', 'VV'),
|
||||
'LOG10' : ( 23, 1, 1, 'V', 'V'),
|
||||
'LOGEST' : ( 51, 1, 4, 'A', 'RRVV'),
|
||||
'LOGINV' : (291, 3, 3, 'V', 'VVV'),
|
||||
'LOGNORMDIST' : (290, 3, 3, 'V', 'VVV'),
|
||||
'LOOKUP' : ( 28, 2, 3, 'V', 'VRR'),
|
||||
'LOWER' : (112, 1, 1, 'V', 'V'),
|
||||
'MATCH' : ( 64, 2, 3, 'V', 'VRR'),
|
||||
'MAX' : ( 7, 1, 30, 'V', 'D+'),
|
||||
'MAXA' : (362, 1, 30, 'V', 'D+'),
|
||||
'MDETERM' : (163, 1, 1, 'V', 'A'),
|
||||
'MDURATION' : ( -1, 5, 6, 'V', 'VVVVVV'),
|
||||
'MEDIAN' : (227, 1, 30, 'V', 'D+'),
|
||||
'MID' : ( 31, 3, 3, 'V', 'VVV'),
|
||||
'MIDB' : (210, 3, 3, 'V', 'VVV'),
|
||||
'MIN' : ( 6, 1, 30, 'V', 'D+'),
|
||||
'MINA' : (363, 1, 30, 'V', 'D+'),
|
||||
'MINUTE' : ( 72, 1, 1, 'V', 'V'),
|
||||
'MINVERSE' : (164, 1, 1, 'A', 'A'),
|
||||
'MIRR' : ( 61, 3, 3, 'V', 'RVV'),
|
||||
'MMULT' : (165, 2, 2, 'A', 'AA'),
|
||||
'MOD' : ( 39, 2, 2, 'V', 'VV'),
|
||||
'MODE' : (330, 1, 30, 'V', 'A+'), ################ weird #################
|
||||
'MONTH' : ( 68, 1, 1, 'V', 'V'),
|
||||
'MROUND' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'MULTINOMIAL' : ( -1, 1, 29, 'V', 'V+'),
|
||||
'N' : (131, 1, 1, 'V', 'R'),
|
||||
'NA' : ( 10, 0, 0, 'V', '-'),
|
||||
'NEGBINOMDIST': (292, 3, 3, 'V', 'VVV'),
|
||||
'NETWORKDAYS' : ( -1, 2, 3, 'V', 'VVR'),
|
||||
'NOMINAL' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'NORMDIST' : (293, 4, 4, 'V', 'VVVV'),
|
||||
'NORMINV' : (295, 3, 3, 'V', 'VVV'),
|
||||
'NORMSDIST' : (294, 1, 1, 'V', 'V'),
|
||||
'NORMSINV' : (296, 1, 1, 'V', 'V'),
|
||||
'NOT' : ( 38, 1, 1, 'V', 'V'),
|
||||
'NOW' : ( 74, 0, 0, 'V', '-'),
|
||||
'NPER' : ( 58, 3, 5, 'V', 'VVVVV'),
|
||||
'NPV' : ( 11, 2, 30, 'V', 'VD+'),
|
||||
'OCT2BIN' : ( -1, 1, 2, 'V', 'VV'),
|
||||
'OCT2DEC' : ( -1, 1, 1, 'V', 'V'),
|
||||
'OCT2HEX' : ( -1, 1, 2, 'V', 'VV'),
|
||||
'ODD' : (298, 1, 1, 'V', 'V'),
|
||||
'ODDFPRICE' : ( -1, 9, 9, 'V', 'VVVVVVVVV'),
|
||||
'ODDFYIELD' : ( -1, 9, 9, 'V', 'VVVVVVVVV'),
|
||||
'ODDLPRICE' : ( -1, 8, 8, 'V', 'VVVVVVVV'),
|
||||
'ODDLYIELD' : ( -1, 8, 8, 'V', 'VVVVVVVV'),
|
||||
'OFFSET' : ( 78, 3, 5, 'R', 'RVVVV'),
|
||||
'OR' : ( 37, 1, 30, 'V', 'D+'),
|
||||
'PEARSON' : (312, 2, 2, 'V', 'AA'),
|
||||
'PERCENTILE' : (328, 2, 2, 'V', 'RV'),
|
||||
'PERCENTRANK' : (329, 2, 3, 'V', 'RVV'),
|
||||
'PERMUT' : (299, 2, 2, 'V', 'VV'),
|
||||
'PHONETIC' : (360, 1, 1, 'V', 'R'),
|
||||
'PI' : ( 19, 0, 0, 'V', '-'),
|
||||
'PMT' : ( 59, 3, 5, 'V', 'VVVVV'),
|
||||
'POISSON' : (300, 3, 3, 'V', 'VVV'),
|
||||
'POWER' : (337, 2, 2, 'V', 'VV'),
|
||||
'PPMT' : (168, 4, 6, 'V', 'VVVVVV'),
|
||||
'PRICE' : ( -1, 6, 7, 'V', 'VVVVVVV'),
|
||||
'PRICEDISC' : ( -1, 4, 5, 'V', 'VVVVV'),
|
||||
'PRICEMAT' : ( -1, 5, 6, 'V', 'VVVVVV'),
|
||||
'PROB' : (317, 3, 4, 'V', 'AAVV'),
|
||||
'PRODUCT' : (183, 1, 30, 'V', 'D+'),
|
||||
'PROPER' : (114, 1, 1, 'V', 'V'),
|
||||
'PV' : ( 56, 3, 5, 'V', 'VVVVV'),
|
||||
'QUARTILE' : (327, 2, 2, 'V', 'RV'),
|
||||
'QUOTIENT' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'RADIANS' : (342, 1, 1, 'V', 'V'),
|
||||
'RAND' : ( 63, 0, 0, 'V', '-'),
|
||||
'RANDBETWEEN' : ( -1, 2, 2, 'V', 'VV'),
|
||||
'RANK' : (216, 2, 3, 'V', 'VRV'),
|
||||
'RATE' : ( 60, 3, 6, 'V', 'VVVVVV'),
|
||||
'RECEIVED' : ( -1, 4, 5, 'V', 'VVVVV'),
|
||||
'REPLACE' : (119, 4, 4, 'V', 'VVVV'),
|
||||
'REPLACEB' : (207, 4, 4, 'V', 'VVVV'),
|
||||
'REPT' : ( 30, 2, 2, 'V', 'VV'),
|
||||
'RIGHT' : (116, 1, 2, 'V', 'VV'),
|
||||
'RIGHTB' : (209, 1, 2, 'V', 'VV'),
|
||||
'ROMAN' : (354, 1, 2, 'V', 'VV'),
|
||||
'ROUND' : ( 27, 2, 2, 'V', 'VV'),
|
||||
'ROUNDDOWN' : (213, 2, 2, 'V', 'VV'),
|
||||
'ROUNDUP' : (212, 2, 2, 'V', 'VV'),
|
||||
'ROW' : ( 8, 0, 1, 'V', 'R'),
|
||||
'ROWS' : ( 76, 1, 1, 'V', 'R'),
|
||||
'RSQ' : (313, 2, 2, 'V', 'AA'),
|
||||
'RTD' : (379, 3, 30, 'A', 'VVV+'),
|
||||
'SEARCH' : ( 82, 2, 3, 'V', 'VVV'),
|
||||
'SEARCHB' : (206, 2, 3, 'V', 'VVV'),
|
||||
'SECOND' : ( 73, 1, 1, 'V', 'V'),
|
||||
'SERIESSUM' : ( -1, 4, 4, 'V', 'VVVA'),
|
||||
'SIGN' : ( 26, 1, 1, 'V', 'V'),
|
||||
'SIN' : ( 15, 1, 1, 'V', 'V'),
|
||||
'SINH' : (229, 1, 1, 'V', 'V'),
|
||||
'SKEW' : (323, 1, 30, 'V', 'D+'),
|
||||
'SLN' : (142, 3, 3, 'V', 'VVV'),
|
||||
'SLOPE' : (315, 2, 2, 'V', 'AA'),
|
||||
'SMALL' : (326, 2, 2, 'V', 'RV'),
|
||||
'SQRT' : ( 20, 1, 1, 'V', 'V'),
|
||||
'SQRTPI' : ( -1, 1, 1, 'V', 'V'),
|
||||
'STANDARDIZE' : (297, 3, 3, 'V', 'VVV'),
|
||||
'STDEV' : ( 12, 1, 30, 'V', 'D+'),
|
||||
'STDEVA' : (366, 1, 30, 'V', 'D+'),
|
||||
'STDEVP' : (193, 1, 30, 'V', 'D+'),
|
||||
'STDEVPA' : (364, 1, 30, 'V', 'D+'),
|
||||
'STEYX' : (314, 2, 2, 'V', 'AA'),
|
||||
'SUBSTITUTE' : (120, 3, 4, 'V', 'VVVV'),
|
||||
'SUBTOTAL' : (344, 2, 30, 'V', 'VR+'),
|
||||
'SUM' : ( 4, 1, 30, 'V', 'D+'),
|
||||
'SUMIF' : (345, 2, 3, 'V', 'RVR'),
|
||||
'SUMPRODUCT' : (228, 1, 30, 'V', 'A+'),
|
||||
'SUMSQ' : (321, 1, 30, 'V', 'D+'),
|
||||
'SUMX2MY2' : (304, 2, 2, 'V', 'AA'),
|
||||
'SUMX2PY2' : (305, 2, 2, 'V', 'AA'),
|
||||
'SUMXMY2' : (303, 2, 2, 'V', 'AA'),
|
||||
'SYD' : (143, 4, 4, 'V', 'VVVV'),
|
||||
'T' : (130, 1, 1, 'V', 'R'),
|
||||
'TAN' : ( 17, 1, 1, 'V', 'V'),
|
||||
'TANH' : (231, 1, 1, 'V', 'V'),
|
||||
'TBILLEQ' : ( -1, 3, 3, 'V', 'VVV'),
|
||||
'TBILLPRICE' : ( -1, 3, 3, 'V', 'VVV'),
|
||||
'TBILLYIELD' : ( -1, 3, 3, 'V', 'VVV'),
|
||||
'TDIST' : (301, 3, 3, 'V', 'VVV'),
|
||||
'TEXT' : ( 48, 2, 2, 'V', 'VV'),
|
||||
'TIME' : ( 66, 3, 3, 'V', 'VVV'),
|
||||
'TIMEVALUE' : (141, 1, 1, 'V', 'V'),
|
||||
'TINV' : (332, 2, 2, 'V', 'VV'),
|
||||
'TODAY' : (221, 0, 0, 'V', '-'),
|
||||
'TRANSPOSE' : ( 83, 1, 1, 'A', 'A'),
|
||||
'TREND' : ( 50, 1, 4, 'A', 'RRRV'),
|
||||
'TRIM' : (118, 1, 1, 'V', 'V'),
|
||||
'TRIMMEAN' : (331, 2, 2, 'V', 'RV'),
|
||||
'TRUE' : ( 34, 0, 0, 'V', '-'),
|
||||
'TRUNC' : (197, 1, 2, 'V', 'VV'),
|
||||
'TTEST' : (316, 4, 4, 'V', 'AAVV'),
|
||||
'TYPE' : ( 86, 1, 1, 'V', 'V'),
|
||||
'UPPER' : (113, 1, 1, 'V', 'V'),
|
||||
'USDOLLAR' : (204, 1, 2, 'V', 'VV'),
|
||||
'VALUE' : ( 33, 1, 1, 'V', 'V'),
|
||||
'VAR' : ( 46, 1, 30, 'V', 'D+'),
|
||||
'VARA' : (367, 1, 30, 'V', 'D+'),
|
||||
'VARP' : (194, 1, 30, 'V', 'D+'),
|
||||
'VARPA' : (365, 1, 30, 'V', 'D+'),
|
||||
'VDB' : (222, 5, 7, 'V', 'VVVVVVV'),
|
||||
'VLOOKUP' : (102, 3, 4, 'V', 'VRRV'),
|
||||
'WEEKDAY' : ( 70, 1, 2, 'V', 'VV'),
|
||||
'WEEKNUM' : ( -1, 1, 2, 'V', 'VV'),
|
||||
'WEIBULL' : (302, 4, 4, 'V', 'VVVV'),
|
||||
'WORKDAY' : ( -1, 2, 3, 'V', 'VVR'),
|
||||
'XIRR' : ( -1, 2, 3, 'V', 'AAV'),
|
||||
'XNPV' : ( -1, 3, 3, 'V', 'VAA'),
|
||||
'YEAR' : ( 69, 1, 1, 'V', 'V'),
|
||||
'YEARFRAC' : ( -1, 2, 3, 'V', 'VVV'),
|
||||
'YIELD' : ( -1, 6, 7, 'V', 'VVVVVVV'),
|
||||
'YIELDDISC' : ( -1, 4, 5, 'V', 'VVVVV'),
|
||||
'YIELDMAT' : ( -1, 5, 6, 'V', 'VVVVVV'),
|
||||
'ZTEST' : (324, 2, 3, 'V', 'RVV'),
|
||||
}
|
||||
|
||||
# Formulas Parse things
|
||||
|
||||
ptgExp = 0x01
|
||||
ptgTbl = 0x02
|
||||
ptgAdd = 0x03
|
||||
ptgSub = 0x04
|
||||
ptgMul = 0x05
|
||||
ptgDiv = 0x06
|
||||
ptgPower = 0x07
|
||||
ptgConcat = 0x08
|
||||
ptgLT = 0x09
|
||||
ptgLE = 0x0a
|
||||
ptgEQ = 0x0b
|
||||
ptgGE = 0x0c
|
||||
ptgGT = 0x0d
|
||||
ptgNE = 0x0e
|
||||
ptgIsect = 0x0f
|
||||
ptgUnion = 0x10
|
||||
ptgRange = 0x11
|
||||
ptgUplus = 0x12
|
||||
ptgUminus = 0x13
|
||||
ptgPercent = 0x14
|
||||
ptgParen = 0x15
|
||||
ptgMissArg = 0x16
|
||||
ptgStr = 0x17
|
||||
ptgExtend = 0x18
|
||||
ptgAttr = 0x19
|
||||
ptgSheet = 0x1a
|
||||
ptgEndSheet = 0x1b
|
||||
ptgErr = 0x1c
|
||||
ptgBool = 0x1d
|
||||
ptgInt = 0x1e
|
||||
ptgNum = 0x1f
|
||||
|
||||
ptgArrayR = 0x20
|
||||
ptgFuncR = 0x21
|
||||
ptgFuncVarR = 0x22
|
||||
ptgNameR = 0x23
|
||||
ptgRefR = 0x24
|
||||
ptgAreaR = 0x25
|
||||
ptgMemAreaR = 0x26
|
||||
ptgMemErrR = 0x27
|
||||
ptgMemNoMemR = 0x28
|
||||
ptgMemFuncR = 0x29
|
||||
ptgRefErrR = 0x2a
|
||||
ptgAreaErrR = 0x2b
|
||||
ptgRefNR = 0x2c
|
||||
ptgAreaNR = 0x2d
|
||||
ptgMemAreaNR = 0x2e
|
||||
ptgMemNoMemNR = 0x2f
|
||||
ptgNameXR = 0x39
|
||||
ptgRef3dR = 0x3a
|
||||
ptgArea3dR = 0x3b
|
||||
ptgRefErr3dR = 0x3c
|
||||
ptgAreaErr3dR = 0x3d
|
||||
|
||||
ptgArrayV = 0x40
|
||||
ptgFuncV = 0x41
|
||||
ptgFuncVarV = 0x42
|
||||
ptgNameV = 0x43
|
||||
ptgRefV = 0x44
|
||||
ptgAreaV = 0x45
|
||||
ptgMemAreaV = 0x46
|
||||
ptgMemErrV = 0x47
|
||||
ptgMemNoMemV = 0x48
|
||||
ptgMemFuncV = 0x49
|
||||
ptgRefErrV = 0x4a
|
||||
ptgAreaErrV = 0x4b
|
||||
ptgRefNV = 0x4c
|
||||
ptgAreaNV = 0x4d
|
||||
ptgMemAreaNV = 0x4e
|
||||
ptgMemNoMemNV = 0x4f
|
||||
ptgFuncCEV = 0x58
|
||||
ptgNameXV = 0x59
|
||||
ptgRef3dV = 0x5a
|
||||
ptgArea3dV = 0x5b
|
||||
ptgRefErr3dV = 0x5c
|
||||
ptgAreaErr3dV = 0x5d
|
||||
|
||||
ptgArrayA = 0x60
|
||||
ptgFuncA = 0x61
|
||||
ptgFuncVarA = 0x62
|
||||
ptgNameA = 0x63
|
||||
ptgRefA = 0x64
|
||||
ptgAreaA = 0x65
|
||||
ptgMemAreaA = 0x66
|
||||
ptgMemErrA = 0x67
|
||||
ptgMemNoMemA = 0x68
|
||||
ptgMemFuncA = 0x69
|
||||
ptgRefErrA = 0x6a
|
||||
ptgAreaErrA = 0x6b
|
||||
ptgRefNA = 0x6c
|
||||
ptgAreaNA = 0x6d
|
||||
ptgMemAreaNA = 0x6e
|
||||
ptgMemNoMemNA = 0x6f
|
||||
ptgFuncCEA = 0x78
|
||||
ptgNameXA = 0x79
|
||||
ptgRef3dA = 0x7a
|
||||
ptgArea3dA = 0x7b
|
||||
ptgRefErr3dA = 0x7c
|
||||
ptgAreaErr3dA = 0x7d
|
||||
|
||||
|
||||
PtgNames = {
|
||||
ptgExp : "ptgExp",
|
||||
ptgTbl : "ptgTbl",
|
||||
ptgAdd : "ptgAdd",
|
||||
ptgSub : "ptgSub",
|
||||
ptgMul : "ptgMul",
|
||||
ptgDiv : "ptgDiv",
|
||||
ptgPower : "ptgPower",
|
||||
ptgConcat : "ptgConcat",
|
||||
ptgLT : "ptgLT",
|
||||
ptgLE : "ptgLE",
|
||||
ptgEQ : "ptgEQ",
|
||||
ptgGE : "ptgGE",
|
||||
ptgGT : "ptgGT",
|
||||
ptgNE : "ptgNE",
|
||||
ptgIsect : "ptgIsect",
|
||||
ptgUnion : "ptgUnion",
|
||||
ptgRange : "ptgRange",
|
||||
ptgUplus : "ptgUplus",
|
||||
ptgUminus : "ptgUminus",
|
||||
ptgPercent : "ptgPercent",
|
||||
ptgParen : "ptgParen",
|
||||
ptgMissArg : "ptgMissArg",
|
||||
ptgStr : "ptgStr",
|
||||
ptgExtend : "ptgExtend",
|
||||
ptgAttr : "ptgAttr",
|
||||
ptgSheet : "ptgSheet",
|
||||
ptgEndSheet : "ptgEndSheet",
|
||||
ptgErr : "ptgErr",
|
||||
ptgBool : "ptgBool",
|
||||
ptgInt : "ptgInt",
|
||||
ptgNum : "ptgNum",
|
||||
ptgArrayR : "ptgArrayR",
|
||||
ptgFuncR : "ptgFuncR",
|
||||
ptgFuncVarR : "ptgFuncVarR",
|
||||
ptgNameR : "ptgNameR",
|
||||
ptgRefR : "ptgRefR",
|
||||
ptgAreaR : "ptgAreaR",
|
||||
ptgMemAreaR : "ptgMemAreaR",
|
||||
ptgMemErrR : "ptgMemErrR",
|
||||
ptgMemNoMemR : "ptgMemNoMemR",
|
||||
ptgMemFuncR : "ptgMemFuncR",
|
||||
ptgRefErrR : "ptgRefErrR",
|
||||
ptgAreaErrR : "ptgAreaErrR",
|
||||
ptgRefNR : "ptgRefNR",
|
||||
ptgAreaNR : "ptgAreaNR",
|
||||
ptgMemAreaNR : "ptgMemAreaNR",
|
||||
ptgMemNoMemNR : "ptgMemNoMemNR",
|
||||
ptgNameXR : "ptgNameXR",
|
||||
ptgRef3dR : "ptgRef3dR",
|
||||
ptgArea3dR : "ptgArea3dR",
|
||||
ptgRefErr3dR : "ptgRefErr3dR",
|
||||
ptgAreaErr3dR : "ptgAreaErr3dR",
|
||||
ptgArrayV : "ptgArrayV",
|
||||
ptgFuncV : "ptgFuncV",
|
||||
ptgFuncVarV : "ptgFuncVarV",
|
||||
ptgNameV : "ptgNameV",
|
||||
ptgRefV : "ptgRefV",
|
||||
ptgAreaV : "ptgAreaV",
|
||||
ptgMemAreaV : "ptgMemAreaV",
|
||||
ptgMemErrV : "ptgMemErrV",
|
||||
ptgMemNoMemV : "ptgMemNoMemV",
|
||||
ptgMemFuncV : "ptgMemFuncV",
|
||||
ptgRefErrV : "ptgRefErrV",
|
||||
ptgAreaErrV : "ptgAreaErrV",
|
||||
ptgRefNV : "ptgRefNV",
|
||||
ptgAreaNV : "ptgAreaNV",
|
||||
ptgMemAreaNV : "ptgMemAreaNV",
|
||||
ptgMemNoMemNV : "ptgMemNoMemNV",
|
||||
ptgFuncCEV : "ptgFuncCEV",
|
||||
ptgNameXV : "ptgNameXV",
|
||||
ptgRef3dV : "ptgRef3dV",
|
||||
ptgArea3dV : "ptgArea3dV",
|
||||
ptgRefErr3dV : "ptgRefErr3dV",
|
||||
ptgAreaErr3dV : "ptgAreaErr3dV",
|
||||
ptgArrayA : "ptgArrayA",
|
||||
ptgFuncA : "ptgFuncA",
|
||||
ptgFuncVarA : "ptgFuncVarA",
|
||||
ptgNameA : "ptgNameA",
|
||||
ptgRefA : "ptgRefA",
|
||||
ptgAreaA : "ptgAreaA",
|
||||
ptgMemAreaA : "ptgMemAreaA",
|
||||
ptgMemErrA : "ptgMemErrA",
|
||||
ptgMemNoMemA : "ptgMemNoMemA",
|
||||
ptgMemFuncA : "ptgMemFuncA",
|
||||
ptgRefErrA : "ptgRefErrA",
|
||||
ptgAreaErrA : "ptgAreaErrA",
|
||||
ptgRefNA : "ptgRefNA",
|
||||
ptgAreaNA : "ptgAreaNA",
|
||||
ptgMemAreaNA : "ptgMemAreaNA",
|
||||
ptgMemNoMemNA : "ptgMemNoMemNA",
|
||||
ptgFuncCEA : "ptgFuncCEA",
|
||||
ptgNameXA : "ptgNameXA",
|
||||
ptgRef3dA : "ptgRef3dA",
|
||||
ptgArea3dA : "ptgArea3dA",
|
||||
ptgRefErr3dA : "ptgRefErr3dA",
|
||||
ptgAreaErr3dA : "ptgAreaErr3dA"
|
||||
}
|
||||
|
||||
|
||||
error_msg_by_code = {
|
||||
0x00: u"#NULL!", # intersection of two cell ranges is empty
|
||||
0x07: u"#DIV/0!", # division by zero
|
||||
0x0F: u"#VALUE!", # wrong type of operand
|
||||
0x17: u"#REF!", # illegal or deleted cell reference
|
||||
0x1D: u"#NAME?", # wrong function or range name
|
||||
0x24: u"#NUM!", # value range overflow
|
||||
0x2A: u"#N/A!" # argument or function not available
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
The XF record is able to store explicit cell formatting attributes or the
|
||||
attributes of a cell style. Explicit formatting includes the reference to
|
||||
a cell style XF record. This allows to extend a defined cell style with
|
||||
some explicit attributes. The formatting attributes are divided into
|
||||
6 groups:
|
||||
|
||||
Group Attributes
|
||||
-------------------------------------
|
||||
Number format Number format index (index to FORMAT record)
|
||||
Font Font index (index to FONT record)
|
||||
Alignment Horizontal and vertical alignment, text wrap, indentation,
|
||||
orientation/rotation, text direction
|
||||
Border Border line styles and colours
|
||||
Background Background area style and colours
|
||||
Protection Cell locked, formula hidden
|
||||
|
||||
For each group a flag in the cell XF record specifies whether to use the
|
||||
attributes contained in that XF record or in the referenced style
|
||||
XF record. In style XF records, these flags specify whether the attributes
|
||||
will overwrite explicit cell formatting when the style is applied to
|
||||
a cell. Changing a cell style (without applying this style to a cell) will
|
||||
change all cells which already use that style and do not contain explicit
|
||||
cell attributes for the changed style attributes. If a cell XF record does
|
||||
not contain explicit attributes in a group (if the attribute group flag
|
||||
is not set), it repeats the attributes of its style XF record.
|
||||
|
||||
'''
|
||||
|
||||
import BIFFRecords
|
||||
|
||||
class Font(object):
|
||||
|
||||
ESCAPEMENT_NONE = 0x00
|
||||
ESCAPEMENT_SUPERSCRIPT = 0x01
|
||||
ESCAPEMENT_SUBSCRIPT = 0x02
|
||||
|
||||
UNDERLINE_NONE = 0x00
|
||||
UNDERLINE_SINGLE = 0x01
|
||||
UNDERLINE_SINGLE_ACC = 0x21
|
||||
UNDERLINE_DOUBLE = 0x02
|
||||
UNDERLINE_DOUBLE_ACC = 0x22
|
||||
|
||||
FAMILY_NONE = 0x00
|
||||
FAMILY_ROMAN = 0x01
|
||||
FAMILY_SWISS = 0x02
|
||||
FAMILY_MODERN = 0x03
|
||||
FAMILY_SCRIPT = 0x04
|
||||
FAMILY_DECORATIVE = 0x05
|
||||
|
||||
CHARSET_ANSI_LATIN = 0x00
|
||||
CHARSET_SYS_DEFAULT = 0x01
|
||||
CHARSET_SYMBOL = 0x02
|
||||
CHARSET_APPLE_ROMAN = 0x4D
|
||||
CHARSET_ANSI_JAP_SHIFT_JIS = 0x80
|
||||
CHARSET_ANSI_KOR_HANGUL = 0x81
|
||||
CHARSET_ANSI_KOR_JOHAB = 0x82
|
||||
CHARSET_ANSI_CHINESE_GBK = 0x86
|
||||
CHARSET_ANSI_CHINESE_BIG5 = 0x88
|
||||
CHARSET_ANSI_GREEK = 0xA1
|
||||
CHARSET_ANSI_TURKISH = 0xA2
|
||||
CHARSET_ANSI_VIETNAMESE = 0xA3
|
||||
CHARSET_ANSI_HEBREW = 0xB1
|
||||
CHARSET_ANSI_ARABIC = 0xB2
|
||||
CHARSET_ANSI_BALTIC = 0xBA
|
||||
CHARSET_ANSI_CYRILLIC = 0xCC
|
||||
CHARSET_ANSI_THAI = 0xDE
|
||||
CHARSET_ANSI_LATIN_II = 0xEE
|
||||
CHARSET_OEM_LATIN_I = 0xFF
|
||||
|
||||
def __init__(self):
|
||||
# twip = 1/20 of a point = 1/1440 of a inch
|
||||
# usually resolution == 96 pixels per 1 inch
|
||||
# (rarely 120 pixels per 1 inch or another one)
|
||||
|
||||
self.height = 0x00C8 # 200: this is font with height 10 points
|
||||
self.italic = False
|
||||
self.struck_out = False
|
||||
self.outline = False
|
||||
self.shadow = False
|
||||
self.colour_index = 0x7FFF
|
||||
self.bold = False
|
||||
self._weight = 0x0190 # 0x02BC gives bold font
|
||||
self.escapement = self.ESCAPEMENT_NONE
|
||||
self.underline = self.UNDERLINE_NONE
|
||||
self.family = self.FAMILY_NONE
|
||||
self.charset = self.CHARSET_SYS_DEFAULT
|
||||
self.name = 'Arial'
|
||||
|
||||
def get_biff_record(self):
|
||||
height = self.height
|
||||
|
||||
options = 0x00
|
||||
if self.bold:
|
||||
options |= 0x01
|
||||
self._weight = 0x02BC
|
||||
if self.italic:
|
||||
options |= 0x02
|
||||
if self.underline != self.UNDERLINE_NONE:
|
||||
options |= 0x04
|
||||
if self.struck_out:
|
||||
options |= 0x08
|
||||
if self.outline:
|
||||
options |= 0x010
|
||||
if self.shadow:
|
||||
options |= 0x020
|
||||
|
||||
colour_index = self.colour_index
|
||||
weight = self._weight
|
||||
escapement = self.escapement
|
||||
underline = self.underline
|
||||
family = self.family
|
||||
charset = self.charset
|
||||
name = self.name
|
||||
|
||||
return BIFFRecords.FontRecord(height, options, colour_index, weight, escapement,
|
||||
underline, family, charset,
|
||||
name)
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.height,
|
||||
self.italic,
|
||||
self.struck_out,
|
||||
self.outline,
|
||||
self.shadow,
|
||||
self.colour_index,
|
||||
self.bold,
|
||||
self._weight,
|
||||
self.escapement,
|
||||
self.underline,
|
||||
self.family,
|
||||
self.charset,
|
||||
self.name,
|
||||
)
|
||||
|
||||
class Alignment(object):
|
||||
HORZ_GENERAL = 0x00
|
||||
HORZ_LEFT = 0x01
|
||||
HORZ_CENTER = 0x02
|
||||
HORZ_RIGHT = 0x03
|
||||
HORZ_FILLED = 0x04
|
||||
HORZ_JUSTIFIED = 0x05 # BIFF4-BIFF8X
|
||||
HORZ_CENTER_ACROSS_SEL = 0x06 # Centred across selection (BIFF4-BIFF8X)
|
||||
HORZ_DISTRIBUTED = 0x07 # Distributed (BIFF8X)
|
||||
|
||||
VERT_TOP = 0x00
|
||||
VERT_CENTER = 0x01
|
||||
VERT_BOTTOM = 0x02
|
||||
VERT_JUSTIFIED = 0x03 # Justified (BIFF5-BIFF8X)
|
||||
VERT_DISTRIBUTED = 0x04 # Distributed (BIFF8X)
|
||||
|
||||
DIRECTION_GENERAL = 0x00 # BIFF8X
|
||||
DIRECTION_LR = 0x01
|
||||
DIRECTION_RL = 0x02
|
||||
|
||||
ORIENTATION_NOT_ROTATED = 0x00
|
||||
ORIENTATION_STACKED = 0x01
|
||||
ORIENTATION_90_CC = 0x02
|
||||
ORIENTATION_90_CW = 0x03
|
||||
|
||||
ROTATION_0_ANGLE = 0x00
|
||||
ROTATION_STACKED = 0xFF
|
||||
|
||||
WRAP_AT_RIGHT = 0x01
|
||||
NOT_WRAP_AT_RIGHT = 0x00
|
||||
|
||||
SHRINK_TO_FIT = 0x01
|
||||
NOT_SHRINK_TO_FIT = 0x00
|
||||
|
||||
def __init__(self):
|
||||
self.horz = self.HORZ_GENERAL
|
||||
self.vert = self.VERT_BOTTOM
|
||||
self.dire = self.DIRECTION_GENERAL
|
||||
self.orie = self.ORIENTATION_NOT_ROTATED
|
||||
self.rota = self.ROTATION_0_ANGLE
|
||||
self.wrap = self.NOT_WRAP_AT_RIGHT
|
||||
self.shri = self.NOT_SHRINK_TO_FIT
|
||||
self.inde = 0
|
||||
self.merg = 0
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.horz, self.vert, self.dire, self.orie, self.rota,
|
||||
self.wrap, self.shri, self.inde, self.merg,
|
||||
)
|
||||
|
||||
class Borders(object):
|
||||
NO_LINE = 0x00
|
||||
THIN = 0x01
|
||||
MEDIUM = 0x02
|
||||
DASHED = 0x03
|
||||
DOTTED = 0x04
|
||||
THICK = 0x05
|
||||
DOUBLE = 0x06
|
||||
HAIR = 0x07
|
||||
#The following for BIFF8
|
||||
MEDIUM_DASHED = 0x08
|
||||
THIN_DASH_DOTTED = 0x09
|
||||
MEDIUM_DASH_DOTTED = 0x0A
|
||||
THIN_DASH_DOT_DOTTED = 0x0B
|
||||
MEDIUM_DASH_DOT_DOTTED = 0x0C
|
||||
SLANTED_MEDIUM_DASH_DOTTED = 0x0D
|
||||
|
||||
NEED_DIAG1 = 0x01
|
||||
NEED_DIAG2 = 0x01
|
||||
NO_NEED_DIAG1 = 0x00
|
||||
NO_NEED_DIAG2 = 0x00
|
||||
|
||||
def __init__(self):
|
||||
self.left = self.NO_LINE
|
||||
self.right = self.NO_LINE
|
||||
self.top = self.NO_LINE
|
||||
self.bottom = self.NO_LINE
|
||||
self.diag = self.NO_LINE
|
||||
|
||||
self.left_colour = 0x40
|
||||
self.right_colour = 0x40
|
||||
self.top_colour = 0x40
|
||||
self.bottom_colour = 0x40
|
||||
self.diag_colour = 0x40
|
||||
|
||||
self.need_diag1 = self.NO_NEED_DIAG1
|
||||
self.need_diag2 = self.NO_NEED_DIAG2
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.left, self.right, self.top, self.bottom, self.diag,
|
||||
self.left_colour, self.right_colour, self.top_colour,
|
||||
self.bottom_colour, self.diag_colour,
|
||||
self.need_diag1, self.need_diag2,
|
||||
)
|
||||
|
||||
class Pattern(object):
|
||||
# patterns 0x00 - 0x12
|
||||
NO_PATTERN = 0x00
|
||||
SOLID_PATTERN = 0x01
|
||||
|
||||
def __init__(self):
|
||||
self.pattern = self.NO_PATTERN
|
||||
self.pattern_fore_colour = 0x40
|
||||
self.pattern_back_colour = 0x41
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.pattern,
|
||||
self.pattern_fore_colour,
|
||||
self.pattern_back_colour,
|
||||
)
|
||||
|
||||
class Protection(object):
|
||||
def __init__(self):
|
||||
self.cell_locked = 1
|
||||
self.formula_hidden = 0
|
||||
|
||||
def _search_key(self):
|
||||
return (
|
||||
self.cell_locked,
|
||||
self.formula_hidden,
|
||||
)
|
||||
@@ -0,0 +1,253 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
import BIFFRecords
|
||||
import Style
|
||||
from Cell import StrCell, BlankCell, NumberCell, FormulaCell, MulBlankCell, BooleanCell, ErrorCell, \
|
||||
_get_cells_biff_data_mul
|
||||
import ExcelFormula
|
||||
import datetime as dt
|
||||
try:
|
||||
from decimal import Decimal
|
||||
except ImportError:
|
||||
# Python 2.3: decimal not supported; create dummy Decimal class
|
||||
class Decimal(object):
|
||||
pass
|
||||
|
||||
|
||||
class Row(object):
|
||||
__slots__ = [# private variables
|
||||
"__idx",
|
||||
"__parent",
|
||||
"__parent_wb",
|
||||
"__cells",
|
||||
"__min_col_idx",
|
||||
"__max_col_idx",
|
||||
"__xf_index",
|
||||
"__has_default_xf_index",
|
||||
"__height_in_pixels",
|
||||
# public variables
|
||||
"height",
|
||||
"has_default_height",
|
||||
"height_mismatch",
|
||||
"level",
|
||||
"collapse",
|
||||
"hidden",
|
||||
"space_above",
|
||||
"space_below"]
|
||||
|
||||
def __init__(self, rowx, parent_sheet):
|
||||
if not (isinstance(rowx, int) and 0 <= rowx <= 65535):
|
||||
raise ValueError("row index (%r) not an int in range(65536)" % rowx)
|
||||
self.__idx = rowx
|
||||
self.__parent = parent_sheet
|
||||
self.__parent_wb = parent_sheet.get_parent()
|
||||
self.__cells = {}
|
||||
self.__min_col_idx = 0
|
||||
self.__max_col_idx = 0
|
||||
self.__xf_index = 0x0F
|
||||
self.__has_default_xf_index = 0
|
||||
self.__height_in_pixels = 0x11
|
||||
|
||||
self.height = 0x00FF
|
||||
self.has_default_height = 0x00
|
||||
self.height_mismatch = 0
|
||||
self.level = 0
|
||||
self.collapse = 0
|
||||
self.hidden = 0
|
||||
self.space_above = 0
|
||||
self.space_below = 0
|
||||
|
||||
|
||||
def __adjust_height(self, style):
|
||||
twips = style.font.height
|
||||
points = float(twips)/20.0
|
||||
# Cell height in pixels can be calcuted by following approx. formula:
|
||||
# cell height in pixels = font height in points * 83/50 + 2/5
|
||||
# It works when screen resolution is 96 dpi
|
||||
pix = int(round(points*83.0/50.0 + 2.0/5.0))
|
||||
if pix > self.__height_in_pixels:
|
||||
self.__height_in_pixels = pix
|
||||
|
||||
|
||||
def __adjust_bound_col_idx(self, *args):
|
||||
for arg in args:
|
||||
iarg = int(arg)
|
||||
if not ((0 <= iarg <= 255) and arg == iarg):
|
||||
raise ValueError("column index (%r) not an int in range(256)" % arg)
|
||||
sheet = self.__parent
|
||||
if iarg < self.__min_col_idx:
|
||||
self.__min_col_idx = iarg
|
||||
if iarg > self.__max_col_idx:
|
||||
self.__max_col_idx = iarg
|
||||
if iarg < sheet.first_used_col:
|
||||
sheet.first_used_col = iarg
|
||||
if iarg > sheet.last_used_col:
|
||||
sheet.last_used_col = iarg
|
||||
|
||||
def __excel_date_dt(self, date):
|
||||
if isinstance(date, dt.date) and (not isinstance(date, dt.datetime)):
|
||||
epoch = dt.date(1899, 12, 31)
|
||||
elif isinstance(date, dt.time):
|
||||
date = dt.datetime.combine(dt.datetime(1900, 1, 1), date)
|
||||
epoch = dt.datetime(1900, 1, 1, 0, 0, 0)
|
||||
else:
|
||||
epoch = dt.datetime(1899, 12, 31, 0, 0, 0)
|
||||
delta = date - epoch
|
||||
xldate = delta.days + float(delta.seconds) / (24*60*60)
|
||||
# Add a day for Excel's missing leap day in 1900
|
||||
if xldate > 59:
|
||||
xldate += 1
|
||||
return xldate
|
||||
|
||||
def get_height_in_pixels(self):
|
||||
return self.__height_in_pixels
|
||||
|
||||
|
||||
def set_style(self, style):
|
||||
self.__adjust_height(style)
|
||||
self.__xf_index = self.__parent_wb.add_style(style)
|
||||
self.__has_default_xf_index = 1
|
||||
|
||||
|
||||
def get_xf_index(self):
|
||||
return self.__xf_index
|
||||
|
||||
|
||||
def get_cells_count(self):
|
||||
return len(self.__cells)
|
||||
|
||||
|
||||
def get_min_col(self):
|
||||
return self.__min_col_idx
|
||||
|
||||
|
||||
def get_max_col(self):
|
||||
return self.__max_col_idx
|
||||
|
||||
|
||||
def get_row_biff_data(self):
|
||||
height_options = (self.height & 0x07FFF)
|
||||
height_options |= (self.has_default_height & 0x01) << 15
|
||||
|
||||
options = (self.level & 0x07) << 0
|
||||
options |= (self.collapse & 0x01) << 4
|
||||
options |= (self.hidden & 0x01) << 5
|
||||
options |= (self.height_mismatch & 0x01) << 6
|
||||
options |= (self.__has_default_xf_index & 0x01) << 7
|
||||
options |= (0x01 & 0x01) << 8
|
||||
options |= (self.__xf_index & 0x0FFF) << 16
|
||||
options |= (self.space_above & 1) << 28
|
||||
options |= (self.space_below & 1) << 29
|
||||
|
||||
return BIFFRecords.RowRecord(self.__idx, self.__min_col_idx,
|
||||
self.__max_col_idx, height_options, options).get()
|
||||
|
||||
def insert_cell(self, col_index, cell_obj):
|
||||
if col_index in self.__cells:
|
||||
if not self.__parent._cell_overwrite_ok:
|
||||
msg = "Attempt to overwrite cell: sheetname=%r rowx=%d colx=%d" \
|
||||
% (self.__parent.name, self.__idx, col_index)
|
||||
raise Exception(msg)
|
||||
prev_cell_obj = self.__cells[col_index]
|
||||
sst_idx = getattr(prev_cell_obj, 'sst_idx', None)
|
||||
if sst_idx is not None:
|
||||
self.__parent_wb.del_str(sst_idx)
|
||||
self.__cells[col_index] = cell_obj
|
||||
|
||||
def insert_mulcells(self, colx1, colx2, cell_obj):
|
||||
self.insert_cell(colx1, cell_obj)
|
||||
for col_index in xrange(colx1+1, colx2+1):
|
||||
self.insert_cell(col_index, None)
|
||||
|
||||
def get_cells_biff_data(self):
|
||||
cell_items = [item for item in self.__cells.iteritems() if item[1] is not None]
|
||||
cell_items.sort() # in column order
|
||||
return _get_cells_biff_data_mul(self.__idx, cell_items)
|
||||
# previously:
|
||||
# return ''.join([cell.get_biff_data() for colx, cell in cell_items])
|
||||
|
||||
def get_index(self):
|
||||
return self.__idx
|
||||
|
||||
def set_cell_text(self, colx, value, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, StrCell(self.__idx, colx, xf_index, self.__parent_wb.add_str(value)))
|
||||
|
||||
def set_cell_blank(self, colx, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, BlankCell(self.__idx, colx, xf_index))
|
||||
|
||||
def set_cell_mulblanks(self, first_colx, last_colx, style=Style.default_style):
|
||||
assert 0 <= first_colx <= last_colx <= 255
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(first_colx, last_colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
# ncols = last_colx - first_colx + 1
|
||||
self.insert_mulcells(first_colx, last_colx, MulBlankCell(self.__idx, first_colx, last_colx, xf_index))
|
||||
|
||||
def set_cell_number(self, colx, number, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, NumberCell(self.__idx, colx, xf_index, number))
|
||||
|
||||
def set_cell_date(self, colx, datetime_obj, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx,
|
||||
NumberCell(self.__idx, colx, xf_index, self.__excel_date_dt(datetime_obj)))
|
||||
|
||||
def set_cell_formula(self, colx, formula, style=Style.default_style, calc_flags=0):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.__parent_wb.add_sheet_reference(formula)
|
||||
self.insert_cell(colx, FormulaCell(self.__idx, colx, xf_index, formula, calc_flags=0))
|
||||
|
||||
def set_cell_boolean(self, colx, value, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, BooleanCell(self.__idx, colx, xf_index, bool(value)))
|
||||
|
||||
def set_cell_error(self, colx, error_string_or_code, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(colx)
|
||||
xf_index = self.__parent_wb.add_style(style)
|
||||
self.insert_cell(colx, ErrorCell(self.__idx, colx, xf_index, error_string_or_code))
|
||||
|
||||
def write(self, col, label, style=Style.default_style):
|
||||
self.__adjust_height(style)
|
||||
self.__adjust_bound_col_idx(col)
|
||||
style_index = self.__parent_wb.add_style(style)
|
||||
if isinstance(label, basestring):
|
||||
if len(label) > 0:
|
||||
self.insert_cell(col,
|
||||
StrCell(self.__idx, col, style_index, self.__parent_wb.add_str(label))
|
||||
)
|
||||
else:
|
||||
self.insert_cell(col, BlankCell(self.__idx, col, style_index))
|
||||
elif isinstance(label, bool): # bool is subclass of int; test bool first
|
||||
self.insert_cell(col, BooleanCell(self.__idx, col, style_index, label))
|
||||
elif isinstance(label, (float, int, long, Decimal)):
|
||||
self.insert_cell(col, NumberCell(self.__idx, col, style_index, label))
|
||||
elif isinstance(label, (dt.datetime, dt.date, dt.time)):
|
||||
date_number = self.__excel_date_dt(label)
|
||||
self.insert_cell(col, NumberCell(self.__idx, col, style_index, date_number))
|
||||
elif label is None:
|
||||
self.insert_cell(col, BlankCell(self.__idx, col, style_index))
|
||||
elif isinstance(label, ExcelFormula.Formula):
|
||||
self.__parent_wb.add_sheet_reference(label)
|
||||
self.insert_cell(col, FormulaCell(self.__idx, col, style_index, label))
|
||||
else:
|
||||
raise Exception("Unexpected data type %r" % type(label))
|
||||
|
||||
write_blanks = set_cell_mulblanks
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,592 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
import Formatting
|
||||
from BIFFRecords import *
|
||||
|
||||
FIRST_USER_DEFINED_NUM_FORMAT_IDX = 164
|
||||
|
||||
class XFStyle(object):
|
||||
|
||||
def __init__(self):
|
||||
self.num_format_str = 'General'
|
||||
self.font = Formatting.Font()
|
||||
self.alignment = Formatting.Alignment()
|
||||
self.borders = Formatting.Borders()
|
||||
self.pattern = Formatting.Pattern()
|
||||
self.protection = Formatting.Protection()
|
||||
|
||||
default_style = XFStyle()
|
||||
|
||||
class StyleCollection(object):
|
||||
_std_num_fmt_list = [
|
||||
'general',
|
||||
'0',
|
||||
'0.00',
|
||||
'#,##0',
|
||||
'#,##0.00',
|
||||
'"$"#,##0_);("$"#,##',
|
||||
'"$"#,##0_);[Red]("$"#,##',
|
||||
'"$"#,##0.00_);("$"#,##',
|
||||
'"$"#,##0.00_);[Red]("$"#,##',
|
||||
'0%',
|
||||
'0.00%',
|
||||
'0.00E+00',
|
||||
'# ?/?',
|
||||
'# ??/??',
|
||||
'M/D/YY',
|
||||
'D-MMM-YY',
|
||||
'D-MMM',
|
||||
'MMM-YY',
|
||||
'h:mm AM/PM',
|
||||
'h:mm:ss AM/PM',
|
||||
'h:mm',
|
||||
'h:mm:ss',
|
||||
'M/D/YY h:mm',
|
||||
'_(#,##0_);(#,##0)',
|
||||
'_(#,##0_);[Red](#,##0)',
|
||||
'_(#,##0.00_);(#,##0.00)',
|
||||
'_(#,##0.00_);[Red](#,##0.00)',
|
||||
'_("$"* #,##0_);_("$"* (#,##0);_("$"* "-"_);_(@_)',
|
||||
'_(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)',
|
||||
'_("$"* #,##0.00_);_("$"* (#,##0.00);_("$"* "-"??_);_(@_)',
|
||||
'_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)',
|
||||
'mm:ss',
|
||||
'[h]:mm:ss',
|
||||
'mm:ss.0',
|
||||
'##0.0E+0',
|
||||
'@'
|
||||
]
|
||||
|
||||
def __init__(self, style_compression=0):
|
||||
self.style_compression = style_compression
|
||||
self.stats = [0, 0, 0, 0, 0, 0]
|
||||
self._font_id2x = {}
|
||||
self._font_x2id = {}
|
||||
self._font_val2x = {}
|
||||
|
||||
for x in (0, 1, 2, 3, 5): # The font with index 4 is omitted in all BIFF versions
|
||||
font = Formatting.Font()
|
||||
search_key = font._search_key()
|
||||
self._font_id2x[font] = x
|
||||
self._font_x2id[x] = font
|
||||
self._font_val2x[search_key] = x
|
||||
|
||||
self._xf_id2x = {}
|
||||
self._xf_x2id = {}
|
||||
self._xf_val2x = {}
|
||||
|
||||
self._num_formats = {}
|
||||
for fmtidx, fmtstr in zip(range(0, 23), StyleCollection._std_num_fmt_list[0:23]):
|
||||
self._num_formats[fmtstr] = fmtidx
|
||||
for fmtidx, fmtstr in zip(range(37, 50), StyleCollection._std_num_fmt_list[23:]):
|
||||
self._num_formats[fmtstr] = fmtidx
|
||||
|
||||
self.default_style = XFStyle()
|
||||
self._default_xf = self._add_style(self.default_style)[0]
|
||||
|
||||
def add(self, style):
|
||||
if style == None:
|
||||
return 0x10
|
||||
return self._add_style(style)[1]
|
||||
|
||||
def _add_style(self, style):
|
||||
num_format_str = style.num_format_str
|
||||
if num_format_str in self._num_formats:
|
||||
num_format_idx = self._num_formats[num_format_str]
|
||||
else:
|
||||
num_format_idx = (
|
||||
FIRST_USER_DEFINED_NUM_FORMAT_IDX
|
||||
+ len(self._num_formats)
|
||||
- len(StyleCollection._std_num_fmt_list)
|
||||
)
|
||||
self._num_formats[num_format_str] = num_format_idx
|
||||
|
||||
font = style.font
|
||||
if font in self._font_id2x:
|
||||
font_idx = self._font_id2x[font]
|
||||
self.stats[0] += 1
|
||||
elif self.style_compression:
|
||||
search_key = font._search_key()
|
||||
font_idx = self._font_val2x.get(search_key)
|
||||
if font_idx is not None:
|
||||
self._font_id2x[font] = font_idx
|
||||
self.stats[1] += 1
|
||||
else:
|
||||
font_idx = len(self._font_x2id) + 1 # Why plus 1? Font 4 is missing
|
||||
self._font_id2x[font] = font_idx
|
||||
self._font_val2x[search_key] = font_idx
|
||||
self._font_x2id[font_idx] = font
|
||||
self.stats[2] += 1
|
||||
else:
|
||||
font_idx = len(self._font_id2x) + 1
|
||||
self._font_id2x[font] = font_idx
|
||||
self.stats[2] += 1
|
||||
|
||||
gof = (style.alignment, style.borders, style.pattern, style.protection)
|
||||
xf = (font_idx, num_format_idx) + gof
|
||||
if xf in self._xf_id2x:
|
||||
xf_index = self._xf_id2x[xf]
|
||||
self.stats[3] += 1
|
||||
elif self.style_compression == 2:
|
||||
xf_key = (font_idx, num_format_idx) + tuple([obj._search_key() for obj in gof])
|
||||
xf_index = self._xf_val2x.get(xf_key)
|
||||
if xf_index is not None:
|
||||
self._xf_id2x[xf] = xf_index
|
||||
self.stats[4] += 1
|
||||
else:
|
||||
xf_index = 0x10 + len(self._xf_x2id)
|
||||
self._xf_id2x[xf] = xf_index
|
||||
self._xf_val2x[xf_key] = xf_index
|
||||
self._xf_x2id[xf_index] = xf
|
||||
self.stats[5] += 1
|
||||
else:
|
||||
xf_index = 0x10 + len(self._xf_id2x)
|
||||
self._xf_id2x[xf] = xf_index
|
||||
self.stats[5] += 1
|
||||
|
||||
if xf_index >= 0xFFF:
|
||||
# 12 bits allowed, 0xFFF is a sentinel value
|
||||
raise ValueError("More than 4094 XFs (styles)")
|
||||
|
||||
return xf, xf_index
|
||||
|
||||
def get_biff_data(self):
|
||||
result = ''
|
||||
result += self._all_fonts()
|
||||
result += self._all_num_formats()
|
||||
result += self._all_cell_styles()
|
||||
result += self._all_styles()
|
||||
return result
|
||||
|
||||
def _all_fonts(self):
|
||||
result = ''
|
||||
if self.style_compression:
|
||||
alist = self._font_x2id.items()
|
||||
else:
|
||||
alist = [(x, o) for o, x in self._font_id2x.items()]
|
||||
alist.sort()
|
||||
for font_idx, font in alist:
|
||||
result += font.get_biff_record().get()
|
||||
return result
|
||||
|
||||
def _all_num_formats(self):
|
||||
result = ''
|
||||
alist = [
|
||||
(v, k)
|
||||
for k, v in self._num_formats.items()
|
||||
if v >= FIRST_USER_DEFINED_NUM_FORMAT_IDX
|
||||
]
|
||||
alist.sort()
|
||||
for fmtidx, fmtstr in alist:
|
||||
result += NumberFormatRecord(fmtidx, fmtstr).get()
|
||||
return result
|
||||
|
||||
def _all_cell_styles(self):
|
||||
result = ''
|
||||
for i in range(0, 16):
|
||||
result += XFRecord(self._default_xf, 'style').get()
|
||||
if self.style_compression == 2:
|
||||
alist = self._xf_x2id.items()
|
||||
else:
|
||||
alist = [(x, o) for o, x in self._xf_id2x.items()]
|
||||
alist.sort()
|
||||
for xf_idx, xf in alist:
|
||||
result += XFRecord(xf).get()
|
||||
return result
|
||||
|
||||
def _all_styles(self):
|
||||
return StyleRecord().get()
|
||||
|
||||
# easyxf and its supporting objects ###################################
|
||||
|
||||
class EasyXFException(Exception):
|
||||
pass
|
||||
|
||||
class EasyXFCallerError(EasyXFException):
|
||||
pass
|
||||
|
||||
class EasyXFAuthorError(EasyXFException):
|
||||
pass
|
||||
|
||||
class IntULim(object):
|
||||
# If astring represents a valid unsigned integer ('123', '0xabcd', etc)
|
||||
# and it is <= limit, return the int value; otherwise return None.
|
||||
|
||||
def __init__(self, limit):
|
||||
self.limit = limit
|
||||
|
||||
def __call__(self, astring):
|
||||
try:
|
||||
value = int(astring, 0)
|
||||
except ValueError:
|
||||
return None
|
||||
if not 0 <= value <= self.limit:
|
||||
return None
|
||||
return value
|
||||
|
||||
bool_map = {
|
||||
# Text values for all Boolean attributes
|
||||
'1': 1, 'yes': 1, 'true': 1, 'on': 1,
|
||||
'0': 0, 'no': 0, 'false': 0, 'off': 0,
|
||||
}
|
||||
|
||||
border_line_map = {
|
||||
# Text values for these borders attributes:
|
||||
# left, right, top, bottom and diag
|
||||
'no_line': 0x00,
|
||||
'thin': 0x01,
|
||||
'medium': 0x02,
|
||||
'dashed': 0x03,
|
||||
'dotted': 0x04,
|
||||
'thick': 0x05,
|
||||
'double': 0x06,
|
||||
'hair': 0x07,
|
||||
'medium_dashed': 0x08,
|
||||
'thin_dash_dotted': 0x09,
|
||||
'medium_dash_dotted': 0x0a,
|
||||
'thin_dash_dot_dotted': 0x0b,
|
||||
'medium_dash_dot_dotted': 0x0c,
|
||||
'slanted_medium_dash_dotted': 0x0d,
|
||||
}
|
||||
|
||||
charset_map = {
|
||||
# Text values for font.charset
|
||||
'ansi_latin': 0x00,
|
||||
'sys_default': 0x01,
|
||||
'symbol': 0x02,
|
||||
'apple_roman': 0x4d,
|
||||
'ansi_jap_shift_jis': 0x80,
|
||||
'ansi_kor_hangul': 0x81,
|
||||
'ansi_kor_johab': 0x82,
|
||||
'ansi_chinese_gbk': 0x86,
|
||||
'ansi_chinese_big5': 0x88,
|
||||
'ansi_greek': 0xa1,
|
||||
'ansi_turkish': 0xa2,
|
||||
'ansi_vietnamese': 0xa3,
|
||||
'ansi_hebrew': 0xb1,
|
||||
'ansi_arabic': 0xb2,
|
||||
'ansi_baltic': 0xba,
|
||||
'ansi_cyrillic': 0xcc,
|
||||
'ansi_thai': 0xde,
|
||||
'ansi_latin_ii': 0xee,
|
||||
'oem_latin_i': 0xff,
|
||||
}
|
||||
|
||||
|
||||
# Text values for colour indices. "grey" is a synonym of "gray".
|
||||
# The names are those given by Microsoft Excel 2003 to the colours
|
||||
# in the default palette. There is no great correspondence with
|
||||
# any W3C name-to-RGB mapping.
|
||||
_colour_map_text = """\
|
||||
aqua 0x31
|
||||
black 0x08
|
||||
blue 0x0C
|
||||
blue_gray 0x36
|
||||
bright_green 0x0B
|
||||
brown 0x3C
|
||||
coral 0x1D
|
||||
cyan_ega 0x0F
|
||||
dark_blue 0x12
|
||||
dark_blue_ega 0x12
|
||||
dark_green 0x3A
|
||||
dark_green_ega 0x11
|
||||
dark_purple 0x1C
|
||||
dark_red 0x10
|
||||
dark_red_ega 0x10
|
||||
dark_teal 0x38
|
||||
dark_yellow 0x13
|
||||
gold 0x33
|
||||
gray_ega 0x17
|
||||
gray25 0x16
|
||||
gray40 0x37
|
||||
gray50 0x17
|
||||
gray80 0x3F
|
||||
green 0x11
|
||||
ice_blue 0x1F
|
||||
indigo 0x3E
|
||||
ivory 0x1A
|
||||
lavender 0x2E
|
||||
light_blue 0x30
|
||||
light_green 0x2A
|
||||
light_orange 0x34
|
||||
light_turquoise 0x29
|
||||
light_yellow 0x2B
|
||||
lime 0x32
|
||||
magenta_ega 0x0E
|
||||
ocean_blue 0x1E
|
||||
olive_ega 0x13
|
||||
olive_green 0x3B
|
||||
orange 0x35
|
||||
pale_blue 0x2C
|
||||
periwinkle 0x18
|
||||
pink 0x0E
|
||||
plum 0x3D
|
||||
purple_ega 0x14
|
||||
red 0x0A
|
||||
rose 0x2D
|
||||
sea_green 0x39
|
||||
silver_ega 0x16
|
||||
sky_blue 0x28
|
||||
tan 0x2F
|
||||
teal 0x15
|
||||
teal_ega 0x15
|
||||
turquoise 0x0F
|
||||
violet 0x14
|
||||
white 0x09
|
||||
yellow 0x0D"""
|
||||
|
||||
colour_map = {}
|
||||
for _line in _colour_map_text.splitlines():
|
||||
_name, _num = _line.split()
|
||||
_num = int(_num, 0)
|
||||
colour_map[_name] = _num
|
||||
if 'gray' in _name:
|
||||
colour_map[_name.replace('gray', 'grey')] = _num
|
||||
del _colour_map_text, _line, _name, _num
|
||||
|
||||
|
||||
pattern_map = {
|
||||
# Text values for pattern.pattern
|
||||
# xlwt/doc/pattern_examples.xls showcases all of these patterns.
|
||||
'no_fill': 0,
|
||||
'none': 0,
|
||||
'solid': 1,
|
||||
'solid_fill': 1,
|
||||
'solid_pattern': 1,
|
||||
'fine_dots': 2,
|
||||
'alt_bars': 3,
|
||||
'sparse_dots': 4,
|
||||
'thick_horz_bands': 5,
|
||||
'thick_vert_bands': 6,
|
||||
'thick_backward_diag': 7,
|
||||
'thick_forward_diag': 8,
|
||||
'big_spots': 9,
|
||||
'bricks': 10,
|
||||
'thin_horz_bands': 11,
|
||||
'thin_vert_bands': 12,
|
||||
'thin_backward_diag': 13,
|
||||
'thin_forward_diag': 14,
|
||||
'squares': 15,
|
||||
'diamonds': 16,
|
||||
}
|
||||
|
||||
def any_str_func(s):
|
||||
return s.strip()
|
||||
|
||||
def colour_index_func(s, maxval=0x7F):
|
||||
try:
|
||||
value = int(s, 0)
|
||||
except ValueError:
|
||||
return None
|
||||
if not (0 <= value <= maxval):
|
||||
return None
|
||||
return value
|
||||
|
||||
colour_index_func_7 = colour_index_func
|
||||
|
||||
def colour_index_func_15(s):
|
||||
return colour_index_func(s, maxval=0x7FFF)
|
||||
|
||||
def rotation_func(s):
|
||||
try:
|
||||
value = int(s, 0)
|
||||
except ValueError:
|
||||
return None
|
||||
if not (-90 <= value <= 90):
|
||||
raise EasyXFCallerError("rotation %d: should be -90 to +90 degrees" % value)
|
||||
if value < 0:
|
||||
value = 90 - value # encode as 91 to 180 (clockwise)
|
||||
return value
|
||||
|
||||
xf_dict = {
|
||||
'align': 'alignment', # synonym
|
||||
'alignment': {
|
||||
'dire': {
|
||||
'general': 0,
|
||||
'lr': 1,
|
||||
'rl': 2,
|
||||
},
|
||||
'direction': 'dire',
|
||||
'horiz': 'horz',
|
||||
'horizontal': 'horz',
|
||||
'horz': {
|
||||
'general': 0,
|
||||
'left': 1,
|
||||
'center': 2,
|
||||
'centre': 2, # "align: horiz centre" means xf.alignment.horz is set to 2
|
||||
'right': 3,
|
||||
'filled': 4,
|
||||
'justified': 5,
|
||||
'center_across_selection': 6,
|
||||
'centre_across_selection': 6,
|
||||
'distributed': 7,
|
||||
},
|
||||
'inde': IntULim(15), # restriction: 0 <= value <= 15
|
||||
'indent': 'inde',
|
||||
'rota': [{'stacked': 255, 'none': 0, }, rotation_func],
|
||||
'rotation': 'rota',
|
||||
'shri': bool_map,
|
||||
'shrink': 'shri',
|
||||
'shrink_to_fit': 'shri',
|
||||
'vert': {
|
||||
'top': 0,
|
||||
'center': 1,
|
||||
'centre': 1,
|
||||
'bottom': 2,
|
||||
'justified': 3,
|
||||
'distributed': 4,
|
||||
},
|
||||
'vertical': 'vert',
|
||||
'wrap': bool_map,
|
||||
},
|
||||
'border': 'borders',
|
||||
'borders': {
|
||||
'left': [border_line_map, IntULim(0x0d)],
|
||||
'right': [border_line_map, IntULim(0x0d)],
|
||||
'top': [border_line_map, IntULim(0x0d)],
|
||||
'bottom': [border_line_map, IntULim(0x0d)],
|
||||
'diag': [border_line_map, IntULim(0x0d)],
|
||||
'top_colour': [colour_map, colour_index_func_7],
|
||||
'bottom_colour': [colour_map, colour_index_func_7],
|
||||
'left_colour': [colour_map, colour_index_func_7],
|
||||
'right_colour': [colour_map, colour_index_func_7],
|
||||
'diag_colour': [colour_map, colour_index_func_7],
|
||||
'top_color': 'top_colour',
|
||||
'bottom_color': 'bottom_colour',
|
||||
'left_color': 'left_colour',
|
||||
'right_color': 'right_colour',
|
||||
'diag_color': 'diag-colour',
|
||||
'need_diag_1': bool_map,
|
||||
'need_diag_2': bool_map,
|
||||
},
|
||||
'font': {
|
||||
'bold': bool_map,
|
||||
'charset': charset_map,
|
||||
'color': 'colour_index',
|
||||
'color_index': 'colour_index',
|
||||
'colour': 'colour_index',
|
||||
'colour_index': [colour_map, colour_index_func_15],
|
||||
'escapement': {'none': 0, 'superscript': 1, 'subscript': 2},
|
||||
'family': {'none': 0, 'roman': 1, 'swiss': 2, 'modern': 3, 'script': 4, 'decorative': 5, },
|
||||
'height': IntULim(0xFFFF), # practical limits are much narrower e.g. 160 to 1440 (8pt to 72pt)
|
||||
'italic': bool_map,
|
||||
'name': any_str_func,
|
||||
'outline': bool_map,
|
||||
'shadow': bool_map,
|
||||
'struck_out': bool_map,
|
||||
'underline': [bool_map, {'none': 0, 'single': 1, 'single_acc': 0x21, 'double': 2, 'double_acc': 0x22, }],
|
||||
},
|
||||
'pattern': {
|
||||
'back_color': 'pattern_back_colour',
|
||||
'back_colour': 'pattern_back_colour',
|
||||
'fore_color': 'pattern_fore_colour',
|
||||
'fore_colour': 'pattern_fore_colour',
|
||||
'pattern': [pattern_map, IntULim(16)],
|
||||
'pattern_back_color': 'pattern_back_colour',
|
||||
'pattern_back_colour': [colour_map, colour_index_func_7],
|
||||
'pattern_fore_color': 'pattern_fore_colour',
|
||||
'pattern_fore_colour': [colour_map, colour_index_func_7],
|
||||
},
|
||||
'protection': {
|
||||
'cell_locked' : bool_map,
|
||||
'formula_hidden': bool_map,
|
||||
},
|
||||
}
|
||||
|
||||
def _esplit(s, split_char, esc_char="\\"):
|
||||
escaped = False
|
||||
olist = ['']
|
||||
for c in s:
|
||||
if escaped:
|
||||
olist[-1] += c
|
||||
escaped = False
|
||||
elif c == esc_char:
|
||||
escaped = True
|
||||
elif c == split_char:
|
||||
olist.append('')
|
||||
else:
|
||||
olist[-1] += c
|
||||
return olist
|
||||
|
||||
def _parse_strg_to_obj(strg, obj, parse_dict,
|
||||
field_sep=",", line_sep=";", intro_sep=":", esc_char="\\", debug=False):
|
||||
for line in _esplit(strg, line_sep, esc_char):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
break
|
||||
split_line = _esplit(line, intro_sep, esc_char)
|
||||
if len(split_line) != 2:
|
||||
raise EasyXFCallerError('line %r should have exactly 1 "%c"' % (line, intro_sep))
|
||||
section, item_str = split_line
|
||||
section = section.strip().lower()
|
||||
for counter in range(2):
|
||||
result = parse_dict.get(section)
|
||||
if result is None:
|
||||
raise EasyXFCallerError('section %r is unknown' % section)
|
||||
if isinstance(result, dict):
|
||||
break
|
||||
if not isinstance(result, str):
|
||||
raise EasyXFAuthorError(
|
||||
'section %r should map to dict or str object; found %r' % (section, type(result)))
|
||||
# synonym
|
||||
old_section = section
|
||||
section = result
|
||||
else:
|
||||
raise EasyXFAuthorError('Attempt to define synonym of synonym (%r: %r)' % (old_section, result))
|
||||
section_dict = result
|
||||
section_obj = getattr(obj, section, None)
|
||||
if section_obj is None:
|
||||
raise EasyXFAuthorError('instance of %s class has no attribute named %s' % (obj.__class__.__name__, section))
|
||||
for kv_str in _esplit(item_str, field_sep, esc_char):
|
||||
guff = kv_str.split()
|
||||
if not guff:
|
||||
continue
|
||||
k = guff[0].lower().replace('-', '_')
|
||||
v = ' '.join(guff[1:])
|
||||
if not v:
|
||||
raise EasyXFCallerError("no value supplied for %s.%s" % (section, k))
|
||||
for counter in xrange(2):
|
||||
result = section_dict.get(k)
|
||||
if result is None:
|
||||
raise EasyXFCallerError('%s.%s is not a known attribute' % (section, k))
|
||||
if not isinstance(result, basestring):
|
||||
break
|
||||
# synonym
|
||||
old_k = k
|
||||
k = result
|
||||
else:
|
||||
raise EasyXFAuthorError('Attempt to define synonym of synonym (%r: %r)' % (old_k, result))
|
||||
value_info = result
|
||||
if not isinstance(value_info, list):
|
||||
value_info = [value_info]
|
||||
for value_rule in value_info:
|
||||
if isinstance(value_rule, dict):
|
||||
# dict maps strings to integer field values
|
||||
vl = v.lower().replace('-', '_')
|
||||
if vl in value_rule:
|
||||
value = value_rule[vl]
|
||||
break
|
||||
elif callable(value_rule):
|
||||
value = value_rule(v)
|
||||
if value is not None:
|
||||
break
|
||||
else:
|
||||
raise EasyXFAuthorError("unknown value rule for attribute %r: %r" % (k, value_rule))
|
||||
else:
|
||||
raise EasyXFCallerError("unexpected value %r for %s.%s" % (v, section, k))
|
||||
try:
|
||||
orig = getattr(section_obj, k)
|
||||
except AttributeError:
|
||||
raise EasyXFAuthorError('%s.%s in dictionary but not in supplied object' % (section, k))
|
||||
if debug: print "+++ %s.%s = %r # %s; was %r" % (section, k, value, v, orig)
|
||||
setattr(section_obj, k, value)
|
||||
|
||||
def easyxf(strg_to_parse="", num_format_str=None,
|
||||
field_sep=",", line_sep=";", intro_sep=":", esc_char="\\", debug=False):
|
||||
xfobj = XFStyle()
|
||||
if num_format_str is not None:
|
||||
xfobj.num_format_str = num_format_str
|
||||
if strg_to_parse:
|
||||
_parse_strg_to_obj(strg_to_parse, xfobj, xf_dict,
|
||||
field_sep=field_sep, line_sep=line_sep, intro_sep=intro_sep, esc_char=esc_char, debug=debug)
|
||||
return xfobj
|
||||
@@ -0,0 +1,81 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
'''
|
||||
From BIFF8 on, strings are always stored using UTF-16LE text encoding. The
|
||||
character array is a sequence of 16-bit values4. Additionally it is
|
||||
possible to use a compressed format, which omits the high bytes of all
|
||||
characters, if they are all zero.
|
||||
|
||||
The following tables describe the standard format of the entire string, but
|
||||
in many records the strings differ from this format. This will be mentioned
|
||||
separately. It is possible (but not required) to store Rich-Text formatting
|
||||
information and Asian phonetic information inside a Unicode string. This
|
||||
results in four different ways to store a string. The character array
|
||||
is not zero-terminated.
|
||||
|
||||
The string consists of the character count (as usual an 8-bit value or
|
||||
a 16-bit value), option flags, the character array and optional formatting
|
||||
information. If the string is empty, sometimes the option flags field will
|
||||
not occur. This is mentioned at the respective place.
|
||||
|
||||
Offset Size Contents
|
||||
0 1 or 2 Length of the string (character count, ln)
|
||||
1 or 2 1 Option flags:
|
||||
Bit Mask Contents
|
||||
0 01H Character compression (ccompr):
|
||||
0 = Compressed (8-bit characters)
|
||||
1 = Uncompressed (16-bit characters)
|
||||
2 04H Asian phonetic settings (phonetic):
|
||||
0 = Does not contain Asian phonetic settings
|
||||
1 = Contains Asian phonetic settings
|
||||
3 08H Rich-Text settings (richtext):
|
||||
0 = Does not contain Rich-Text settings
|
||||
1 = Contains Rich-Text settings
|
||||
[2 or 3] 2 (optional, only if richtext=1) Number of Rich-Text formatting runs (rt)
|
||||
[var.] 4 (optional, only if phonetic=1) Size of Asian phonetic settings block (in bytes, sz)
|
||||
var. ln or
|
||||
2·ln Character array (8-bit characters or 16-bit characters, dependent on ccompr)
|
||||
[var.] 4·rt (optional, only if richtext=1) List of rt formatting runs
|
||||
[var.] sz (optional, only if phonetic=1) Asian Phonetic Settings Block
|
||||
'''
|
||||
|
||||
|
||||
from struct import pack
|
||||
|
||||
def upack2(s, encoding='ascii'):
|
||||
# If not unicode, make it so.
|
||||
if isinstance(s, unicode):
|
||||
us = s
|
||||
else:
|
||||
us = unicode(s, encoding)
|
||||
# Limit is based on number of content characters
|
||||
# (not on number of bytes in packed result)
|
||||
len_us = len(us)
|
||||
if len_us > 65535:
|
||||
raise Exception('String longer than 65535 characters')
|
||||
try:
|
||||
encs = us.encode('latin1')
|
||||
# Success here means all chars are in U+0000 to U+00FF
|
||||
# inclusive, meaning that we can use "compressed format".
|
||||
flag = 0
|
||||
except UnicodeEncodeError:
|
||||
encs = us.encode('utf_16_le')
|
||||
flag = 1
|
||||
return pack('<HB', len_us, flag) + encs
|
||||
|
||||
def upack1(s, encoding='ascii'):
|
||||
# Same as upack2(), but with a one-byte length field.
|
||||
if isinstance(s, unicode):
|
||||
us = s
|
||||
else:
|
||||
us = unicode(s, encoding)
|
||||
len_us = len(us)
|
||||
if len_us > 255:
|
||||
raise Exception('String longer than 255 characters')
|
||||
try:
|
||||
encs = us.encode('latin1')
|
||||
flag = 0
|
||||
except UnicodeEncodeError:
|
||||
encs = us.encode('utf_16_le')
|
||||
flag = 1
|
||||
return pack('<BB', len_us, flag) + encs
|
||||
@@ -0,0 +1,196 @@
|
||||
# pyXLWriter: A library for generating Excel Spreadsheets
|
||||
# Copyright (c) 2004 Evgeny Filatov <fufff@users.sourceforge.net>
|
||||
# Copyright (c) 2002-2004 John McNamara (Perl Spreadsheet::WriteExcel)
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this library; if not, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#----------------------------------------------------------------------------
|
||||
# This module was written/ported from PERL Spreadsheet::WriteExcel module
|
||||
# The author of the PERL Spreadsheet::WriteExcel module is John McNamara
|
||||
# <jmcnamara@cpan.org>
|
||||
#----------------------------------------------------------------------------
|
||||
# See the README.txt distributed with pyXLWriter for more details.
|
||||
|
||||
# Portions are (C) Roman V. Kiseliov, 2005
|
||||
|
||||
|
||||
# Utilities for work with reference to cells and with sheetnames
|
||||
|
||||
|
||||
__rev_id__ = """$Id: Utils.py 3844 2009-05-20 01:02:54Z sjmachin $"""
|
||||
|
||||
import re
|
||||
from struct import pack
|
||||
from ExcelMagic import MAX_ROW, MAX_COL
|
||||
|
||||
|
||||
_re_cell_ex = re.compile(r"(\$?)([A-I]?[A-Z])(\$?)(\d+)", re.IGNORECASE)
|
||||
_re_row_range = re.compile(r"\$?(\d+):\$?(\d+)")
|
||||
_re_col_range = re.compile(r"\$?([A-I]?[A-Z]):\$?([A-I]?[A-Z])", re.IGNORECASE)
|
||||
_re_cell_range = re.compile(r"\$?([A-I]?[A-Z]\$?\d+):\$?([A-I]?[A-Z]\$?\d+)", re.IGNORECASE)
|
||||
_re_cell_ref = re.compile(r"\$?([A-I]?[A-Z]\$?\d+)", re.IGNORECASE)
|
||||
|
||||
|
||||
def col_by_name(colname):
|
||||
"""
|
||||
"""
|
||||
col = 0
|
||||
pow = 1
|
||||
for i in xrange(len(colname)-1, -1, -1):
|
||||
ch = colname[i]
|
||||
col += (ord(ch) - ord('A') + 1) * pow
|
||||
pow *= 26
|
||||
return col - 1
|
||||
|
||||
|
||||
def cell_to_rowcol(cell):
|
||||
"""Convert an Excel cell reference string in A1 notation
|
||||
to numeric row/col notation.
|
||||
|
||||
Returns: row, col, row_abs, col_abs
|
||||
|
||||
"""
|
||||
m = _re_cell_ex.match(cell)
|
||||
if not m:
|
||||
raise Exception("Ill-formed single_cell reference: %s" % cell)
|
||||
col_abs, col, row_abs, row = m.groups()
|
||||
row_abs = bool(row_abs)
|
||||
col_abs = bool(col_abs)
|
||||
row = int(row) - 1
|
||||
col = col_by_name(col.upper())
|
||||
return row, col, row_abs, col_abs
|
||||
|
||||
|
||||
def cell_to_rowcol2(cell):
|
||||
"""Convert an Excel cell reference string in A1 notation
|
||||
to numeric row/col notation.
|
||||
|
||||
Returns: row, col
|
||||
|
||||
"""
|
||||
m = _re_cell_ex.match(cell)
|
||||
if not m:
|
||||
raise Exception("Error in cell format")
|
||||
col_abs, col, row_abs, row = m.groups()
|
||||
# Convert base26 column string to number
|
||||
# All your Base are belong to us.
|
||||
row = int(row) - 1
|
||||
col = col_by_name(col.upper())
|
||||
return row, col
|
||||
|
||||
|
||||
def rowcol_to_cell(row, col, row_abs=False, col_abs=False):
|
||||
"""Convert numeric row/col notation to an Excel cell reference string in
|
||||
A1 notation.
|
||||
|
||||
"""
|
||||
assert 0 <= row < MAX_ROW # MAX_ROW counts from 1
|
||||
assert 0 <= col < MAX_COL # MAX_COL counts from 1
|
||||
d = col // 26
|
||||
m = col % 26
|
||||
chr1 = "" # Most significant character in AA1
|
||||
if row_abs:
|
||||
row_abs = '$'
|
||||
else:
|
||||
row_abs = ''
|
||||
if col_abs:
|
||||
col_abs = '$'
|
||||
else:
|
||||
col_abs = ''
|
||||
if d > 0:
|
||||
chr1 = chr(ord('A') + d - 1)
|
||||
chr2 = chr(ord('A') + m)
|
||||
# Zero index to 1-index
|
||||
return col_abs + chr1 + chr2 + row_abs + str(row + 1)
|
||||
|
||||
def rowcol_pair_to_cellrange(row1, col1, row2, col2,
|
||||
row1_abs=False, col1_abs=False, row2_abs=False, col2_abs=False):
|
||||
"""Convert two (row,column) pairs
|
||||
into a cell range string in A1:B2 notation.
|
||||
|
||||
Returns: cell range string
|
||||
"""
|
||||
assert row1 <= row2
|
||||
assert col1 <= col2
|
||||
return (
|
||||
rowcol_to_cell(row1, col1, row1_abs, col1_abs)
|
||||
+ ":"
|
||||
+ rowcol_to_cell(row2, col2, row2_abs, col2_abs)
|
||||
)
|
||||
|
||||
def cellrange_to_rowcol_pair(cellrange):
|
||||
"""Convert cell range string in A1 notation to numeric row/col
|
||||
pair.
|
||||
|
||||
Returns: row1, col1, row2, col2
|
||||
|
||||
"""
|
||||
cellrange = cellrange.upper()
|
||||
# Convert a row range: '1:3'
|
||||
res = _re_row_range.match(cellrange)
|
||||
if res:
|
||||
row1 = int(res.group(1)) - 1
|
||||
col1 = 0
|
||||
row2 = int(res.group(2)) - 1
|
||||
col2 = -1
|
||||
return row1, col1, row2, col2
|
||||
# Convert a column range: 'A:A' or 'B:G'.
|
||||
# A range such as A:A is equivalent to A1:A16384, so add rows as required
|
||||
res = _re_col_range.match(cellrange)
|
||||
if res:
|
||||
col1 = col_by_name(res.group(1).upper())
|
||||
row1 = 0
|
||||
col2 = col_by_name(res.group(2).upper())
|
||||
row2 = -1
|
||||
return row1, col1, row2, col2
|
||||
# Convert a cell range: 'A1:B7'
|
||||
res = _re_cell_range.match(cellrange)
|
||||
if res:
|
||||
row1, col1 = cell_to_rowcol2(res.group(1))
|
||||
row2, col2 = cell_to_rowcol2(res.group(2))
|
||||
return row1, col1, row2, col2
|
||||
# Convert a cell reference: 'A1' or 'AD2000'
|
||||
res = _re_cell_ref.match(cellrange)
|
||||
if res:
|
||||
row1, col1 = cell_to_rowcol2(res.group(1))
|
||||
return row1, col1, row1, col1
|
||||
raise Exception("Unknown cell reference %s" % (cell))
|
||||
|
||||
|
||||
def cell_to_packed_rowcol(cell):
|
||||
""" pack row and column into the required 4 byte format """
|
||||
row, col, row_abs, col_abs = cell_to_rowcol(cell)
|
||||
if col >= MAX_COL:
|
||||
raise Exception("Column %s greater than IV in formula" % cell)
|
||||
if row >= MAX_ROW: # this for BIFF8. for BIFF7 available 2^14
|
||||
raise Exception("Row %s greater than %d in formula" % (cell, MAX_ROW))
|
||||
col |= int(not row_abs) << 15
|
||||
col |= int(not col_abs) << 14
|
||||
return row, col
|
||||
|
||||
# === sheetname functions ===
|
||||
|
||||
def valid_sheet_name(sheet_name):
|
||||
if sheet_name == u"" or sheet_name[0] == u"'" or len(sheet_name) > 31:
|
||||
return False
|
||||
for c in sheet_name:
|
||||
if c in u"[]:\\?/*\x00":
|
||||
return False
|
||||
return True
|
||||
|
||||
def quote_sheet_name(unquoted_sheet_name):
|
||||
if not valid_sheet_name(unquoted_sheet_name):
|
||||
raise Exception(
|
||||
'attempt to quote an invalid worksheet name %r' % unquoted_sheet_name)
|
||||
return u"'" + unquoted_sheet_name.replace(u"'", u"''") + u"'"
|
||||
@@ -0,0 +1,636 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
'''
|
||||
Record Order in BIFF8
|
||||
Workbook Globals Substream
|
||||
BOF Type = workbook globals
|
||||
Interface Header
|
||||
MMS
|
||||
Interface End
|
||||
WRITEACCESS
|
||||
CODEPAGE
|
||||
DSF
|
||||
TABID
|
||||
FNGROUPCOUNT
|
||||
Workbook Protection Block
|
||||
WINDOWPROTECT
|
||||
PROTECT
|
||||
PASSWORD
|
||||
PROT4REV
|
||||
PROT4REVPASS
|
||||
BACKUP
|
||||
HIDEOBJ
|
||||
WINDOW1
|
||||
DATEMODE
|
||||
PRECISION
|
||||
REFRESHALL
|
||||
BOOKBOOL
|
||||
FONT +
|
||||
FORMAT *
|
||||
XF +
|
||||
STYLE +
|
||||
? PALETTE
|
||||
USESELFS
|
||||
|
||||
BOUNDSHEET +
|
||||
|
||||
COUNTRY
|
||||
? Link Table
|
||||
SST
|
||||
ExtSST
|
||||
EOF
|
||||
'''
|
||||
|
||||
import BIFFRecords
|
||||
import Style
|
||||
|
||||
class Workbook(object):
|
||||
|
||||
#################################################################
|
||||
## Constructor
|
||||
#################################################################
|
||||
def __init__(self, encoding='ascii', style_compression=0):
|
||||
self.encoding = encoding
|
||||
self.__owner = 'None'
|
||||
self.__country_code = None # 0x07 is Russia :-)
|
||||
self.__wnd_protect = 0
|
||||
self.__obj_protect = 0
|
||||
self.__protect = 0
|
||||
self.__backup_on_save = 0
|
||||
# for WINDOW1 record
|
||||
self.__hpos_twips = 0x01E0
|
||||
self.__vpos_twips = 0x005A
|
||||
self.__width_twips = 0x3FCF
|
||||
self.__height_twips = 0x2A4E
|
||||
|
||||
self.__active_sheet = 0
|
||||
self.__first_tab_index = 0
|
||||
self.__selected_tabs = 0x01
|
||||
self.__tab_width_twips = 0x0258
|
||||
|
||||
self.__wnd_hidden = 0
|
||||
self.__wnd_mini = 0
|
||||
self.__hscroll_visible = 1
|
||||
self.__vscroll_visible = 1
|
||||
self.__tabs_visible = 1
|
||||
|
||||
self.__styles = Style.StyleCollection(style_compression)
|
||||
|
||||
self.__dates_1904 = 0
|
||||
self.__use_cell_values = 1
|
||||
|
||||
self.__sst = BIFFRecords.SharedStringTable(self.encoding)
|
||||
|
||||
self.__worksheets = []
|
||||
self.__worksheet_idx_from_name = {}
|
||||
self.__sheet_refs = {}
|
||||
self._supbook_xref = {}
|
||||
self._xcall_xref = {}
|
||||
self._ownbook_supbookx = None
|
||||
self._ownbook_supbook_ref = None
|
||||
self._xcall_supbookx = None
|
||||
self._xcall_supbook_ref = None
|
||||
|
||||
|
||||
|
||||
#################################################################
|
||||
## Properties, "getters", "setters"
|
||||
#################################################################
|
||||
|
||||
def get_style_stats(self):
|
||||
return self.__styles.stats[:]
|
||||
|
||||
def set_owner(self, value):
|
||||
self.__owner = value
|
||||
|
||||
def get_owner(self):
|
||||
return self.__owner
|
||||
|
||||
owner = property(get_owner, set_owner)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_country_code(self, value):
|
||||
self.__country_code = value
|
||||
|
||||
def get_country_code(self):
|
||||
return self.__country_code
|
||||
|
||||
country_code = property(get_country_code, set_country_code)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_wnd_protect(self, value):
|
||||
self.__wnd_protect = int(value)
|
||||
|
||||
def get_wnd_protect(self):
|
||||
return bool(self.__wnd_protect)
|
||||
|
||||
wnd_protect = property(get_wnd_protect, set_wnd_protect)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_obj_protect(self, value):
|
||||
self.__obj_protect = int(value)
|
||||
|
||||
def get_obj_protect(self):
|
||||
return bool(self.__obj_protect)
|
||||
|
||||
obj_protect = property(get_obj_protect, set_obj_protect)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_protect(self, value):
|
||||
self.__protect = int(value)
|
||||
|
||||
def get_protect(self):
|
||||
return bool(self.__protect)
|
||||
|
||||
protect = property(get_protect, set_protect)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_backup_on_save(self, value):
|
||||
self.__backup_on_save = int(value)
|
||||
|
||||
def get_backup_on_save(self):
|
||||
return bool(self.__backup_on_save)
|
||||
|
||||
backup_on_save = property(get_backup_on_save, set_backup_on_save)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_hpos(self, value):
|
||||
self.__hpos_twips = value & 0xFFFF
|
||||
|
||||
def get_hpos(self):
|
||||
return self.__hpos_twips
|
||||
|
||||
hpos = property(get_hpos, set_hpos)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_vpos(self, value):
|
||||
self.__vpos_twips = value & 0xFFFF
|
||||
|
||||
def get_vpos(self):
|
||||
return self.__vpos_twips
|
||||
|
||||
vpos = property(get_vpos, set_vpos)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_width(self, value):
|
||||
self.__width_twips = value & 0xFFFF
|
||||
|
||||
def get_width(self):
|
||||
return self.__width_twips
|
||||
|
||||
width = property(get_width, set_width)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_height(self, value):
|
||||
self.__height_twips = value & 0xFFFF
|
||||
|
||||
def get_height(self):
|
||||
return self.__height_twips
|
||||
|
||||
height = property(get_height, set_height)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_active_sheet(self, value):
|
||||
self.__active_sheet = value & 0xFFFF
|
||||
self.__first_tab_index = self.__active_sheet
|
||||
|
||||
def get_active_sheet(self):
|
||||
return self.__active_sheet
|
||||
|
||||
active_sheet = property(get_active_sheet, set_active_sheet)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_tab_width(self, value):
|
||||
self.__tab_width_twips = value & 0xFFFF
|
||||
|
||||
def get_tab_width(self):
|
||||
return self.__tab_width_twips
|
||||
|
||||
tab_width = property(get_tab_width, set_tab_width)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_wnd_visible(self, value):
|
||||
self.__wnd_hidden = int(not value)
|
||||
|
||||
def get_wnd_visible(self):
|
||||
return not bool(self.__wnd_hidden)
|
||||
|
||||
wnd_visible = property(get_wnd_visible, set_wnd_visible)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_wnd_mini(self, value):
|
||||
self.__wnd_mini = int(value)
|
||||
|
||||
def get_wnd_mini(self):
|
||||
return bool(self.__wnd_mini)
|
||||
|
||||
wnd_mini = property(get_wnd_mini, set_wnd_mini)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_hscroll_visible(self, value):
|
||||
self.__hscroll_visible = int(value)
|
||||
|
||||
def get_hscroll_visible(self):
|
||||
return bool(self.__hscroll_visible)
|
||||
|
||||
hscroll_visible = property(get_hscroll_visible, set_hscroll_visible)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_vscroll_visible(self, value):
|
||||
self.__vscroll_visible = int(value)
|
||||
|
||||
def get_vscroll_visible(self):
|
||||
return bool(self.__vscroll_visible)
|
||||
|
||||
vscroll_visible = property(get_vscroll_visible, set_vscroll_visible)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_tabs_visible(self, value):
|
||||
self.__tabs_visible = int(value)
|
||||
|
||||
def get_tabs_visible(self):
|
||||
return bool(self.__tabs_visible)
|
||||
|
||||
tabs_visible = property(get_tabs_visible, set_tabs_visible)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_dates_1904(self, value):
|
||||
self.__dates_1904 = int(value)
|
||||
|
||||
def get_dates_1904(self):
|
||||
return bool(self.__dates_1904)
|
||||
|
||||
dates_1904 = property(get_dates_1904, set_dates_1904)
|
||||
|
||||
#################################################################
|
||||
|
||||
def set_use_cell_values(self, value):
|
||||
self.__use_cell_values = int(value)
|
||||
|
||||
def get_use_cell_values(self):
|
||||
return bool(self.__use_cell_values)
|
||||
|
||||
use_cell_values = property(get_use_cell_values, set_use_cell_values)
|
||||
|
||||
#################################################################
|
||||
|
||||
def get_default_style(self):
|
||||
return self.__styles.default_style
|
||||
|
||||
default_style = property(get_default_style)
|
||||
|
||||
##################################################################
|
||||
## Methods
|
||||
##################################################################
|
||||
|
||||
def add_style(self, style):
|
||||
return self.__styles.add(style)
|
||||
|
||||
def add_str(self, s):
|
||||
return self.__sst.add_str(s)
|
||||
|
||||
def del_str(self, sst_idx):
|
||||
self.__sst.del_str(sst_idx)
|
||||
|
||||
def str_index(self, s):
|
||||
return self.__sst.str_index(s)
|
||||
|
||||
def add_sheet(self, sheetname, cell_overwrite_ok=False):
|
||||
import Worksheet, Utils
|
||||
if not isinstance(sheetname, unicode):
|
||||
sheetname = sheetname.decode(self.encoding)
|
||||
if not Utils.valid_sheet_name(sheetname):
|
||||
raise Exception("invalid worksheet name %r" % sheetname)
|
||||
lower_name = sheetname.lower()
|
||||
if lower_name in self.__worksheet_idx_from_name:
|
||||
raise Exception("duplicate worksheet name %r" % sheetname)
|
||||
self.__worksheet_idx_from_name[lower_name] = len(self.__worksheets)
|
||||
self.__worksheets.append(Worksheet.Worksheet(sheetname, self, cell_overwrite_ok))
|
||||
return self.__worksheets[-1]
|
||||
|
||||
def get_sheet(self, sheetnum):
|
||||
return self.__worksheets[sheetnum]
|
||||
|
||||
def raise_bad_sheetname(self, sheetname):
|
||||
raise Exception("Formula: unknown sheet name %s" % sheetname)
|
||||
|
||||
def convert_sheetindex(self, strg_ref, n_sheets):
|
||||
idx = int(strg_ref)
|
||||
if 0 <= idx < n_sheets:
|
||||
return idx
|
||||
msg = "Formula: sheet index (%s) >= number of sheets (%d)" % (strg_ref, n_sheets)
|
||||
raise Exception(msg)
|
||||
|
||||
def _get_supbook_index(self, tag):
|
||||
if tag in self._supbook_xref:
|
||||
return self._supbook_xref[tag]
|
||||
self._supbook_xref[tag] = idx = len(self._supbook_xref)
|
||||
return idx
|
||||
|
||||
def setup_ownbook(self):
|
||||
self._ownbook_supbookx = self._get_supbook_index(('ownbook', 0))
|
||||
self._ownbook_supbook_ref = None
|
||||
reference = (self._ownbook_supbookx, 0xFFFE, 0xFFFE)
|
||||
if reference in self.__sheet_refs:
|
||||
raise Exception("can't happen")
|
||||
self.__sheet_refs[reference] = self._ownbook_supbook_ref = len(self.__sheet_refs)
|
||||
|
||||
def setup_xcall(self):
|
||||
self._xcall_supbookx = self._get_supbook_index(('xcall', 0))
|
||||
self._xcall_supbook_ref = None
|
||||
reference = (self._xcall_supbookx, 0xFFFE, 0xFFFE)
|
||||
if reference in self.__sheet_refs:
|
||||
raise Exception("can't happen")
|
||||
self.__sheet_refs[reference] = self._xcall_supbook_ref = len(self.__sheet_refs)
|
||||
|
||||
def add_sheet_reference(self, formula):
|
||||
patches = []
|
||||
n_sheets = len(self.__worksheets)
|
||||
sheet_refs, xcall_refs = formula.get_references()
|
||||
|
||||
for ref0, ref1, offset in sheet_refs:
|
||||
if not ref0.isdigit():
|
||||
try:
|
||||
ref0n = self.__worksheet_idx_from_name[ref0.lower()]
|
||||
except KeyError:
|
||||
self.raise_bad_sheetname(ref0)
|
||||
else:
|
||||
ref0n = self.convert_sheetindex(ref0, n_sheets)
|
||||
if ref1 == ref0:
|
||||
ref1n = ref0n
|
||||
elif not ref1.isdigit():
|
||||
try:
|
||||
ref1n = self.__worksheet_idx_from_name[ref1.lower()]
|
||||
except KeyError:
|
||||
self.raise_bad_sheetname(ref1)
|
||||
else:
|
||||
ref1n = self.convert_sheetindex(ref1, n_sheets)
|
||||
if ref1n < ref0n:
|
||||
msg = "Formula: sheets out of order; %r:%r -> (%d, %d)" \
|
||||
% (ref0, ref1, ref0n, ref1n)
|
||||
raise Exception(msg)
|
||||
if self._ownbook_supbookx is None:
|
||||
self.setup_ownbook()
|
||||
reference = (self._ownbook_supbookx, ref0n, ref1n)
|
||||
if reference in self.__sheet_refs:
|
||||
patches.append((offset, self.__sheet_refs[reference]))
|
||||
else:
|
||||
nrefs = len(self.__sheet_refs)
|
||||
if nrefs > 65535:
|
||||
raise Exception('More than 65536 inter-sheet references')
|
||||
self.__sheet_refs[reference] = nrefs
|
||||
patches.append((offset, nrefs))
|
||||
|
||||
for funcname, offset in xcall_refs:
|
||||
if self._ownbook_supbookx is None:
|
||||
self.setup_ownbook()
|
||||
if self._xcall_supbookx is None:
|
||||
self.setup_xcall()
|
||||
# print funcname, self._supbook_xref
|
||||
patches.append((offset, self._xcall_supbook_ref))
|
||||
if not isinstance(funcname, unicode):
|
||||
funcname = funcname.decode(self.encoding)
|
||||
if funcname in self._xcall_xref:
|
||||
idx = self._xcall_xref[funcname]
|
||||
else:
|
||||
self._xcall_xref[funcname] = idx = len(self._xcall_xref)
|
||||
patches.append((offset + 2, idx + 1))
|
||||
|
||||
formula.patch_references(patches)
|
||||
|
||||
##################################################################
|
||||
## BIFF records generation
|
||||
##################################################################
|
||||
|
||||
def __bof_rec(self):
|
||||
return BIFFRecords.Biff8BOFRecord(BIFFRecords.Biff8BOFRecord.BOOK_GLOBAL).get()
|
||||
|
||||
def __eof_rec(self):
|
||||
return BIFFRecords.EOFRecord().get()
|
||||
|
||||
def __intf_hdr_rec(self):
|
||||
return BIFFRecords.InteraceHdrRecord().get()
|
||||
|
||||
def __intf_end_rec(self):
|
||||
return BIFFRecords.InteraceEndRecord().get()
|
||||
|
||||
def __intf_mms_rec(self):
|
||||
return BIFFRecords.MMSRecord().get()
|
||||
|
||||
def __write_access_rec(self):
|
||||
return BIFFRecords.WriteAccessRecord(self.__owner).get()
|
||||
|
||||
def __wnd_protect_rec(self):
|
||||
return BIFFRecords.WindowProtectRecord(self.__wnd_protect).get()
|
||||
|
||||
def __obj_protect_rec(self):
|
||||
return BIFFRecords.ObjectProtectRecord(self.__obj_protect).get()
|
||||
|
||||
def __protect_rec(self):
|
||||
return BIFFRecords.ProtectRecord(self.__protect).get()
|
||||
|
||||
def __password_rec(self):
|
||||
return BIFFRecords.PasswordRecord().get()
|
||||
|
||||
def __prot4rev_rec(self):
|
||||
return BIFFRecords.Prot4RevRecord().get()
|
||||
|
||||
def __prot4rev_pass_rec(self):
|
||||
return BIFFRecords.Prot4RevPassRecord().get()
|
||||
|
||||
def __backup_rec(self):
|
||||
return BIFFRecords.BackupRecord(self.__backup_on_save).get()
|
||||
|
||||
def __hide_obj_rec(self):
|
||||
return BIFFRecords.HideObjRecord().get()
|
||||
|
||||
def __window1_rec(self):
|
||||
flags = 0
|
||||
flags |= (self.__wnd_hidden) << 0
|
||||
flags |= (self.__wnd_mini) << 1
|
||||
flags |= (self.__hscroll_visible) << 3
|
||||
flags |= (self.__vscroll_visible) << 4
|
||||
flags |= (self.__tabs_visible) << 5
|
||||
|
||||
return BIFFRecords.Window1Record(self.__hpos_twips, self.__vpos_twips,
|
||||
self.__width_twips, self.__height_twips,
|
||||
flags,
|
||||
self.__active_sheet, self.__first_tab_index,
|
||||
self.__selected_tabs, self.__tab_width_twips).get()
|
||||
|
||||
def __codepage_rec(self):
|
||||
return BIFFRecords.CodepageBiff8Record().get()
|
||||
|
||||
def __country_rec(self):
|
||||
if not self.__country_code:
|
||||
return ''
|
||||
return BIFFRecords.CountryRecord(self.__country_code, self.__country_code).get()
|
||||
|
||||
def __dsf_rec(self):
|
||||
return BIFFRecords.DSFRecord().get()
|
||||
|
||||
def __tabid_rec(self):
|
||||
return BIFFRecords.TabIDRecord(len(self.__worksheets)).get()
|
||||
|
||||
def __fngroupcount_rec(self):
|
||||
return BIFFRecords.FnGroupCountRecord().get()
|
||||
|
||||
def __datemode_rec(self):
|
||||
return BIFFRecords.DateModeRecord(self.__dates_1904).get()
|
||||
|
||||
def __precision_rec(self):
|
||||
return BIFFRecords.PrecisionRecord(self.__use_cell_values).get()
|
||||
|
||||
def __refresh_all_rec(self):
|
||||
return BIFFRecords.RefreshAllRecord().get()
|
||||
|
||||
def __bookbool_rec(self):
|
||||
return BIFFRecords.BookBoolRecord().get()
|
||||
|
||||
def __all_fonts_num_formats_xf_styles_rec(self):
|
||||
return self.__styles.get_biff_data()
|
||||
|
||||
def __palette_rec(self):
|
||||
result = ''
|
||||
return result
|
||||
|
||||
def __useselfs_rec(self):
|
||||
return BIFFRecords.UseSelfsRecord().get()
|
||||
|
||||
def __boundsheets_rec(self, data_len_before, data_len_after, sheet_biff_lens):
|
||||
# .................................
|
||||
# BOUNDSEHEET0
|
||||
# BOUNDSEHEET1
|
||||
# BOUNDSEHEET2
|
||||
# ..................................
|
||||
# WORKSHEET0
|
||||
# WORKSHEET1
|
||||
# WORKSHEET2
|
||||
boundsheets_len = 0
|
||||
for sheet in self.__worksheets:
|
||||
boundsheets_len += len(BIFFRecords.BoundSheetRecord(
|
||||
0x00L, sheet.visibility, sheet.name, self.encoding
|
||||
).get())
|
||||
|
||||
start = data_len_before + boundsheets_len + data_len_after
|
||||
|
||||
result = ''
|
||||
for sheet_biff_len, sheet in zip(sheet_biff_lens, self.__worksheets):
|
||||
result += BIFFRecords.BoundSheetRecord(
|
||||
start, sheet.visibility, sheet.name, self.encoding
|
||||
).get()
|
||||
start += sheet_biff_len
|
||||
return result
|
||||
|
||||
def __all_links_rec(self):
|
||||
pieces = []
|
||||
temp = [(idx, tag) for tag, idx in self._supbook_xref.items()]
|
||||
temp.sort()
|
||||
for idx, tag in temp:
|
||||
stype, snum = tag
|
||||
if stype == 'ownbook':
|
||||
rec = BIFFRecords.InternalReferenceSupBookRecord(len(self.__worksheets)).get()
|
||||
pieces.append(rec)
|
||||
elif stype == 'xcall':
|
||||
rec = BIFFRecords.XcallSupBookRecord().get()
|
||||
pieces.append(rec)
|
||||
temp = [(idx, name) for name, idx in self._xcall_xref.items()]
|
||||
temp.sort()
|
||||
for idx, name in temp:
|
||||
rec = BIFFRecords.ExternnameRecord(
|
||||
options=0, index=0, name=name, fmla='\x02\x00\x1c\x17').get()
|
||||
pieces.append(rec)
|
||||
else:
|
||||
raise Exception('unknown supbook stype %r' % stype)
|
||||
if len(self.__sheet_refs) > 0:
|
||||
# get references in index order
|
||||
temp = [(idx, ref) for ref, idx in self.__sheet_refs.items()]
|
||||
temp.sort()
|
||||
temp = [ref for idx, ref in temp]
|
||||
externsheet_record = BIFFRecords.ExternSheetRecord(temp).get()
|
||||
pieces.append(externsheet_record)
|
||||
return ''.join(pieces)
|
||||
|
||||
def __sst_rec(self):
|
||||
return self.__sst.get_biff_record()
|
||||
|
||||
def __ext_sst_rec(self, abs_stream_pos):
|
||||
return ''
|
||||
#return BIFFRecords.ExtSSTRecord(abs_stream_pos, self.sst_record.str_placement,
|
||||
#self.sst_record.portions_len).get()
|
||||
|
||||
def get_biff_data(self):
|
||||
before = ''
|
||||
before += self.__bof_rec()
|
||||
before += self.__intf_hdr_rec()
|
||||
before += self.__intf_mms_rec()
|
||||
before += self.__intf_end_rec()
|
||||
before += self.__write_access_rec()
|
||||
before += self.__codepage_rec()
|
||||
before += self.__dsf_rec()
|
||||
before += self.__tabid_rec()
|
||||
before += self.__fngroupcount_rec()
|
||||
before += self.__wnd_protect_rec()
|
||||
before += self.__protect_rec()
|
||||
before += self.__obj_protect_rec()
|
||||
before += self.__password_rec()
|
||||
before += self.__prot4rev_rec()
|
||||
before += self.__prot4rev_pass_rec()
|
||||
before += self.__backup_rec()
|
||||
before += self.__hide_obj_rec()
|
||||
before += self.__window1_rec()
|
||||
before += self.__datemode_rec()
|
||||
before += self.__precision_rec()
|
||||
before += self.__refresh_all_rec()
|
||||
before += self.__bookbool_rec()
|
||||
before += self.__all_fonts_num_formats_xf_styles_rec()
|
||||
before += self.__palette_rec()
|
||||
before += self.__useselfs_rec()
|
||||
|
||||
country = self.__country_rec()
|
||||
all_links = self.__all_links_rec()
|
||||
|
||||
shared_str_table = self.__sst_rec()
|
||||
after = country + all_links + shared_str_table
|
||||
|
||||
ext_sst = self.__ext_sst_rec(0) # need fake cause we need calc stream pos
|
||||
eof = self.__eof_rec()
|
||||
|
||||
self.__worksheets[self.__active_sheet].selected = True
|
||||
sheets = ''
|
||||
sheet_biff_lens = []
|
||||
for sheet in self.__worksheets:
|
||||
data = sheet.get_biff_data()
|
||||
sheets += data
|
||||
sheet_biff_lens.append(len(data))
|
||||
|
||||
bundlesheets = self.__boundsheets_rec(len(before), len(after)+len(ext_sst)+len(eof), sheet_biff_lens)
|
||||
|
||||
sst_stream_pos = len(before) + len(bundlesheets) + len(country) + len(all_links)
|
||||
ext_sst = self.__ext_sst_rec(sst_stream_pos)
|
||||
|
||||
return before + bundlesheets + after + ext_sst + eof + sheets
|
||||
|
||||
def save(self, filename):
|
||||
import CompoundDoc
|
||||
|
||||
doc = CompoundDoc.XlsDoc()
|
||||
doc.save(filename, self.get_biff_data())
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
# -*- coding: windows-1252 -*-
|
||||
|
||||
__VERSION__ = '0.7.2'
|
||||
|
||||
import sys
|
||||
if sys.version_info[:2] < (2, 3):
|
||||
print >> sys.stderr, "Sorry, xlwt requires Python 2.3 or later"
|
||||
sys.exit(1)
|
||||
|
||||
from Workbook import Workbook
|
||||
from Worksheet import Worksheet
|
||||
from Row import Row
|
||||
from Column import Column
|
||||
from Formatting import Font, Alignment, Borders, Pattern, Protection
|
||||
from Style import XFStyle, easyxf
|
||||
from ExcelFormula import *
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,199 @@
|
||||
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv='Content-Type' content='text/html; charset=us-ascii' />
|
||||
<title>The xlwt Module</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>The xlwt Module</h1>
|
||||
<p /><p><b>A Python package for generating Microsoft Excel ™ spreadsheet files.
|
||||
</b></p>
|
||||
|
||||
<h2>General information</h2>
|
||||
|
||||
<h3>State of Documentation</h3>
|
||||
|
||||
<p>
|
||||
This documentation is currently incomplete. There may be methods and
|
||||
classes not included and any item marked with a <em
|
||||
style="color:red;">[NC]</em> is not complete and may have further
|
||||
parameters, methods, attributes and functionality that are not
|
||||
documented. In these cases, you'll have to refer to the source if the
|
||||
documentation provided is insufficient.
|
||||
</p>
|
||||
|
||||
</p>
|
||||
|
||||
<h2>Module Contents <em style="color:red;">[NC]</em></h2>
|
||||
<dl>
|
||||
<dt><b>easyxf</b> (function)</dt>
|
||||
<dd>
|
||||
<p>
|
||||
This function is used to create and configure XFStyle objects
|
||||
for use with (for example) the Worksheet.write method.
|
||||
</p>
|
||||
<dl>
|
||||
<dt><i>strg_to_parse</i></dt>
|
||||
<dd>
|
||||
<p>
|
||||
A string to be parsed to obtain attribute values for Alignment, Borders, Font,
|
||||
Pattern and Protection objects. Refer to the examples
|
||||
in the file .../examples/xlwt_easyxf_simple_demo.py and to the xf_dict
|
||||
dictionary in Style.py. Various synonyms including color/colour, center/centre and gray/grey
|
||||
are allowed. Case is irrelevant (except maybe in font names). '-' may be used instead
|
||||
of '_'.<br />
|
||||
Example: "font: bold on; align: wrap on, vert centre, horiz center"
|
||||
</p>
|
||||
</dd>
|
||||
<dt><i>num_format_str</i></dt>
|
||||
<dd>
|
||||
<p>
|
||||
To get the "number format string" of an existing cell whose format you want to reproduce,
|
||||
select the cell and click on Format/Cells/Number/Custom. Otherwise, refer to Excel help.<br />
|
||||
Examples: "#,##0.00", "dd/mm/yyyy"
|
||||
</p>
|
||||
</dd>
|
||||
<dt>Returns:</dt>
|
||||
<dd>
|
||||
An object of the XFstyle class
|
||||
</dd>
|
||||
</dl>
|
||||
<br />
|
||||
|
||||
</dd>
|
||||
|
||||
<dt><b>Workbook</b> (class) [<a href='#xlwt.Workbook-class'>#</a>]</dt>
|
||||
<dd>
|
||||
<p>The class to instantiate to create a workbook</p>
|
||||
<p>For more information about this class, see <a href='#xlwt.Workbook-class'><i>The Workbook Class</i></a>.</p>
|
||||
</dd>
|
||||
<dt><b>Worksheet</b> (class) [<a href='#xlwt.Worksheet-class'>#</a>]</dt>
|
||||
<dd>
|
||||
<p>A class to represent the contents of a sheet in a workbook.</p>
|
||||
<p>For more information about this class, see <a href='#xlwt.Worksheet-class'><i>The Worksheet Class</i></a>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<h2><a id='xlwt.Workbook-class' name='xlwt.Workbook-class'>The Workbook Class</a><em style="color:red;">[NC]</em></h2>
|
||||
<dl>
|
||||
<dt><b>Workbook(encoding='ascii',style_compression=0)</b> (class) [<a href='#xlwt.Workbook-class'>#</a>]</dt>
|
||||
<dd>
|
||||
<p>
|
||||
This is a class representing a workbook and all its contents.
|
||||
When creating Excel files with xlwt, you will normally start by
|
||||
instantiating an object of this class.
|
||||
</p>
|
||||
<dl>
|
||||
<dt><i>encoding</i></dt>
|
||||
<dd>
|
||||
<em style="color:red;">[NC]</em>
|
||||
</dd>
|
||||
<dt><i>style_compression</i></dt>
|
||||
<dd>
|
||||
<em style="color:red;">[NC]</em>
|
||||
</dd>
|
||||
<dt>Returns:</dt>
|
||||
<dd>
|
||||
An object of the <a href="#xlwt.Workbook-class">Workbook</a> class
|
||||
</dd>
|
||||
</dl>
|
||||
<br />
|
||||
</dd>
|
||||
<dt><a id='xlwt.Workbook.add_sheet-method' name='xlwt.Workbook.add_sheet-method'><b>add_sheet(sheetname)</b></a> [<a href='#xlwt.Workbook.add_sheet-method'>#</a>]</dt>
|
||||
<dd>
|
||||
<p>
|
||||
This method is used to create Worksheets in a Workbook.
|
||||
</p>
|
||||
<dl>
|
||||
<dt><i>sheetname</i></dt>
|
||||
<dd>
|
||||
The name to use for this sheet, as it will appear in the tabs at
|
||||
the bottom of the Excel application.
|
||||
</dd>
|
||||
<dt>Returns:</dt>
|
||||
<dd>
|
||||
An object of the <a href="#xlwt.Worksheet-class">Worksheet</a> class
|
||||
</dd>
|
||||
</dl>
|
||||
<br />
|
||||
</dd>
|
||||
<dt><a id='xlwt.Workbook.save-method' name='xlwt.Workbook.save-method'><b>save(filename_or_stream)</b></a> [<a href='#xlwt.Workbook.save-method'>#</a>]</dt>
|
||||
<dd>
|
||||
<p>
|
||||
This method is used to save Workbook to a file in native Excel format.
|
||||
</p>
|
||||
<dl>
|
||||
<dt><i>filename_or_stream</i></dt>
|
||||
<dd>
|
||||
<p>
|
||||
This can be a string containing a filename of the file, in which case
|
||||
the excel file is saved to disk using the name provided.
|
||||
</p>
|
||||
<p>
|
||||
It can also be a stream object with a write method, such as a
|
||||
StringIO, in which case the data for the excel file is written
|
||||
to the stream.
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<br />
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<h2><a id='xlwt.Worksheet-class' name='xlwt.Worksheet-class'>The Worksheet Class</a><em style="color:red;">[NC]</em></h2>
|
||||
<dl>
|
||||
<dt><b>Worksheet(sheetname, parent_book)</b> (class) [<a href='#xlwt.Worksheet-class'>#</a>]</dt>
|
||||
<dd>
|
||||
<p>
|
||||
This is a class representing the contents of a sheet in a workbook.
|
||||
</p>
|
||||
<p>
|
||||
WARNING: You don't normally create instances of this class
|
||||
yourself. They are returned from calls to <a href="#xlwt.Workbook.add_sheet-method">Workbook.add_sheet</a>
|
||||
</p>
|
||||
</dd>
|
||||
<dt><a id='xlwt.Worksheet.write-method'
|
||||
name='xlwt.Worksheet.write-method'><b>write(r, c, label="", style=Style.default_style)</b></a> [<a href='#xlwt.Worksheet.write-method'>#</a>]</dt>
|
||||
<dd>
|
||||
<p>
|
||||
This method is used to write a cell to a Worksheet..
|
||||
</p>
|
||||
<dl>
|
||||
<dt><i>r</i></dt>
|
||||
<dd>
|
||||
The zero-relative number of the row in the worksheet to which the cell should be written.
|
||||
</dd>
|
||||
<dt><i>c</i></dt>
|
||||
<dd>
|
||||
The zero-relative number of the column in the worksheet to which the cell should be written.
|
||||
</dd>
|
||||
<dt><i>label</i></dt>
|
||||
<dd>
|
||||
The data value to be written.
|
||||
An int, long, or decimal.Decimal instance is converted to float.
|
||||
A unicode instance is written as is.
|
||||
A str instance is converted to unicode using the encoding (default: 'ascii') specified
|
||||
when the Workbook instance was created.
|
||||
A datetime.datetime, datetime.date, or datetime.time instance is converted into Excel date format
|
||||
(a float representing the number of days since (typically) 1899-12-31T00:00:00,
|
||||
under the pretence that 1900 was a leap year).
|
||||
A bool instance will show up as TRUE or FALSE in Excel.
|
||||
None causes the cell to be blank -- no data, only formatting.
|
||||
An xlwt.Formula instance causes an Excel formula to be written.
|
||||
<em style="color:red;">[NC]</em>
|
||||
</dd>
|
||||
<dt><i>style</i></dt>
|
||||
<dd>
|
||||
A style -- also known as an XF (extended format) -- is an XFStyle object, which encapsulates
|
||||
the formatting applied to the cell and its contents. XFStyle objects are best set up using the
|
||||
<i>easyxf</i> function. They may also be set up by setting attributes in
|
||||
Alignment, Borders, Pattern, Font and Protection objects
|
||||
then setting those objects and a format string as attributes of an XFStyle object.
|
||||
<em style="color:red;">[NC]</em>
|
||||
</dd>
|
||||
</dl>
|
||||
<br />
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
</body></html>
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python
|
||||
# tries stress SST, SAT and MSAT
|
||||
|
||||
from time import *
|
||||
from xlwt.Workbook import *
|
||||
from xlwt.Style import *
|
||||
|
||||
style = XFStyle()
|
||||
|
||||
wb = Workbook()
|
||||
ws0 = wb.add_sheet('0')
|
||||
|
||||
colcount = 200 + 1
|
||||
rowcount = 6000 + 1
|
||||
|
||||
t0 = time()
|
||||
print "\nstart: %s" % ctime(t0)
|
||||
|
||||
print "Filling..."
|
||||
for col in xrange(colcount):
|
||||
print "[%d]" % col,
|
||||
for row in xrange(rowcount):
|
||||
#ws0.write(row, col, "BIG(%d, %d)" % (row, col))
|
||||
ws0.write(row, col, "BIG")
|
||||
|
||||
t1 = time() - t0
|
||||
print "\nsince starting elapsed %.2f s" % (t1)
|
||||
|
||||
print "Storing..."
|
||||
wb.save('big-16Mb.xls')
|
||||
|
||||
t2 = time() - t0
|
||||
print "since starting elapsed %.2f s" % (t2)
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
# tries stress SST, SAT and MSAT
|
||||
|
||||
from time import *
|
||||
from xlwt import *
|
||||
|
||||
style = XFStyle()
|
||||
|
||||
wb = Workbook()
|
||||
ws0 = wb.add_sheet('0')
|
||||
|
||||
colcount = 200 + 1
|
||||
rowcount = 6000 + 1
|
||||
|
||||
t0 = time()
|
||||
print "\nstart: %s" % ctime(t0)
|
||||
|
||||
print "Filling..."
|
||||
for col in xrange(colcount):
|
||||
print "[%d]" % col,
|
||||
for row in xrange(rowcount):
|
||||
ws0.write(row, col, "BIG(%d, %d)" % (row, col))
|
||||
#ws0.write(row, col, "BIG")
|
||||
|
||||
t1 = time() - t0
|
||||
print "\nsince starting elapsed %.2f s" % (t1)
|
||||
|
||||
print "Storing..."
|
||||
wb.save('big-35Mb.xls')
|
||||
|
||||
t2 = time() - t0
|
||||
print "since starting elapsed %.2f s" % (t2)
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: windows-1251 -*-
|
||||
# Copyright (C) 2005 Kiseliov Roman
|
||||
|
||||
from xlwt import *
|
||||
|
||||
font0 = Font()
|
||||
font0.name = 'Times New Roman'
|
||||
font0.struck_out = True
|
||||
font0.bold = True
|
||||
|
||||
style0 = XFStyle()
|
||||
style0.font = font0
|
||||
|
||||
|
||||
wb = Workbook()
|
||||
ws0 = wb.add_sheet('0')
|
||||
|
||||
ws0.write(1, 1, 'Test', style0)
|
||||
|
||||
for i in range(0, 0x53):
|
||||
borders = Borders()
|
||||
borders.left = i
|
||||
borders.right = i
|
||||
borders.top = i
|
||||
borders.bottom = i
|
||||
|
||||
style = XFStyle()
|
||||
style.borders = borders
|
||||
|
||||
ws0.write(i, 2, '', style)
|
||||
ws0.write(i, 3, hex(i), style0)
|
||||
|
||||
ws0.write_merge(5, 8, 6, 10, "")
|
||||
|
||||
wb.save('blanks.xls')
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: windows-1251 -*-
|
||||
# Copyright (C) 2005 Kiseliov Roman
|
||||
__rev_id__ = """$Id: col_width.py 3315 2008-03-14 14:44:52Z chris $"""
|
||||
|
||||
|
||||
from xlwt import *
|
||||
|
||||
w = Workbook()
|
||||
ws = w.add_sheet('Hey, Dude')
|
||||
|
||||
for i in range(6, 80):
|
||||
fnt = Font()
|
||||
fnt.height = i*20
|
||||
style = XFStyle()
|
||||
style.font = fnt
|
||||
ws.write(1, i, 'Test')
|
||||
ws.col(i).width = 0x0d00 + i
|
||||
w.save('col_width.xls')
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: windows-1252 -*-
|
||||
# Copyright (C) 2007 John Machin
|
||||
|
||||
from xlwt import *
|
||||
|
||||
w = Workbook()
|
||||
w.country_code = 61
|
||||
ws = w.add_sheet('AU')
|
||||
w.save('country.xls')
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: windows-1251 -*-
|
||||
# Copyright (C) 2005 Kiseliov Roman
|
||||
|
||||
from xlwt import *
|
||||
from datetime import datetime
|
||||
|
||||
w = Workbook()
|
||||
ws = w.add_sheet('Hey, Dude')
|
||||
|
||||
fmts = [
|
||||
'M/D/YY',
|
||||
'D-MMM-YY',
|
||||
'D-MMM',
|
||||
'MMM-YY',
|
||||
'h:mm AM/PM',
|
||||
'h:mm:ss AM/PM',
|
||||
'h:mm',
|
||||
'h:mm:ss',
|
||||
'M/D/YY h:mm',
|
||||
'mm:ss',
|
||||
'[h]:mm:ss',
|
||||
'mm:ss.0',
|
||||
]
|
||||
|
||||
i = 0
|
||||
for fmt in fmts:
|
||||
ws.write(i, 0, fmt)
|
||||
|
||||
style = XFStyle()
|
||||
style.num_format_str = fmt
|
||||
|
||||
ws.write(i, 4, datetime.now(), style)
|
||||
|
||||
i += 1
|
||||
|
||||
w.save('dates.xls')
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: windows-1251 -*-
|
||||
# Copyright (C) 2005 Kiseliov Roman
|
||||
|
||||
from xlwt import *
|
||||
|
||||
font0 = Font()
|
||||
font0.name = 'Times New Roman'
|
||||
font0.struck_out = True
|
||||
font0.bold = True
|
||||
|
||||
style0 = XFStyle()
|
||||
style0.font = font0
|
||||
|
||||
|
||||
wb = Workbook()
|
||||
ws0 = wb.add_sheet('0')
|
||||
|
||||
ws0.write(1, 1, 'Test', style0)
|
||||
|
||||
for i in range(0, 0x53):
|
||||
fnt = Font()
|
||||
fnt.name = 'Arial'
|
||||
fnt.colour_index = i
|
||||
fnt.outline = True
|
||||
|
||||
borders = Borders()
|
||||
borders.left = i
|
||||
|
||||
style = XFStyle()
|
||||
style.font = fnt
|
||||
style.borders = borders
|
||||
|
||||
ws0.write(i, 2, 'colour', style)
|
||||
ws0.write(i, 3, hex(i), style0)
|
||||
|
||||
|
||||
wb.save('format.xls')
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: windows-1251 -*-
|
||||
# Copyright (C) 2005 Kiseliov Roman
|
||||
|
||||
from xlwt import *
|
||||
from xlwt.ExcelFormulaParser import FormulaParseException
|
||||
|
||||
w = Workbook()
|
||||
ws = w.add_sheet('F')
|
||||
|
||||
## This example is a little silly since the formula building is
|
||||
## so simplistic that it often fails because the generated text
|
||||
## has the wrong number of parameters for the function being
|
||||
## tested.
|
||||
|
||||
i = 0
|
||||
succeed_count = 0
|
||||
fail_count = 0
|
||||
for n in sorted(ExcelMagic.std_func_by_name):
|
||||
ws.write(i, 0, n)
|
||||
text = n + "($A$1)"
|
||||
try:
|
||||
formula = Formula(text)
|
||||
except FormulaParseException,e:
|
||||
print "Could not parse %r: %s" % (text,e.args[0])
|
||||
fail_count += 1
|
||||
else:
|
||||
ws.write(i, 3, formula)
|
||||
succeed_count += 1
|
||||
i += 1
|
||||
|
||||
w.save('formula_names.xls')
|
||||
|
||||
print "succeeded with %i functions, failed with %i" % (succeed_count,fail_count)
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: windows-1251 -*-
|
||||
# Copyright (C) 2005 Kiseliov Roman
|
||||
|
||||
from xlwt import *
|
||||
|
||||
w = Workbook()
|
||||
ws = w.add_sheet('F')
|
||||
|
||||
ws.write(0, 0, Formula("-(1+1)"))
|
||||
ws.write(1, 0, Formula("-(1+1)/(-2-2)"))
|
||||
ws.write(2, 0, Formula("-(134.8780789+1)"))
|
||||
ws.write(3, 0, Formula("-(134.8780789e-10+1)"))
|
||||
ws.write(4, 0, Formula("-1/(1+1)+9344"))
|
||||
|
||||
ws.write(0, 1, Formula("-(1+1)"))
|
||||
ws.write(1, 1, Formula("-(1+1)/(-2-2)"))
|
||||
ws.write(2, 1, Formula("-(134.8780789+1)"))
|
||||
ws.write(3, 1, Formula("-(134.8780789e-10+1)"))
|
||||
ws.write(4, 1, Formula("-1/(1+1)+9344"))
|
||||
|
||||
ws.write(0, 2, Formula("A1*B1"))
|
||||
ws.write(1, 2, Formula("A2*B2"))
|
||||
ws.write(2, 2, Formula("A3*B3"))
|
||||
ws.write(3, 2, Formula("A4*B4*sin(pi()/4)"))
|
||||
ws.write(4, 2, Formula("A5%*B5*pi()/1000"))
|
||||
|
||||
##############
|
||||
## NOTE: parameters are separated by semicolon!!!
|
||||
##############
|
||||
|
||||
|
||||
ws.write(5, 2, Formula("C1+C2+C3+C4+C5/(C1+C2+C3+C4/(C1+C2+C3+C4/(C1+C2+C3+C4)+C5)+C5)-20.3e-2"))
|
||||
ws.write(5, 3, Formula("C1^2"))
|
||||
ws.write(6, 2, Formula("SUM(C1;C2;;;;;C3;;;C4)"))
|
||||
ws.write(6, 3, Formula("SUM($A$1:$C$5)"))
|
||||
|
||||
ws.write(7, 0, Formula('"lkjljllkllkl"'))
|
||||
ws.write(7, 1, Formula('"yuyiyiyiyi"'))
|
||||
ws.write(7, 2, Formula('A8 & B8 & A8'))
|
||||
ws.write(8, 2, Formula('now()'))
|
||||
|
||||
ws.write(10, 2, Formula('TRUE'))
|
||||
ws.write(11, 2, Formula('FALSE'))
|
||||
ws.write(12, 3, Formula('IF(A1>A2;3;"hkjhjkhk")'))
|
||||
|
||||
w.save('formulas.xls')
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: windows-1251 -*-
|
||||
# Copyright (C) 2005 Kiseliov Roman
|
||||
|
||||
from xlwt import *
|
||||
|
||||
f = Font()
|
||||
f.height = 20*72
|
||||
f.name = 'Verdana'
|
||||
f.bold = True
|
||||
f.underline = Font.UNDERLINE_DOUBLE
|
||||
f.colour_index = 4
|
||||
|
||||
h_style = XFStyle()
|
||||
h_style.font = f
|
||||
|
||||
w = Workbook()
|
||||
ws = w.add_sheet('F')
|
||||
|
||||
##############
|
||||
## NOTE: parameters are separated by semicolon!!!
|
||||
##############
|
||||
|
||||
n = "HYPERLINK"
|
||||
ws.write_merge(1, 1, 1, 10, Formula(n + '("http://www.irs.gov/pub/irs-pdf/f1000.pdf";"f1000.pdf")'), h_style)
|
||||
ws.write_merge(2, 2, 2, 25, Formula(n + '("mailto:roman.kiseliov@gmail.com?subject=pyExcelerator-feedback&Body=Hello,%20Roman!";"pyExcelerator-feedback")'), h_style)
|
||||
|
||||
w.save("hyperlinks.xls")
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: windows-1251 -*-
|
||||
# Copyright (C) 2005 Kiseliov Roman
|
||||
|
||||
from xlwt import *
|
||||
|
||||
w = Workbook()
|
||||
ws = w.add_sheet('Image')
|
||||
ws.insert_bitmap('python.bmp', 2, 2)
|
||||
ws.insert_bitmap('python.bmp', 10, 2)
|
||||
|
||||
w.save('image.xls')
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: windows-1251 -*-
|
||||
# Copyright (C) 2005 Kiseliov Roman
|
||||
|
||||
from xlwt import *
|
||||
|
||||
fnt = Font()
|
||||
fnt.name = 'Arial'
|
||||
fnt.colour_index = 4
|
||||
fnt.bold = True
|
||||
|
||||
borders = Borders()
|
||||
borders.left = 6
|
||||
borders.right = 6
|
||||
borders.top = 6
|
||||
borders.bottom = 6
|
||||
|
||||
al = Alignment()
|
||||
al.horz = Alignment.HORZ_CENTER
|
||||
al.vert = Alignment.VERT_CENTER
|
||||
|
||||
style = XFStyle()
|
||||
style.font = fnt
|
||||
style.borders = borders
|
||||
style.alignment = al
|
||||
|
||||
|
||||
wb = Workbook()
|
||||
ws0 = wb.add_sheet('sheet0')
|
||||
ws1 = wb.add_sheet('sheet1')
|
||||
ws2 = wb.add_sheet('sheet2')
|
||||
|
||||
for i in range(0, 0x200, 2):
|
||||
ws0.write_merge(i, i+1, 1, 5, 'test %d' % i, style)
|
||||
ws1.write_merge(i, i, 1, 7, 'test %d' % i, style)
|
||||
ws2.write_merge(i, i+1, 1, 7 + (i%10), 'test %d' % i, style)
|
||||
|
||||
|
||||
wb.save('merged.xls')
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: windows-1251 -*-
|
||||
# Copyright (C) 2005 Kiseliov Roman
|
||||
|
||||
from xlwt import *
|
||||
|
||||
wb = Workbook()
|
||||
ws0 = wb.add_sheet('sheet0')
|
||||
|
||||
|
||||
fnt = Font()
|
||||
fnt.name = 'Arial'
|
||||
fnt.colour_index = 4
|
||||
fnt.bold = True
|
||||
|
||||
borders = Borders()
|
||||
borders.left = 6
|
||||
borders.right = 6
|
||||
borders.top = 6
|
||||
borders.bottom = 6
|
||||
|
||||
style = XFStyle()
|
||||
style.font = fnt
|
||||
style.borders = borders
|
||||
|
||||
ws0.write_merge(3, 3, 1, 5, 'test1', style)
|
||||
ws0.write_merge(4, 10, 1, 5, 'test2', style)
|
||||
ws0.col(1).width = 0x0d00
|
||||
|
||||
wb.save('merged0.xls')
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user