summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristophe Siraut <csiraut@entrouvert.com>2019-02-12 14:20:31 (GMT)
committerChristophe Siraut <csiraut@entrouvert.com>2019-02-12 14:20:31 (GMT)
commitf1b59bd452443bb3073fffd5c3c96c6d320d765f (patch)
tree2f00053914cdf452eac5f8b12aa8a79fc6a48ce2
parent2620c12e8a78e3b9a4742117fe5b8be47f82c720 (diff)
parent571e8826a9f721edbad4991cff45e86408879c11 (diff)
downloadpython-pyexcel-ods-f1b59bd452443bb3073fffd5c3c96c6d320d765f.zip
python-pyexcel-ods-f1b59bd452443bb3073fffd5c3c96c6d320d765f.tar.gz
python-pyexcel-ods-f1b59bd452443bb3073fffd5c3c96c6d320d765f.tar.bz2
Merge tag 'v0.5.4' of https://github.com/pyexcel/pyexcel-ods
-rw-r--r--.gitignore474
-rw-r--r--.moban.d/README.rst16
-rw-r--r--.moban.d/setup.py6
-rw-r--r--.moban.d/tests/requirements.txt4
-rw-r--r--.moban.d/tests/test_formatters.py4
-rw-r--r--.moban.d/travis.yml8
-rw-r--r--.moban.yml27
-rw-r--r--.travis.yml9
-rw-r--r--CHANGELOG.rst184
-rw-r--r--LICENSE6
-rw-r--r--MANIFEST.in1
-rw-r--r--Makefile9
-rw-r--r--README.rst133
-rw-r--r--changelog.yml185
-rw-r--r--docs/source/conf.py203
-rw-r--r--pyexcel-ods.yml (renamed from pyexcel_ods.yaml)9
-rw-r--r--pyexcel_ods/__init__.py26
-rw-r--r--pyexcel_ods/converter.py133
-rw-r--r--pyexcel_ods/ods.py287
-rw-r--r--pyexcel_ods/odsr.py160
-rw-r--r--pyexcel_ods/odsw.py102
-rw-r--r--requirements.txt4
-rw-r--r--rnd_requirements.txt3
-rw-r--r--setup.cfg4
-rw-r--r--setup.py169
-rw-r--r--test.bat3
-rw-r--r--test.sh2
-rw-r--r--tests/base.py110
-rw-r--r--tests/fixtures/12_day_as_time.odsbin0 -> 8026 bytes
-rw-r--r--tests/fixtures/comment-in-cell.odsbin0 -> 7936 bytes
-rw-r--r--tests/fixtures/issue_27.odsbin0 -> 10960 bytes
-rw-r--r--tests/fixtures/issue_61.odsbin0 -> 9539 bytes
-rw-r--r--tests/fixtures/multilineods.odsbin8030 -> 8218 bytes
-rwxr-xr-xtests/fixtures/pyexcel_81_ods_19.odsbin0 -> 11013 bytes
-rw-r--r--tests/fixtures/white_space.odsbin0 -> 9868 bytes
-rw-r--r--tests/requirements.txt5
-rw-r--r--tests/test_bug_fixes.py146
-rw-r--r--tests/test_filter.py49
-rw-r--r--tests/test_formatters.py52
-rw-r--r--tests/test_multiline_feature.py6
-rw-r--r--tests/test_multiple_sheets.py (renamed from tests/test_mutliple_sheets.py)44
-rw-r--r--tests/test_ods_reader.py20
-rw-r--r--tests/test_stringio.py44
-rw-r--r--tests/test_writer.py23
44 files changed, 1879 insertions, 791 deletions
diff --git a/.gitignore b/.gitignore
index d98e374..d09b336 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,470 @@
-*.pyc
-*~
+# moban hashes
+.moban.hashes
+
+# Extra rules from https://github.com/github/gitignore/
+# Python rules
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
.coverage
-pyexcel*-info
-build
-dist \ No newline at end of file
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+
+# VirtualEnv rules
+# Virtualenv
+# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
+.Python
+[Bb]in
+[Ii]nclude
+[Ll]ib
+[Ll]ib64
+[Ll]ocal
+[Ss]cripts
+pyvenv.cfg
+.venv
+pip-selfcheck.json
+
+# Linux rules
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+# Windows rules
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# macOS rules
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# Emacs rules
+# -*- mode: gitignore; -*-
+*~
+\#*\#
+/.emacs.desktop
+/.emacs.desktop.lock
+*.elc
+auto-save-list
+tramp
+.\#*
+
+# Org-mode
+.org-id-locations
+*_archive
+
+# flymake-mode
+*_flymake.*
+
+# eshell files
+/eshell/history
+/eshell/lastdir
+
+# elpa packages
+/elpa/
+
+# reftex files
+*.rel
+
+# AUCTeX auto folder
+/auto/
+
+# cask packages
+.cask/
+dist/
+
+# Flycheck
+flycheck_*.el
+
+# server auth directory
+/server/
+
+# projectiles files
+.projectile
+
+# directory configuration
+.dir-locals.el
+
+# Vim rules
+# Swap
+[._]*.s[a-v][a-z]
+[._]*.sw[a-p]
+[._]s[a-rt-v][a-z]
+[._]ss[a-gi-z]
+[._]sw[a-p]
+
+# Session
+Session.vim
+
+# Temporary
+.netrwhist
+*~
+# Auto-generated tag files
+tags
+# Persistent undo
+[._]*.un~
+
+# JetBrains rules
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# SublimeText rules
+# Cache files for Sublime Text
+*.tmlanguage.cache
+*.tmPreferences.cache
+*.stTheme.cache
+
+# Workspace files are user-specific
+*.sublime-workspace
+
+# Project files should be checked into the repository, unless a significant
+# proportion of contributors will probably not be using Sublime Text
+# *.sublime-project
+
+# SFTP configuration file
+sftp-config.json
+
+# Package control specific files
+Package Control.last-run
+Package Control.ca-list
+Package Control.ca-bundle
+Package Control.system-ca-bundle
+Package Control.cache/
+Package Control.ca-certs/
+Package Control.merged-ca-bundle
+Package Control.user-ca-bundle
+oscrypto-ca-bundle.crt
+bh_unicode_properties.cache
+
+# Sublime-github package stores a github token in this file
+# https://packagecontrol.io/packages/sublime-github
+GitHub.sublime-settings
+
+# KDevelop4 rules
+*.kdev4
+.kdev4/
+
+# Kate rules
+# Swap Files #
+.*.kate-swp
+.swp.*
+
+# TextMate rules
+*.tmproj
+*.tmproject
+tmtags
+
+# VisualStudioCode rules
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# Xcode rules
+# Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+## User settings
+xcuserdata/
+
+## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
+*.xcscmblueprint
+*.xccheckout
+
+## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
+build/
+DerivedData/
+*.moved-aside
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+
+# Eclipse rules
+
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# PyDev specific (Python IDE for Eclipse)
+*.pydevproject
+
+# CDT-specific (C/C++ Development Tooling)
+.cproject
+
+# CDT- autotools
+.autotools
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific (PHP Development Tools)
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# Tern plugin
+.tern-project
+
+# TeXlipse plugin
+.texlipse
+
+# STS (Spring Tool Suite)
+.springBeans
+
+# Code Recommenders
+.recommenders/
+
+# Annotation Processing
+.apt_generated/
+
+# Scala IDE specific (Scala & Java development for Eclipse)
+.cache-main
+.scala_dependencies
+.worksheet
+
+# TortoiseGit rules
+# Project-level settings
+/.tgitconfig
+
+# Tags rules
+# Ignore tags created by etags, ctags, gtags (GNU global) and cscope
+TAGS
+.TAGS
+!TAGS/
+tags
+.tags
+!tags/
+gtags.files
+GTAGS
+GRTAGS
+GPATH
+GSYMS
+cscope.files
+cscope.out
+cscope.in.out
+cscope.po.out
diff --git a/.moban.d/README.rst b/.moban.d/README.rst
index e3d0faf..83502eb 100644
--- a/.moban.d/README.rst
+++ b/.moban.d/README.rst
@@ -1,11 +1,25 @@
{%extends 'README.rst.jj2' %}
+{%block documentation_link%}
+{%endblock%}
+
{%block description%}
**pyexcel-ods** is a tiny wrapper library to read, manipulate and write data in
ods format using python 2.6 and python 2.7. You are likely to use it with
`pyexcel <https://github.com/pyexcel/pyexcel>`_.
`pyexcel-ods3 <https://github.com/pyexcel/pyexcel-ods3>`_ is a sister library that
-does the same thing but supports Python 3.3 and 3.4 and depends on lxml.
+depends on ezodf and lxml. `pyexcel-odsr <https://github.com/pyexcel/pyexcel-odsr>`_
+is the other sister library that has no external dependency but do ods reading only
+{%endblock%}
+
+{% block pagination_note%}
+Special notice 30/01/2017: due to the constraints of the underlying 3rd party
+library, it will read the whole file before returning the paginated data. So
+at the end of day, the only benefit is less data returned from the reading
+function. No major performance improvement will be seen.
+
+With that said, please install `pyexcel-odsr <https://github.com/pyexcel/pyexcel-odsr>`_
+and it gives better performance in pagination.
{%endblock%}
{%block extras %}
diff --git a/.moban.d/setup.py b/.moban.d/setup.py
index aa56454..b276d5a 100644
--- a/.moban.d/setup.py
+++ b/.moban.d/setup.py
@@ -5,9 +5,3 @@
{%block compat_block%}
{%endblock%}
-
-{%block additional_classifiers%}
- 'Programming Language :: Python :: 2.6',
- 'Programming Language :: Python :: 2.7'
-{%endblock%}}
-
diff --git a/.moban.d/tests/requirements.txt b/.moban.d/tests/requirements.txt
index 3f0c5aa..c2b0c7e 100644
--- a/.moban.d/tests/requirements.txt
+++ b/.moban.d/tests/requirements.txt
@@ -1,5 +1,9 @@
{% extends 'tests/requirements.txt.jj2' %}
{%block extras %}
+moban
+black;python_version>="3.6"
+isort;python_version>="3.6"
+psutil
pyexcel
pyexcel-xls
{%endblock%}
diff --git a/.moban.d/tests/test_formatters.py b/.moban.d/tests/test_formatters.py
new file mode 100644
index 0000000..919d6fb
--- /dev/null
+++ b/.moban.d/tests/test_formatters.py
@@ -0,0 +1,4 @@
+{% extends 'tests/test_formatters.py.jj2' %}
+
+{% block test_date_format %}
+{% endblock %}
diff --git a/.moban.d/travis.yml b/.moban.d/travis.yml
index 0f9b438..8da65a9 100644
--- a/.moban.d/travis.yml
+++ b/.moban.d/travis.yml
@@ -2,10 +2,10 @@
{%block custom_python_versions%}
python:
- - 2.6
- - 2.7
- - 3.3
- - 3.4
+ - 3.7-dev
+ - 3.6
- 3.5
+ - 3.4
+ - 2.7
{%endblock%}
diff --git a/.moban.yml b/.moban.yml
index 8294aab..7d0fe4d 100644
--- a/.moban.yml
+++ b/.moban.yml
@@ -1,17 +1,32 @@
+requires:
+ - type: git
+ url: https://github.com/moremoban/pypi-mobans
+ submodule: true
+ - https://github.com/pyexcel/pyexcel-mobans
configuration:
- configuration_dir: "commons/config"
+ configuration_dir: "pyexcel-mobans:config"
template_dir:
- - "commons/templates"
+ - "pyexcel-mobans:templates"
+ - "pypi-mobans:templates"
- ".moban.d"
- configuration: pyexcel_ods.yaml
+ configuration: pyexcel-ods.yml
targets:
- README.rst: README.rst
- setup.py: setup.py
- "docs/source/conf.py": "docs/source/conf.py.jj2"
- .travis.yml: travis.yml
+ - .gitignore: gitignore.jj2
- requirements.txt: requirements.txt
- - LICENSE: LICENSE.jj2
+ - LICENSE: NEW_BSD_LICENSE.jj2
- MANIFEST.in: MANIFEST.in.jj2
- "tests/requirements.txt": "tests/requirements.txt"
- - test.sh: test.sh.jj2
- - test.bat: test.sh.jj2
+ - test.sh: test.script.jj2
+ - test.bat: test.script.jj2
+ - "tests/test_filter.py": "tests/test_filter.py.jj2"
+ - "tests/test_formatters.py": "tests/test_formatters.py"
+ - "tests/test_stringio.py": "tests/test_stringio.py.jj2"
+ - "tests/test_writer.py": "tests/test_writer.py.jj2"
+ - "tests/base.py": "tests/base.py.jj2"
+ - output: CHANGELOG.rst
+ configuration: changelog.yml
+ template: CHANGELOG.rst.jj2 \ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 4f15504..e02c27d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,17 +3,16 @@ language: python
notifications:
email: false
python:
- - 2.6
- - 2.7
- - 3.3
- - 3.4
+ - 3.7-dev
+ - 3.6
- 3.5
+ - 3.4
+ - 2.7
before_install:
- if [[ $TRAVIS_PYTHON_VERSION == "2.6" ]]; then pip install flake8==2.6.2; fi
- if [[ -f min_requirements.txt && "$MINREQ" -eq 1 ]]; then
mv min_requirements.txt requirements.txt ;
fi
- - pip install --upgrade setuptools "pip==7.1"
- test ! -f rnd_requirements.txt || pip install --no-deps -r rnd_requirements.txt
- test ! -f rnd_requirements.txt || pip install -r rnd_requirements.txt ;
- pip install -r tests/requirements.txt
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index eaedf8c..6d621ca 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,11 +1,135 @@
Change log
================================================================================
+0.5.4 - 27.11.2018
+--------------------------------------------------------------------------------
+
+added
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+#. `#30 <https://github.com/pyexcel/pyexcel-ods/issues/30>`_, long type will not
+ be written in ods. please use string type. And if the integer is equal or
+ greater than 10 to the power of 16, it will not be written either in ods. In
+ both situation, IntegerPrecisionLossError will be raised.
+
+0.5.3 - unreleased
+--------------------------------------------------------------------------------
+
+added
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+#. `#24 <https://github.com/pyexcel/pyexcel-ods/issues/24>`_, ignore
+ comments(<office:comment>) in cell
+#. `#27 <https://github.com/pyexcel/pyexcel-ods/issues/27>`_, exception raised
+ when currency type is missing
+#. fix odfpy version on 1.3.5.
+
+0.5.2 - 23.10.2017
+--------------------------------------------------------------------------------
+
+updated
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+#. pyexcel `pyexcel#105 <https://github.com/pyexcel/pyexcel/issues/105>`_,
+ remove gease from setup_requires, introduced by 0.5.1.
+#. remove python2.6 test support
+
+0.5.1 - 20.10.2017
+--------------------------------------------------------------------------------
+
+added
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+#. `pyexcel#103 <https://github.com/pyexcel/pyexcel/issues/103>`_, include
+ LICENSE file in MANIFEST.in, meaning LICENSE file will appear in the released
+ tar ball.
+
+0.5.0 - 30.08.2017
+--------------------------------------------------------------------------------
+
+Updated
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+#. put dependency on pyexcel-io 0.5.0, which uses cStringIO instead of StringIO.
+ Hence, there will be performance boost in handling files in memory.
+
+Relocated
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+#. All ods type conversion code lives in pyexcel_io.service module
+
+0.4.1 - 25.08.2017
+--------------------------------------------------------------------------------
+
+Updated
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+#. `pyexcel#23 <https://github.com/pyexcel/pyexcel/issues/23>`_, handle
+ unseekable stream given by http response
+#. PR `#22 <https://github.com/pyexcel/pyexcel-ods/pull/22>`_, hanle white
+ spaces in a cell.
+
+0.4.0 - 19.06.2017
+--------------------------------------------------------------------------------
+
+Updated
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+#. `pyexcel#14 <https://github.com/pyexcel/pyexcel/issues/14>`_, close file
+ handle
+#. pyexcel-io plugin interface now updated to use `lml
+ <https://github.com/chfw/lml>`_.
+
+0.3.3 - 07.05.2017
+--------------------------------------------------------------------------------
+
+Updated
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+#. issue `pyexcel#19 <https://github.com/pyexcel/pyexcel/issues/19>`_, not all
+ texts in a multi-node cell were extracted.
+
+0.3.2 - 13.04.2017
+--------------------------------------------------------------------------------
+
+Updated
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+#. issue `pyexcel#17 <https://github.com/pyexcel/pyexcel/issues/17>`_, empty new
+ line is ignored
+#. issue `pyexcel#6 <https://github.com/pyexcel/pyexcel/issues/6>`_,
+ PT288H00M00S is valid duration
+
+0.3.1 - 02.02.2017
+--------------------------------------------------------------------------------
+
+Added
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+#. Recognize currency type
+
+0.3.0 - 22.12.2016
+--------------------------------------------------------------------------------
+
+Updated
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+#. Code refactoring with pyexcel-io v 0.3.0
+
+0.2.2 - 24.10.2016
+--------------------------------------------------------------------------------
+
+Updated
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+#. issue `pyexcel#14 <https://github.com/pyexcel/pyexcel/issues/14>`_, index
+ error when reading a ods file that has non-uniform columns repeated property.
+
0.2.1 - 31.08.2016
--------------------------------------------------------------------------------
Added
-********************************************************************************
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#. support pagination. two pairs: start_row, row_limit and start_column,
column_limit help you deal with large files.
@@ -15,114 +139,116 @@ Added
--------------------------------------------------------------------------------
Added
-********************************************************************************
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#. By default, `float` will be converted to `int` where fits. `auto_detect_int`,
a flag to switch off the autoatic conversion from `float` to `int`.
#. 'library=pyexcel-ods' was added so as to inform pyexcel to use it instead of
other libraries, in the situation where multiple plugins were installed.
-
Updated
-********************************************************************************
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#. support the auto-import feature of pyexcel-io 0.2.0
-
0.1.1 - 30.01.2016
--------------------------------------------------------------------------------
Added
-********************************************************************************
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-#. 'streaming' is an extra option given to get_data. Only when 'streaming'
- is explicitly set to True, the data will be consisted of generators,
- hence will break your existing code.
+#. 'streaming' is an extra option given to get_data. Only when 'streaming' is
+ explicitly set to True, the data will be consisted of generators, hence will
+ break your existing code.
#. uses yield in to_array and returns a generator
#. support multi-line text cell #5
#. feature migration from pyexcel-ods3 pyexcel/pyexcel-ods3#5
Updated
-********************************************************************************
-#. compatibility with pyexcel-io 0.1.1
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+#. compatibility with pyexcel-io 0.1.1
0.0.12 - 10.10.2015
--------------------------------------------------------------------------------
Updated
-********************************************************************************
-#. Bug fix: excessive trailing columns with empty values
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+#. Bug fix: excessive trailing columns with empty values
0.0.11 - 26.09.2015
--------------------------------------------------------------------------------
Updated
-********************************************************************************
-#. Complete fix for libreoffice datetime field
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+#. Complete fix for libreoffice datetime field
0.0.10 - 15.09.2015
--------------------------------------------------------------------------------
Updated
-********************************************************************************
-#. Bug fix: date field could have datetime from libreoffice
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+#. Bug fix: date field could have datetime from libreoffice
0.0.9 - 21.08.2015
--------------------------------------------------------------------------------
Updated
-********************************************************************************
-#. Bug fix: utf-8 string throw unicode exceptions
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+#. Bug fix: utf-8 string throw unicode exceptions
0.0.8 - 28.06.2015
--------------------------------------------------------------------------------
Updated
-********************************************************************************
-#. Pin dependency odfpy 0.9.6 to avoid buggy odfpy 1.3.0
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+#. Pin dependency odfpy 0.9.6 to avoid buggy odfpy 1.3.0
0.0.7 - 28.05.2015
--------------------------------------------------------------------------------
Updated
-********************************************************************************
-#. Bug fix: "number-columns-repeated" is now respected
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+#. Bug fix: "number-columns-repeated" is now respected
0.0.6 - 21.05.2015
--------------------------------------------------------------------------------
Updated
-********************************************************************************
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
#. get_data and save_data are seen across pyexcel-* extensions. remember them
once and use them across all extensions.
-
0.0.5 - 22.02.2015
--------------------------------------------------------------------------------
Added
-********************************************************************************
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#. Loads only one sheet from a multiple sheet book
#. Use New BSD License
-
0.0.4 - 14.12.2014
--------------------------------------------------------------------------------
Updated
-********************************************************************************
-#. IO interface update as pyexcel-io introduced keywords.
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+#. IO interface update as pyexcel-io introduced keywords.
+#. initial release
0.0.3 - 08.12.2014
--------------------------------------------------------------------------------
+Updated
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+#. IO interface update as pyexcel-io introduced keywords.
#. initial release
diff --git a/LICENSE b/LICENSE
index 6e42f18..6eef51e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2015-2016 by Onni Software Ltd. and its contributors
+Copyright (c) 2015-2018 by Onni Software Ltd. and its contributors
All rights reserved.
Redistribution and use in source and binary forms of the software as well
@@ -13,7 +13,7 @@ that the following conditions are met:
and/or other materials provided with the distribution.
* Neither the name of 'pyexcel-ods' nor the names of the contributors
- may not be used to endorse or promote products derived from this software
+ may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
@@ -27,4 +27,4 @@ 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 AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGE. \ No newline at end of file
+DAMAGE.
diff --git a/MANIFEST.in b/MANIFEST.in
index 5f13ef0..c2e4b1b 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,3 @@
include README.rst
+include LICENSE
include CHANGELOG.rst
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..62b93c1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,9 @@
+all: test
+
+test:
+ bash test.sh
+
+format:
+ isort -y $(find pyexcel_ods -name "*.py"|xargs echo) $(find tests -name "*.py"|xargs echo)
+ black -l 79 pyexcel_ods
+ black -l 79 tests
diff --git a/README.rst b/README.rst
index 112dd94..099fb92 100644
--- a/README.rst
+++ b/README.rst
@@ -2,17 +2,28 @@
pyexcel-ods - Let you focus on data, instead of ods format
================================================================================
-.. image:: https://api.travis-ci.org/pyexcel/pyexcel-ods.png
- :target: http://travis-ci.org/pyexcel/pyexcel-ods
+.. image:: https://raw.githubusercontent.com/pyexcel/pyexcel.github.io/master/images/patreon.png
+ :target: https://www.patreon.com/pyexcel
+
+.. image:: https://api.bountysource.com/badge/team?team_id=288537
+ :target: https://salt.bountysource.com/teams/chfw-pyexcel
+
+.. image:: https://travis-ci.org/pyexcel/pyexcel-ods.svg?branch=master
+ :target: http://travis-ci.org/pyexcel/pyexcel-ods
+
+.. image:: https://codecov.io/gh/pyexcel/pyexcel-ods/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/pyexcel/pyexcel-ods
+
+.. image:: https://img.shields.io/gitter/room/gitterHQ/gitter.svg
+ :target: https://gitter.im/pyexcel/Lobby
-.. image:: https://codecov.io/github/pyexcel/pyexcel-ods/coverage.png
- :target: https://codecov.io/github/pyexcel/pyexcel-ods
**pyexcel-ods** is a tiny wrapper library to read, manipulate and write data in
ods format using python 2.6 and python 2.7. You are likely to use it with
`pyexcel <https://github.com/pyexcel/pyexcel>`_.
`pyexcel-ods3 <https://github.com/pyexcel/pyexcel-ods3>`_ is a sister library that
-does the same thing but supports Python 3.3 and 3.4 and depends on lxml.
+depends on ezodf and lxml. `pyexcel-odsr <https://github.com/pyexcel/pyexcel-odsr>`_
+is the other sister library that has no external dependency but do ods reading only
Known constraints
==================
@@ -22,7 +33,8 @@ Fonts, colors and charts are not supported.
Installation
================================================================================
-You can install it via pip:
+
+You can install pyexcel-ods via pip:
.. code-block:: bash
@@ -33,19 +45,34 @@ or clone it and install it:
.. code-block:: bash
- $ git clone http://github.com/pyexcel/pyexcel-ods.git
+ $ git clone https://github.com/pyexcel/pyexcel-ods.git
$ cd pyexcel-ods
$ python setup.py install
+Support the project
+================================================================================
+
+If your company has embedded pyexcel and its components into a revenue generating
+product, please support me on `patreon <https://www.patreon.com/bePatron?u=5537627>`_
+or `bounty source <https://salt.bountysource.com/teams/chfw-pyexcel>`_ to maintain
+the project and develop it further.
+
+If you are an individual, you are welcome to support me too and for however long
+you feel like. As my backer, you will receive
+`early access to pyexcel related contents <https://www.patreon.com/pyexcel/posts>`_.
+
+And your issues will get prioritized if you would like to become my patreon as `pyexcel pro user`.
+
+With your financial support, I will be able to invest
+a little bit more time in coding, documentation and writing interesting posts.
+
+
Usage
================================================================================
As a standalone library
--------------------------------------------------------------------------------
-Write to an ods file
-********************************************************************************
-
.. testcode::
:hide:
@@ -62,6 +89,11 @@ Write to an ods file
... from collections import OrderedDict
+Write to an ods file
+********************************************************************************
+
+
+
Here's the sample code to write a dictionary to an ods file:
.. code-block:: python
@@ -72,6 +104,7 @@ Here's the sample code to write a dictionary to an ods file:
>>> data.update({"Sheet 2": [["row 1", "row 2", "row 3"]]})
>>> save_data("your_file.ods", data)
+
Read from an ods file
********************************************************************************
@@ -105,6 +138,7 @@ Here's the sample code to write a dictionary to an ods file:
+
Read from an ods from memory
********************************************************************************
@@ -123,6 +157,14 @@ Continue from previous example:
Pagination feature
********************************************************************************
+Special notice 30/01/2017: due to the constraints of the underlying 3rd party
+library, it will read the whole file before returning the paginated data. So
+at the end of day, the only benefit is less data returned from the reading
+function. No major performance improvement will be seen.
+
+With that said, please install `pyexcel-odsr <https://github.com/pyexcel/pyexcel-odsr>`_
+and it gives better performance in pagination.
+
Let's assume the following file is a huge ods file:
.. code-block:: python
@@ -179,16 +221,6 @@ No longer, explicit import is needed since pyexcel version 0.2.2. Instead,
this library is auto-loaded. So if you want to read data in ods format,
installing it is enough.
-Any version under pyexcel 0.2.2, you have to keep doing the following:
-
-Import it in your file to enable this plugin:
-
-.. code-block:: python
-
- from pyexcel.ext import ods
-
-Please note only pyexcel version 0.0.4+ support this.
-
Reading from an ods file
********************************************************************************
@@ -198,7 +230,6 @@ Here is the sample code:
.. code-block:: python
>>> import pyexcel as pe
- >>> # from pyexcel.ext import ods
>>> sheet = pe.get_book(file_name="your_file.ods")
>>> sheet
Sheet 1:
@@ -269,6 +300,7 @@ You need to pass a StringIO instance to Writer:
>>> # In reality, you might give it to your http response
>>> # object for downloading
+
License
================================================================================
@@ -284,7 +316,7 @@ Development steps for code changes
Upgrade your setup tools and pip. They are needed for development and testing only:
-#. pip install --upgrade setuptools "pip==7.1"
+#. pip install --upgrade setuptools pip
Then install relevant development requirements:
@@ -292,18 +324,39 @@ Then install relevant development requirements:
#. pip install -r requirements.txt
#. pip install -r tests/requirements.txt
+Once you have finished your changes, please provide test case(s), relevant documentation
+and update CHANGELOG.rst.
-In order to update test environment, and documentation, additional setps are
-required:
+.. note::
+
+ As to rnd_requirements.txt, usually, it is created when a dependent
+ library is not released. Once the dependecy is installed
+ (will be released), the future
+ version of the dependency in the requirements.txt will be valid.
-#. pip install moban
-#. git clone https://github.com/pyexcel/pyexcel-commons.git
-#. make your changes in `.moban.d` directory, then issue command `moban`
-What is rnd_requirements.txt
--------------------------------
+How to test your contribution
+------------------------------
+
+Although `nose` and `doctest` are both used in code testing, it is adviable that unit tests are put in tests. `doctest` is incorporated only to make sure the code examples in documentation remain valid across different development releases.
+
+On Linux/Unix systems, please launch your tests like this::
+
+ $ make
+
+On Windows systems, please issue this command::
+
+ > test.bat
-Usually, it is created when a dependent library is not released. Once the dependecy is installed(will be released), the future version of the dependency in the requirements.txt will be valid.
+How to update test environment and update documentation
+---------------------------------------------------------
+
+Additional steps are required:
+
+#. pip install moban
+#. git clone https://github.com/moremoban/setupmobans.git # generic setup
+#. git clone https://github.com/pyexcel/pyexcel-commons.git commons
+#. make your changes in `.moban.d` directory, then issue command `moban`
What is pyexcel-commons
---------------------------------
@@ -315,18 +368,16 @@ What is .moban.d
`.moban.d` stores the specific meta data for the library.
-How to test your contribution
-------------------------------
-
-Although `nose` and `doctest` are both used in code testing, it is adviable that unit tests are put in tests. `doctest` is incorporated only to make sure the code examples in documentation remain valid across different development releases.
-
-On Linux/Unix systems, please launch your tests like this::
-
- $ make test
+Acceptance criteria
+-------------------
-On Windows systems, please issue this command::
-
- > test.bat
+#. Has Test cases written
+#. Has all code lines tested
+#. Passes all Travis CI builds
+#. Has fair amount of documentation if your change is complex
+#. Please update CHANGELOG.rst
+#. Please add yourself to CONTRIBUTORS.rst
+#. Agree on NEW BSD License for your contribution
Credits
================================================================================
diff --git a/changelog.yml b/changelog.yml
new file mode 100644
index 0000000..fc2e04f
--- /dev/null
+++ b/changelog.yml
@@ -0,0 +1,185 @@
+name: pyexcel-ods
+organisation: pyexcel
+releases:
+- changes:
+ - action: added
+ details:
+ - '`#30`, long type will not be written in ods. please use string type. And if the integer is equal or greater than 10 to the power of 16, it will not be written either in ods. In both situation, IntegerPrecisionLossError will be raised.'
+ date: 27.11.2018
+ version: 0.5.4
+- changes:
+ - action: added
+ details:
+ - '`#24`, ignore comments(<office:comment>) in cell'
+ - '`#27`, exception raised when currency type is missing'
+ - fix odfpy version on 1.3.5.
+ date: unreleased
+ version: 0.5.3
+- changes:
+ - action: updated
+ details:
+ - pyexcel `pyexcel#105`, remove gease from setup_requires, introduced by 0.5.1.
+ - remove python2.6 test support
+ date: 23.10.2017
+ version: 0.5.2
+- changes:
+ - action: added
+ details:
+ - '`pyexcel#103`, include LICENSE file in MANIFEST.in, meaning LICENSE file will
+ appear in the released tar ball.'
+ date: 20.10.2017
+ version: 0.5.1
+- changes:
+ - action: Updated
+ details:
+ - put dependency on pyexcel-io 0.5.0, which uses cStringIO instead of StringIO. Hence,
+ there will be performance boost in handling files in memory.
+ - action: Relocated
+ details:
+ - All ods type conversion code lives in pyexcel_io.service module
+ date: 30.08.2017
+ version: 0.5.0
+- changes:
+ - action: Updated
+ details:
+ - '`pyexcel#pyexcel-ods#23`, handle unseekable stream given by http response'
+ - PR `pyexcel#pyexcel-ods#PR#22`, hanle white spaces in a cell.
+ date: 25.08.2017
+ version: 0.4.1
+- changes:
+ - action: Updated
+ details:
+ - '`pyexcel#pyexcel-xlsx#14`, close file handle'
+ - pyexcel-io plugin interface now updated to use `lml <https://github.com/chfw/lml>`_.
+ date: 19.06.2017
+ version: 0.4.0
+- changes:
+ - action: Updated
+ details:
+ - issue `pyexcel#pyexcel-odsr#19`, not all texts in a multi-node cell were extracted.
+ date: 07.05.2017
+ version: 0.3.3
+- changes:
+ - action: Updated
+ details:
+ - issue `pyexcel#pyexcel-ods#17`, empty new line is ignored
+ - issue `pyexcel#pyexcel-ods#6`, PT288H00M00S is valid duration
+ date: 13.04.2017
+ version: 0.3.2
+- changes:
+ - action: Added
+ details:
+ - Recognize currency type
+ date: 02.02.2017
+ version: 0.3.1
+- changes:
+ - action: Updated
+ details:
+ - Code refactoring with pyexcel-io v 0.3.0
+ date: 22.12.2016
+ version: 0.3.0
+- changes:
+ - action: Updated
+ details:
+ - issue `pyexcel#pyexcel-ods#14`, index error when reading a ods file that has
+ non-uniform columns repeated property.
+ date: 24.10.2016
+ version: 0.2.2
+- changes:
+ - action: Added
+ details:
+ - 'support pagination. two pairs: start_row, row_limit and start_column, column_limit
+ help you deal with large files.'
+ - use odfpy 1.3.3 as compulsory package. offically support python 3
+ date: 31.08.2016
+ version: 0.2.1
+- changes:
+ - action: Added
+ details:
+ - By default, `float` will be converted to `int` where fits. `auto_detect_int`, a
+ flag to switch off the autoatic conversion from `float` to `int`.
+ - '''library=pyexcel-ods'' was added so as to inform pyexcel to use it instead
+ of other libraries, in the situation where multiple plugins were installed.'
+ - action: Updated
+ details:
+ - support the auto-import feature of pyexcel-io 0.2.0
+ date: 01.06.2016
+ version: 0.2.0
+- changes:
+ - action: Added
+ details:
+ - '''streaming'' is an extra option given to get_data. Only when ''streaming'' is
+ explicitly set to True, the data will be consisted of generators, hence will
+ break your existing code.'
+ - uses yield in to_array and returns a generator
+ - 'support multi-line text cell #5'
+ - feature migration from pyexcel-ods3 pyexcel/pyexcel-ods3#5
+ - action: Updated
+ details:
+ - compatibility with pyexcel-io 0.1.1
+ date: 30.01.2016
+ version: 0.1.1
+- changes:
+ - action: Updated
+ details:
+ - 'Bug fix: excessive trailing columns with empty values'
+ date: 10.10.2015
+ version: 0.0.12
+- changes:
+ - action: Updated
+ details:
+ - Complete fix for libreoffice datetime field
+ date: 26.09.2015
+ version: 0.0.11
+- changes:
+ - action: Updated
+ details:
+ - 'Bug fix: date field could have datetime from libreoffice'
+ date: 15.09.2015
+ version: 0.0.10
+- changes:
+ - action: Updated
+ details:
+ - 'Bug fix: utf-8 string throw unicode exceptions'
+ date: 21.08.2015
+ version: 0.0.9
+- changes:
+ - action: Updated
+ details:
+ - Pin dependency odfpy 0.9.6 to avoid buggy odfpy 1.3.0
+ date: 28.06.2015
+ version: 0.0.8
+- changes:
+ - action: Updated
+ details:
+ - 'Bug fix: "number-columns-repeated" is now respected'
+ date: 28.05.2015
+ version: 0.0.7
+- changes:
+ - action: Updated
+ details:
+ - get_data and save_data are seen across pyexcel-* extensions. remember them once
+ and use them across all extensions.
+ date: 21.05.2015
+ version: 0.0.6
+- changes:
+ - action: Added
+ details:
+ - Loads only one sheet from a multiple sheet book
+ - Use New BSD License
+ date: 22.02.2015
+ version: 0.0.5
+- changes:
+ - action: Updated
+ details:
+ - IO interface update as pyexcel-io introduced keywords.
+ - initial release
+ date: 14.12.2014
+ version: 0.0.4
+- changes:
+ - action: Updated
+ details:
+ - IO interface update as pyexcel-io introduced keywords.
+ - initial release
+ date: 08.12.2014
+ version: 0.0.3
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 1f3f712..5d14f4c 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -1,39 +1,196 @@
# -*- coding: utf-8 -*-
-extensions = [
- 'sphinx.ext.autodoc',
- 'sphinx.ext.doctest',
- 'sphinx.ext.intersphinx',
- 'sphinx.ext.viewcode',
-]
+DESCRIPTION = (
+ 'A wrapper library to read, manipulate and write data in ods format' +
+ ''
+)
+# -*- coding: utf-8 -*-
+#
+# Configuration file for the Sphinx documentation builder.
+#
+# This file does only contain a selection of the most common options. For a
+# full list see the documentation:
+# http://www.sphinx-doc.org/en/master/config
-intersphinx_mapping = {
- 'pyexcel': ('http://pyexcel.readthedocs.org/en/latest/', None)
-}
-spelling_word_list_filename = 'spelling_wordlist.txt'
+# -- Path setup --------------------------------------------------------------
+
+# 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.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+# -- Project information -----------------------------------------------------
+
+project = u'pyexcel-ods'
+copyright = u'2015-2018 Onni Software Ltd.'
+author = u'C.W.'
+
+# The short X.Y version
+version = u'0.5.4'
+# The full version, including alpha/beta/rc tags
+release = u'0.5.4'
+
+
+# -- 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.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode',]
+
+# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
+
+# The master toctree document.
master_doc = 'index'
-project = u'pyexcel-ods'
-copyright = u'2015-2016 Onni Software Ltd.'
-version = '0.2.0'
-release = '0.2.1'
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = 'en'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
-pygments_style = 'sphinx'
-html_theme = 'default'
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = None
+
+
+# -- 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 = 'alabaster'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# 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']
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# The default sidebars (for documents that don't match any pattern) are
+# defined by theme itself. Builtin themes are using these templates by
+# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
+# 'searchbox.html']``.
+#
+# html_sidebars = {}
+
+
+# -- Options for HTMLHelp output ---------------------------------------------
+
+# Output file base name for HTML help builder.
htmlhelp_basename = 'pyexcel-odsdoc'
-latex_elements = {}
+
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
latex_documents = [
- ('index', 'pyexcel-ods.tex', u'pyexcel-ods Documentation',
- 'Onni Software Ltd.', 'manual'),
+ (master_doc, 'pyexcel-ods.tex', u'pyexcel-ods Documentation',
+ u'Onni Software Ltd.', 'manual'),
]
+
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
man_pages = [
- ('index', 'pyexcel-ods', u'pyexcel-ods Documentation',
- [u'Onni Software Ltd.'], 1)
+ (master_doc, 'pyexcel-ods', u'pyexcel-ods Documentation',
+ [author], 1)
]
+
+
+# -- Options for Texinfo output ----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'pyexcel-ods', u'pyexcel-ods Documentation',
+ author, 'pyexcel-ods', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+
+# -- Options for Epub output -------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#
+# epub_identifier = ''
+
+# A unique identification for the text.
+#
+# epub_uid = ''
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+# -- Extension configuration -------------------------------------------------
+# -- Options for intersphinx extension ---------------------------------------
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'https://docs.python.org/': None}
+# TODO: html_theme not configurable upstream
+html_theme = 'default'
+
+# TODO: DESCRIPTION not configurable upstream
texinfo_documents = [
- ('index', 'pyexcel-ods', u'pyexcel-ods Documentation',
- 'Onni Software Ltd.', 'pyexcel-ods', 'One line description of project.',
+ ('index', 'pyexcel-ods',
+ 'pyexcel-ods Documentation',
+ 'Onni Software Ltd.', 'pyexcel-ods',
+ DESCRIPTION,
'Miscellaneous'),
]
+intersphinx_mapping.update({
+ 'pyexcel': ('http://pyexcel.readthedocs.io/en/latest/', None),
+})
diff --git a/pyexcel_ods.yaml b/pyexcel-ods.yml
index 667d2b3..c0d4fda 100644
--- a/pyexcel_ods.yaml
+++ b/pyexcel-ods.yml
@@ -1,10 +1,11 @@
overrides: "pyexcel.yaml"
name: "pyexcel-ods"
nick_name: ods
-version: 0.2.1
-release: 0.2.0
+version: 0.5.4
+current_version: 0.5.4
+release: 0.5.4
file_type: ods
dependencies:
- - pyexcel-io>=0.2.0
- - odfpy>=1.3.3
+ - pyexcel-io>=0.5.10
+ - odfpy==1.3.5
description: A wrapper library to read, manipulate and write data in ods format
diff --git a/pyexcel_ods/__init__.py b/pyexcel_ods/__init__.py
index 5bc061e..3c2a28b 100644
--- a/pyexcel_ods/__init__.py
+++ b/pyexcel_ods/__init__.py
@@ -2,28 +2,40 @@
pyexcel_ods
~~~~~~~~~~~~~~~~~~~
The lower level ods file format handler using odfpy
- :copyright: (c) 2015-2016 by Onni Software Ltd & its contributors
+ :copyright: (c) 2015-2017 by Onni Software Ltd & its contributors
:license: New BSD License
"""
# flake8: noqa
# this line has to be place above all else
# because of dynamic import
-_FILE_TYPE = 'ods'
-__pyexcel_io_plugins__ = [_FILE_TYPE]
+from pyexcel_io.plugins import IOPluginInfoChain
+from pyexcel_io.io import (
+ get_data as read_data,
+ isstream,
+ store_data as write_data,
+)
-
-from pyexcel_io.io import get_data as read_data, isstream, store_data as write_data
+__FILE_TYPE__ = "ods"
+IOPluginInfoChain(__name__).add_a_reader(
+ relative_plugin_class_path="odsr.ODSBook",
+ file_types=[__FILE_TYPE__],
+ stream_type="binary",
+).add_a_writer(
+ relative_plugin_class_path="odsw.ODSWriter",
+ file_types=[__FILE_TYPE__],
+ stream_type="binary",
+)
def save_data(afile, data, file_type=None, **keywords):
"""standalone module function for writing module supported file type"""
if isstream(afile) and file_type is None:
- file_type = _FILE_TYPE
+ file_type = __FILE_TYPE__
write_data(afile, data, file_type=file_type, **keywords)
def get_data(afile, file_type=None, **keywords):
"""standalone module function for reading module supported file type"""
if isstream(afile) and file_type is None:
- file_type = _FILE_TYPE
+ file_type = __FILE_TYPE__
return read_data(afile, file_type=file_type, **keywords)
diff --git a/pyexcel_ods/converter.py b/pyexcel_ods/converter.py
deleted file mode 100644
index fc65958..0000000
--- a/pyexcel_ods/converter.py
+++ /dev/null
@@ -1,133 +0,0 @@
-import sys
-import datetime
-
-PY2 = sys.version_info[0] == 2
-
-
-def float_value(value):
- """convert a value to float"""
- ret = float(value)
- return ret
-
-
-def date_value(value):
- """convert to data value accroding ods specification"""
- ret = "invalid"
- try:
- # catch strptime exceptions only
- if len(value) == 10:
- ret = datetime.datetime.strptime(
- value,
- "%Y-%m-%d")
- ret = ret.date()
- elif len(value) == 19:
- ret = datetime.datetime.strptime(
- value,
- "%Y-%m-%dT%H:%M:%S")
- elif len(value) > 19:
- ret = datetime.datetime.strptime(
- value[0:26],
- "%Y-%m-%dT%H:%M:%S.%f")
- except ValueError:
- pass
- if ret == "invalid":
- raise Exception("Bad date value %s" % value)
- return ret
-
-
-def ods_date_value(value):
- return value.strftime("%Y-%m-%d")
-
-
-def time_value(value):
- """convert to time value accroding the specification"""
- hour = int(value[2:4])
- minute = int(value[5:7])
- second = int(value[8:10])
- if hour < 24:
- ret = datetime.time(hour, minute, second)
- else:
- ret = datetime.timedelta(hours=hour, minutes=minute, seconds=second)
- return ret
-
-
-def ods_time_value(value):
- return value.strftime("PT%HH%MM%SS")
-
-
-def boolean_value(value):
- """get bolean value"""
- if value == "true":
- ret = True
- else:
- ret = False
- return ret
-
-
-def ods_bool_value(value):
- """convert a boolean value to text"""
- if value is True:
- return "true"
- else:
- return "false"
-
-
-def ods_timedelta_value(cell):
- """convert a cell value to time delta"""
- hours = cell.days * 24 + cell.seconds // 3600
- minutes = (cell.seconds // 60) % 60
- seconds = cell.seconds % 60
- return "PT%02dH%02dM%02dS" % (hours, minutes, seconds)
-
-
-ODS_FORMAT_CONVERSION = {
- "float": float,
- "date": datetime.date,
- "time": datetime.time,
- 'timedelta': datetime.timedelta,
- "boolean": bool,
- "percentage": float,
- "currency": float
-}
-
-
-ODS_WRITE_FORMAT_COVERSION = {
- float: "float",
- int: "float",
- str: "string",
- datetime.date: "date",
- datetime.time: "time",
- datetime.timedelta: "timedelta",
- bool: "boolean"
-}
-
-if PY2:
- ODS_WRITE_FORMAT_COVERSION[unicode] = "string"
-
-VALUE_CONVERTERS = {
- "float": float_value,
- "date": date_value,
- "time": time_value,
- "timedelta": time_value,
- "boolean": boolean_value,
- "percentage": float_value,
- "currency": float_value
-}
-
-ODS_VALUE_CONVERTERS = {
- "date": ods_date_value,
- "time": ods_time_value,
- "boolean": ods_bool_value,
- "timedelta": ods_timedelta_value
-}
-
-
-VALUE_TOKEN = {
- "float": "value",
- "date": "date-value",
- "time": "time-value",
- "boolean": "boolean-value",
- "percentage": "value",
- "currency": "value",
- "timedelta": "time-value"
-}
diff --git a/pyexcel_ods/ods.py b/pyexcel_ods/ods.py
deleted file mode 100644
index 1f6ed67..0000000
--- a/pyexcel_ods/ods.py
+++ /dev/null
@@ -1,287 +0,0 @@
-# Copyright 2011 Marco Conti
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Thanks to grt for the fixes
-import sys
-import math
-
-from odf.table import TableRow, TableCell, Table
-from odf.text import P
-from odf.namespaces import OFFICENS
-from odf.opendocument import OpenDocumentSpreadsheet, load
-
-from pyexcel_io.book import BookReader, BookWriter
-from pyexcel_io.sheet import SheetReader, SheetWriter
-
-import pyexcel_ods.converter as converter
-
-PY2 = sys.version_info[0] == 2
-
-PY27_BELOW = PY2 and sys.version_info[1] < 7
-if PY27_BELOW:
- from ordereddict import OrderedDict
-else:
- from collections import OrderedDict
-
-
-class ODSSheet(SheetReader):
- """native ods sheet"""
- def __init__(self, sheet, auto_detect_int=True, **keywords):
- SheetReader.__init__(self, sheet, **keywords)
- self.auto_detect_int = auto_detect_int
- self.rows = self.native_sheet.getElementsByType(TableRow)
- self.cached_rows = {}
- self._number_of_rows = len(self.rows)
- self._number_of_columns = self._find_columns()
-
- def number_of_rows(self):
- return self._number_of_rows
-
- def number_of_columns(self):
- return self._number_of_columns
-
- @property
- def name(self):
- return self.native_sheet.getAttribute("name")
-
- def _cell_value(self, row, column):
- current_row = self.rows[row]
- cells = current_row.getElementsByType(TableCell)
- cell_value = None
- if str(row) in self.cached_rows:
- row_cache = self.cached_rows[str(row)]
- cell_value = row_cache[column]
- return cell_value
-
- try:
- cell = cells[column]
- cell_value = self._read_cell(cell)
- except IndexError:
- cell_value = None
- return cell_value
-
- def _read_row(self, cells):
- tmp_row = []
- for cell in cells:
- # repeated value?
- repeat = cell.getAttribute("numbercolumnsrepeated")
- cell_value = self._read_cell(cell)
- if repeat:
- number_of_repeat = int(repeat)
- tmp_row += [cell_value] * number_of_repeat
- else:
- tmp_row.append(cell_value)
- return tmp_row
-
- def _read_text_cell(self, cell):
- text_content = []
- paragraphs = cell.getElementsByType(P)
- # for each text node
- for paragraph in paragraphs:
- for node in paragraph.childNodes:
- if (node.nodeType == 3):
- if PY2:
- text_content.append(unicode(node.data))
- else:
- text_content.append(node.data)
- return '\n'.join(text_content)
-
- def _read_cell(self, cell):
- cell_type = cell.getAttrNS(OFFICENS, "value-type")
- value_token = converter.VALUE_TOKEN.get(cell_type, "value")
- ret = None
- if cell_type == "string":
- text_content = self._read_text_cell(cell)
- ret = text_content
- else:
- if cell_type in converter.VALUE_CONVERTERS:
- value = cell.getAttrNS(OFFICENS, value_token)
- n_value = converter.VALUE_CONVERTERS[cell_type](value)
- if cell_type == 'float' and self.auto_detect_int:
- if is_integer_ok_for_xl_float(n_value):
- n_value = int(n_value)
- ret = n_value
- else:
- text_content = self._read_text_cell(cell)
- ret = text_content
- return ret
-
- def _find_columns(self):
- max = -1
- for row_index, row in enumerate(self.rows):
- cells = row.getElementsByType(TableCell)
- if self._check_for_column_repeat(cells):
- row_cache = self._read_row(cells)
- self.cached_rows.update({str(row_index): row_cache})
- length = len(row_cache)
- else:
- length = len(cells)
- if length > max:
- max = length
- return max
-
- def _check_for_column_repeat(self, cells):
- found_repeated_columns = False
- for cell in cells:
- repeat = cell.getAttribute("numbercolumnsrepeated")
- if repeat:
- found_repeated_columns = True
- break
- return found_repeated_columns
-
-
-class ODSBook(BookReader):
- """read ods book"""
-
- def open(self, file_name, **keywords):
- """open ods file"""
- BookReader.open(self, file_name, **keywords)
- self._load_from_file()
-
- def open_stream(self, file_stream, **keywords):
- """open ods file stream"""
- BookReader.open_stream(self, file_stream, **keywords)
- self._load_from_memory()
-
- def read_sheet_by_name(self, sheet_name):
- """read a named sheet"""
- tables = self.native_book.spreadsheet.getElementsByType(Table)
- rets = [table for table in tables
- if table.getAttribute('name') == sheet_name]
- if len(rets) == 0:
- raise ValueError("%s cannot be found" % sheet_name)
- else:
- return self.read_sheet(rets[0])
-
- def read_sheet_by_index(self, sheet_index):
- """read a sheet at a specified index"""
- tables = self.native_book.spreadsheet.getElementsByType(Table)
- length = len(tables)
- if sheet_index < length:
- return self.read_sheet(tables[sheet_index])
- else:
- raise IndexError("Index %d of out bound %d" % (
- sheet_index, length))
-
- def read_all(self):
- """read all sheets"""
- result = OrderedDict()
- for sheet in self.native_book.spreadsheet.getElementsByType(Table):
- ods_sheet = ODSSheet(sheet, **self.keywords)
- result[ods_sheet.name] = ods_sheet.to_array()
-
- return result
-
- def read_sheet(self, native_sheet):
- """read one native sheet"""
- sheet = ODSSheet(native_sheet, **self.keywords)
- return {sheet.name: sheet.to_array()}
-
- def _load_from_memory(self):
- self.native_book = load(self.file_stream)
-
- def _load_from_file(self):
- self.native_book = load(self.file_name)
- pass
-
-
-class ODSSheetWriter(SheetWriter):
- """
- ODS sheet writer
- """
- def set_sheet_name(self, name):
- """initialize the native table"""
- self.native_sheet = Table(name=name)
-
- def set_size(self, size):
- """not used in this class but used in ods3"""
- pass
-
- def write_cell(self, row, cell):
- """write a native cell"""
- cell_to_be_written = TableCell()
- cell_type = type(cell)
- cell_odf_type = converter.ODS_WRITE_FORMAT_COVERSION.get(
- cell_type, "string")
- cell_to_be_written.setAttrNS(OFFICENS, "value-type", cell_odf_type)
- cell_odf_value_token = converter.VALUE_TOKEN.get(
- cell_odf_type, "value")
- converter_func = converter.ODS_VALUE_CONVERTERS.get(
- cell_odf_type, None)
- if converter_func:
- cell = converter_func(cell)
- if cell_odf_type != 'string':
- cell_to_be_written.setAttrNS(OFFICENS, cell_odf_value_token, cell)
- cell_to_be_written.addElement(P(text=cell))
- else:
- lines = cell.split('\n')
- for line in lines:
- cell_to_be_written.addElement(P(text=line))
- row.addElement(cell_to_be_written)
-
- def write_row(self, array):
- """
- write a row into the file
- """
- row = TableRow()
- self.native_sheet.addElement(row)
- for cell in array:
- self.write_cell(row, cell)
-
- def close(self):
- """
- This call writes file
-
- """
- self.native_book.spreadsheet.addElement(self.native_sheet)
-
-
-class ODSWriter(BookWriter):
- """
- open document spreadsheet writer
-
- """
- def __init__(self):
- BookWriter.__init__(self)
- self.native_book = OpenDocumentSpreadsheet()
-
- def create_sheet(self, name):
- """
- write a row into the file
- """
- return ODSSheetWriter(self.native_book, None, name)
-
- def close(self):
- """
- This call writes file
-
- """
- self.native_book.write(self.file_alike_object)
-
-
-def is_integer_ok_for_xl_float(value):
- """check if a float had zero value in digits"""
- return value == math.floor(value)
-
-
-_ods_registry = {
- "file_type": "ods",
- "reader": ODSBook,
- "writer": ODSWriter,
- "stream_type": "binary",
- "mime_type": "application/vnd.oasis.opendocument.spreadsheet",
- "library": "pyexcel-ods"
-}
-
-exports = (_ods_registry,)
diff --git a/pyexcel_ods/odsr.py b/pyexcel_ods/odsr.py
new file mode 100644
index 0000000..3559ffe
--- /dev/null
+++ b/pyexcel_ods/odsr.py
@@ -0,0 +1,160 @@
+"""
+ pyexcel_ods.odsr
+ ~~~~~~~~~~~~~~~~~~~~~
+
+ ods reader
+
+ :copyright: (c) 2014-2017 by Onni Software Ltd.
+ :license: New BSD License, see LICENSE for more details
+"""
+# Copyright 2011 Marco Conti
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import pyexcel_io.service as service
+from odf.namespaces import OFFICENS
+from odf.opendocument import load
+from odf.table import Table, TableCell, TableRow
+
+# Thanks to grt for the fixes
+from odf.teletype import extractText
+from odf.text import P
+from pyexcel_io._compact import OrderedDict
+from pyexcel_io.book import BookReader
+from pyexcel_io.sheet import SheetReader
+
+
+class ODSSheet(SheetReader):
+ """native ods sheet"""
+
+ def __init__(self, sheet, auto_detect_int=True, **keywords):
+ SheetReader.__init__(self, sheet, **keywords)
+ self.__auto_detect_int = auto_detect_int
+
+ @property
+ def name(self):
+ return self._native_sheet.getAttribute("name")
+
+ def row_iterator(self):
+ return self._native_sheet.getElementsByType(TableRow)
+
+ def column_iterator(self, row):
+ cells = row.getElementsByType(TableCell)
+ for cell in cells:
+ repeat = cell.getAttribute("numbercolumnsrepeated")
+ cell_value = self.__read_cell(cell)
+ if repeat:
+ number_of_repeat = int(repeat)
+ for i in range(number_of_repeat):
+ yield cell_value
+ else:
+ yield cell_value
+
+ def __read_cell(self, cell):
+ cell_type = cell.getAttrNS(OFFICENS, "value-type")
+ value_token = service.VALUE_TOKEN.get(cell_type, "value")
+ ret = None
+ if cell_type == "string":
+ text_content = self.__read_text_cell(cell)
+ ret = text_content
+ elif cell_type == "currency":
+ value = cell.getAttrNS(OFFICENS, value_token)
+ currency = cell.getAttrNS(OFFICENS, cell_type)
+ if currency:
+ ret = value + " " + currency
+ else:
+ ret = value
+ else:
+ if cell_type in service.VALUE_CONVERTERS:
+ value = cell.getAttrNS(OFFICENS, value_token)
+ n_value = service.VALUE_CONVERTERS[cell_type](value)
+ if cell_type == "float" and self.__auto_detect_int:
+ if service.has_no_digits_in_float(n_value):
+ n_value = int(n_value)
+ ret = n_value
+ else:
+ text_content = self.__read_text_cell(cell)
+ ret = text_content
+ return ret
+
+ def __read_text_cell(self, cell):
+ text_content = []
+ paragraphs = cell.getElementsByType(P)
+ # for each text node
+ for paragraph in paragraphs:
+ name_space, tag = paragraph.parentNode.qname
+ if tag != str("annotation"):
+ data = extractText(paragraph)
+ text_content.append(data)
+ return "\n".join(text_content)
+
+
+class ODSBook(BookReader):
+ """read ods book"""
+
+ def open(self, file_name, **keywords):
+ """open ods file"""
+ BookReader.open(self, file_name, **keywords)
+ self._load_from_file()
+
+ def open_stream(self, file_stream, **keywords):
+ """open ods file stream"""
+ BookReader.open_stream(self, file_stream, **keywords)
+ self._load_from_memory()
+
+ def read_sheet_by_name(self, sheet_name):
+ """read a named sheet"""
+ tables = self._native_book.spreadsheet.getElementsByType(Table)
+ rets = [
+ table
+ for table in tables
+ if table.getAttribute("name") == sheet_name
+ ]
+ if len(rets) == 0:
+ raise ValueError("%s cannot be found" % sheet_name)
+ else:
+ return self.read_sheet(rets[0])
+
+ def read_sheet_by_index(self, sheet_index):
+ """read a sheet at a specified index"""
+ tables = self._native_book.spreadsheet.getElementsByType(Table)
+ length = len(tables)
+ if sheet_index < length:
+ return self.read_sheet(tables[sheet_index])
+ else:
+ raise IndexError(
+ "Index %d of out bound %d" % (sheet_index, length)
+ )
+
+ def read_all(self):
+ """read all sheets"""
+ result = OrderedDict()
+ for sheet in self._native_book.spreadsheet.getElementsByType(Table):
+ ods_sheet = ODSSheet(sheet, **self._keywords)
+ result[ods_sheet.name] = ods_sheet.to_array()
+
+ return result
+
+ def read_sheet(self, native_sheet):
+ """read one native sheet"""
+ sheet = ODSSheet(native_sheet, **self._keywords)
+ return {sheet.name: sheet.to_array()}
+
+ def close(self):
+ self._native_book = None
+
+ def _load_from_memory(self):
+ self._native_book = load(self._file_stream)
+
+ def _load_from_file(self):
+ self._native_book = load(self._file_name)
diff --git a/pyexcel_ods/odsw.py b/pyexcel_ods/odsw.py
new file mode 100644
index 0000000..c911517
--- /dev/null
+++ b/pyexcel_ods/odsw.py
@@ -0,0 +1,102 @@
+"""
+ pyexcel_ods.odsw
+ ~~~~~~~~~~~~~~~~~~~~~
+
+ ods writer
+
+ :copyright: (c) 2014-2017 by Onni Software Ltd.
+ :license: New BSD License, see LICENSE for more details
+"""
+import sys
+
+import pyexcel_io.service as converter
+from odf.namespaces import OFFICENS
+from odf.opendocument import OpenDocumentSpreadsheet
+from odf.table import Table, TableCell, TableRow
+from odf.text import P
+from pyexcel_io.book import BookWriter
+from pyexcel_io.sheet import SheetWriter
+
+PY2 = sys.version_info[0] == 2
+
+PY27_BELOW = PY2 and sys.version_info[1] < 7
+
+
+class ODSSheetWriter(SheetWriter):
+ """
+ ODS sheet writer
+ """
+
+ def set_sheet_name(self, name):
+ """initialize the native table"""
+ self._native_sheet = Table(name=name)
+
+ def set_size(self, size):
+ """not used in this class but used in ods3"""
+ pass
+
+ def write_cell(self, row, cell):
+ """write a native cell"""
+ cell_to_be_written = TableCell()
+ cell_type = type(cell)
+ cell_odf_type = converter.ODS_WRITE_FORMAT_COVERSION.get(
+ cell_type, "string"
+ )
+ cell_to_be_written.setAttrNS(OFFICENS, "value-type", cell_odf_type)
+ cell_odf_value_token = converter.VALUE_TOKEN.get(
+ cell_odf_type, "value"
+ )
+ converter_func = converter.ODS_VALUE_CONVERTERS.get(
+ cell_odf_type, None
+ )
+ if converter_func:
+ cell = converter_func(cell)
+ if cell_odf_type != "string":
+ cell_to_be_written.setAttrNS(OFFICENS, cell_odf_value_token, cell)
+ cell_to_be_written.addElement(P(text=cell))
+ else:
+ lines = cell.split("\n")
+ for line in lines:
+ cell_to_be_written.addElement(P(text=line))
+ row.addElement(cell_to_be_written)
+
+ def write_row(self, array):
+ """
+ write a row into the file
+ """
+ row = TableRow()
+ self._native_sheet.addElement(row)
+ for cell in array:
+ self.write_cell(row, cell)
+
+ def close(self):
+ """
+ This call writes file
+
+ """
+ self._native_book.spreadsheet.addElement(self._native_sheet)
+
+
+class ODSWriter(BookWriter):
+ """
+ open document spreadsheet writer
+
+ """
+
+ def __init__(self):
+ BookWriter.__init__(self)
+ self._native_book = OpenDocumentSpreadsheet()
+
+ def create_sheet(self, name):
+ """
+ write a row into the file
+ """
+ return ODSSheetWriter(self._native_book, None, name)
+
+ def close(self):
+ """
+ This call writes file
+
+ """
+ self._native_book.write(self._file_alike_object)
+ self._native_book = None
diff --git a/requirements.txt b/requirements.txt
index 59763ff..8bb3c46 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,2 @@
-pyexcel-io>=0.2.0
-odfpy>=1.3.3
+pyexcel-io>=0.5.10
+odfpy==1.3.5
diff --git a/rnd_requirements.txt b/rnd_requirements.txt
deleted file mode 100644
index cabc29e..0000000
--- a/rnd_requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-https://github.com/pyexcel/pyexcel-io/archive/master.zip
-https://github.com/pyexcel/pyexcel-xls/archive/master.zip
-https://github.com/pyexcel/pyexcel/archive/master.zip
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..8c8abfd
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,4 @@
+[metadata]
+description-file = README.rst
+[bdist_wheel]
+universal = 1
diff --git a/setup.py b/setup.py
index fb9b466..4eb9252 100644
--- a/setup.py
+++ b/setup.py
@@ -1,59 +1,159 @@
-try:
- from setuptools import setup, find_packages
-except ImportError:
- from ez_setup import use_setuptools
- use_setuptools()
- from setuptools import setup, find_packages
+#!/usr/bin/env python3
+
+import codecs
+# Template by pypi-mobans
+import os
+import sys
+from shutil import rmtree
+
+from setuptools import Command, find_packages, setup
NAME = 'pyexcel-ods'
AUTHOR = 'C.W.'
-VERSION = '0.2.1'
-EMAIL = 'wangc_2011 (at) hotmail.com'
+VERSION = '0.5.4'
+EMAIL = 'wangc_2011@hotmail.com'
LICENSE = 'New BSD'
-PACKAGES = find_packages(exclude=['ez_setup', 'examples', 'tests'])
DESCRIPTION = (
- 'A wrapper library to read, manipulate and write data in ods format' +
- ''
+ 'A wrapper library to read, manipulate and write data in ods format'
)
+URL = 'https://github.com/pyexcel/pyexcel-ods'
+DOWNLOAD_URL = '%s/archive/0.5.4.tar.gz' % URL
+FILES = ['README.rst', 'CHANGELOG.rst']
KEYWORDS = [
- 'excel',
'python',
- 'pyexcel',
+]
+
+CLASSIFIERS = [
+ 'Topic :: Software Development :: Libraries',
+ 'Programming Language :: Python',
+ 'Intended Audience :: Developers',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: 3.4',
+ 'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: 3.6',
]
INSTALL_REQUIRES = [
- 'pyexcel-io>=0.2.0',
- 'odfpy>=1.3.3',
+ 'pyexcel-io>=0.5.10',
+ 'odfpy==1.3.5',
]
+SETUP_COMMANDS = {}
+PACKAGES = find_packages(exclude=['ez_setup', 'examples', 'tests'])
EXTRAS_REQUIRE = {
}
+# You do not need to read beyond this line
+PUBLISH_COMMAND = '{0} setup.py sdist bdist_wheel upload -r pypi'.format(
+ sys.executable)
+GS_COMMAND = ('gs pyexcel-ods v0.5.4 ' +
+ "Find 0.5.4 in changelog for more details")
+NO_GS_MESSAGE = ('Automatic github release is disabled. ' +
+ 'Please install gease to enable it.')
+UPLOAD_FAILED_MSG = (
+ 'Upload failed. please run "%s" yourself.' % PUBLISH_COMMAND)
+HERE = os.path.abspath(os.path.dirname(__file__))
-CLASSIFIERS = [
- 'Topic :: Office/Business',
- 'Topic :: Utilities',
- 'Topic :: Software Development :: Libraries',
- 'Programming Language :: Python',
- 'License :: OSI Approved :: BSD License',
- 'Intended Audience :: Developers',
- 'Programming Language :: Python :: 2.6',
- 'Programming Language :: Python :: 2.7'
-]
+
+class PublishCommand(Command):
+ """Support setup.py upload."""
+
+ description = 'Build and publish the package on github and pypi'
+ user_options = []
+
+ @staticmethod
+ def status(s):
+ """Prints things in bold."""
+ print('\033[1m{0}\033[0m'.format(s))
+
+ def initialize_options(self):
+ pass
+
+ def finalize_options(self):
+ pass
+
+ def run(self):
+ try:
+ self.status('Removing previous builds...')
+ rmtree(os.path.join(HERE, 'dist'))
+ rmtree(os.path.join(HERE, 'build'))
+ rmtree(os.path.join(HERE, 'pyexcel_ods.egg-info'))
+ except OSError:
+ pass
+
+ self.status('Building Source and Wheel (universal) distribution...')
+ run_status = True
+ if has_gease():
+ run_status = os.system(GS_COMMAND) == 0
+ else:
+ self.status(NO_GS_MESSAGE)
+ if run_status:
+ if os.system(PUBLISH_COMMAND) != 0:
+ self.status(UPLOAD_FAILED_MSG % PUBLISH_COMMAND)
+
+ sys.exit()
+
+
+SETUP_COMMANDS.update({
+ 'publish': PublishCommand
+})
+
+
+def has_gease():
+ """
+ test if github release command is installed
+
+ visit http://github.com/moremoban/gease for more info
+ """
+ try:
+ import gease # noqa
+ return True
+ except ImportError:
+ return False
def read_files(*files):
"""Read files into setup"""
text = ""
for single_file in files:
- text = text + read(single_file) + "\n"
+ content = read(single_file)
+ text = text + content + "\n"
return text
def read(afile):
"""Read a file into setup"""
- with open(afile, 'r') as opened_file:
- return opened_file.read()
+ the_relative_file = os.path.join(HERE, afile)
+ with codecs.open(the_relative_file, 'r', 'utf-8') as opened_file:
+ content = filter_out_test_code(opened_file)
+ content = "".join(list(content))
+ return content
+
+
+def filter_out_test_code(file_handle):
+ found_test_code = False
+ for line in file_handle.readlines():
+ if line.startswith('.. testcode:'):
+ found_test_code = True
+ continue
+ if found_test_code is True:
+ if line.startswith(' '):
+ continue
+ else:
+ empty_line = line.strip()
+ if len(empty_line) == 0:
+ continue
+ else:
+ found_test_code = False
+ yield line
+ else:
+ for keyword in ['|version|', '|today|']:
+ if keyword in line:
+ break
+ else:
+ yield line
if __name__ == '__main__':
@@ -63,14 +163,17 @@ if __name__ == '__main__':
version=VERSION,
author_email=EMAIL,
description=DESCRIPTION,
- install_requires=INSTALL_REQUIRES,
+ url=URL,
+ download_url=DOWNLOAD_URL,
+ long_description=read_files(*FILES),
+ license=LICENSE,
keywords=KEYWORDS,
extras_require=EXTRAS_REQUIRE,
+ tests_require=['nose'],
+ install_requires=INSTALL_REQUIRES,
packages=PACKAGES,
include_package_data=True,
- long_description=read_files('README.rst', 'CHANGELOG.rst'),
zip_safe=False,
- tests_require=['nose'],
- license=LICENSE,
- classifiers=CLASSIFIERS
+ classifiers=CLASSIFIERS,
+ cmdclass=SETUP_COMMANDS
)
diff --git a/test.bat b/test.bat
index dbacc63..ad920bb 100644
--- a/test.bat
+++ b/test.bat
@@ -1,3 +1,2 @@
-
pip freeze
-nosetests --with-cov --cover-package pyexcel_ods --cover-package tests --with-doctest --doctest-extension=.rst tests README.rst pyexcel_ods && flake8 . --exclude=.moban.d --builtins=unicode,xrange,long
+nosetests --with-coverage --cover-package pyexcel_ods --cover-package tests tests --with-doctest --doctest-extension=.rst README.rst docs/source pyexcel_ods && flake8 . --exclude=.moban.d,docs --builtins=unicode,xrange,long
diff --git a/test.sh b/test.sh
new file mode 100644
index 0000000..ad920bb
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,2 @@
+pip freeze
+nosetests --with-coverage --cover-package pyexcel_ods --cover-package tests tests --with-doctest --doctest-extension=.rst README.rst docs/source pyexcel_ods && flake8 . --exclude=.moban.d,docs --builtins=unicode,xrange,long
diff --git a/tests/base.py b/tests/base.py
index 974de35..c312a6a 100644
--- a/tests/base.py
+++ b/tests/base.py
@@ -1,11 +1,12 @@
-import os
+import datetime # noqa
+import os # noqa
+
import pyexcel
-from nose.tools import raises
-import datetime
+from nose.tools import eq_, raises # noqa
def create_sample_file1(file):
- data = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 1.1, 1]
+ data = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 1.1, 1]
table = []
table.append(data[:4])
table.append(data[4:8])
@@ -17,17 +18,17 @@ class PyexcelHatWriterBase:
"""
Abstract functional test for hat writers
"""
+
content = {
"X": [1, 2, 3, 4, 5],
"Y": [6, 7, 8, 9, 10],
- "Z": [11, 12, 13, 14, 15]
+ "Z": [11, 12, 13, 14, 15],
}
def test_series_table(self):
pyexcel.save_as(adict=self.content, dest_file_name=self.testfile)
r = pyexcel.get_sheet(file_name=self.testfile, name_columns_by_row=0)
- actual = pyexcel.utils.to_dict(r)
- assert actual == self.content
+ eq_(r.dict, self.content)
class PyexcelWriterBase:
@@ -37,11 +38,12 @@ class PyexcelWriterBase:
testfile and testfile2 have to be initialized before
it is used for testing
"""
+
content = [
[1, 2, 3, 4, 5],
[1, 2, 3, 4, 5],
[1, 2, 3, 4, 5],
- [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5],
]
def _create_a_file(self, file):
@@ -50,12 +52,11 @@ class PyexcelWriterBase:
def test_write_array(self):
self._create_a_file(self.testfile)
r = pyexcel.get_sheet(file_name=self.testfile)
- actual = pyexcel.utils.to_array(r.rows())
+ actual = list(r.rows())
assert actual == self.content
class PyexcelMultipleSheetBase:
-
def _write_test_file(self, filename):
pyexcel.save_book_as(bookdict=self.content, dest_file_name=filename)
@@ -74,85 +75,66 @@ class PyexcelMultipleSheetBase:
def test_reading_through_sheets(self):
b = pyexcel.BookReader(self.testfile)
- data = pyexcel.utils.to_array(b["Sheet1"].rows())
+ data = list(b["Sheet1"].rows())
expected = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]
assert data == expected
- data = pyexcel.utils.to_array(b["Sheet2"].rows())
+ data = list(b["Sheet2"].rows())
expected = [[4, 4, 4, 4], [5, 5, 5, 5], [6, 6, 6, 6]]
assert data == expected
- data = pyexcel.utils.to_array(b["Sheet3"].rows())
- expected = [[u'X', u'Y', u'Z'], [1, 4, 7], [2, 5, 8], [3, 6, 9]]
+ data = list(b["Sheet3"].rows())
+ expected = [[u"X", u"Y", u"Z"], [1, 4, 7], [2, 5, 8], [3, 6, 9]]
assert data == expected
sheet3 = b["Sheet3"]
sheet3.name_columns_by_row(0)
- data = pyexcel.utils.to_array(b["Sheet3"].rows())
+ data = list(b["Sheet3"].rows())
expected = [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
assert data == expected
- def test_iterate_through_sheets(self):
- b = pyexcel.BookReader(self.testfile)
- for s in b:
- data = pyexcel.utils.to_array(s)
- assert self.content[s.name] == data
- si = pyexcel.sheets.iterators.SheetIterator(b)
- for s in si:
- data = pyexcel.utils.to_array(s)
- assert self.content[s.name] == data
-
- def test_random_access_operator(self):
- r = pyexcel.BookReader(self.testfile)
- value = r["Sheet1"][0, 1]
- assert value == 1
- value = r["Sheet3"][0, 1]
- assert value == 'Y'
- r["Sheet3"].name_columns_by_row(0)
- assert r["Sheet3"][0, 1] == 4
-
class ODSCellTypes:
-
def test_formats(self):
# date formats
date_format = "%d/%m/%Y"
- assert self.data["Sheet1"][0][0] == "Date"
- assert self.data["Sheet1"][1][0].strftime(date_format) == "11/11/2014"
- assert self.data["Sheet1"][2][0].strftime(date_format) == "01/01/2001"
- assert self.data["Sheet1"][3][0] == ""
+ eq_(self.data["Sheet1"][0][0], "Date")
+ eq_(self.data["Sheet1"][1][0].strftime(date_format), "11/11/2014")
+ eq_(self.data["Sheet1"][2][0].strftime(date_format), "01/01/2001")
+ eq_(self.data["Sheet1"][3][0], "")
# time formats
time_format = "%S:%M:%H"
- assert self.data["Sheet1"][0][1] == "Time"
- assert self.data["Sheet1"][1][1].strftime(time_format) == "12:12:11"
- assert self.data["Sheet1"][2][1].strftime(time_format) == "12:00:00"
- assert self.data["Sheet1"][3][1] == 0
- assert self.data["Sheet1"][4][1] == datetime.timedelta(hours=27,
- minutes=17,
- seconds=54)
- assert self.data["Sheet1"][5][1] == "Other"
+ eq_(self.data["Sheet1"][0][1], "Time")
+ eq_(self.data["Sheet1"][1][1].strftime(time_format), "12:12:11")
+ eq_(self.data["Sheet1"][2][1].strftime(time_format), "12:00:00")
+ eq_(self.data["Sheet1"][3][1], 0)
+ eq_(
+ self.data["Sheet1"][4][1],
+ datetime.timedelta(hours=27, minutes=17, seconds=54),
+ )
+ eq_(self.data["Sheet1"][5][1], "Other")
# boolean
- assert self.data["Sheet1"][0][2] == "Boolean"
- assert self.data["Sheet1"][1][2] is True
- assert self.data["Sheet1"][2][2] is False
+ eq_(self.data["Sheet1"][0][2], "Boolean")
+ eq_(self.data["Sheet1"][1][2], True)
+ eq_(self.data["Sheet1"][2][2], False)
# Float
- assert self.data["Sheet1"][0][3] == "Float"
- assert self.data["Sheet1"][1][3] == 11.11
+ eq_(self.data["Sheet1"][0][3], "Float")
+ eq_(self.data["Sheet1"][1][3], 11.11)
# Currency
- assert self.data["Sheet1"][0][4] == "Currency"
- assert self.data["Sheet1"][1][4] == 1
- assert self.data["Sheet1"][2][4] == -10000
+ eq_(self.data["Sheet1"][0][4], "Currency")
+ eq_(self.data["Sheet1"][1][4], "1 GBP")
+ eq_(self.data["Sheet1"][2][4], "-10000 GBP")
# Percentage
- assert self.data["Sheet1"][0][5] == "Percentage"
- assert self.data["Sheet1"][1][5] == 2
+ eq_(self.data["Sheet1"][0][5], "Percentage")
+ eq_(self.data["Sheet1"][1][5], 2)
# int
- assert self.data["Sheet1"][0][6] == "Int"
- assert self.data["Sheet1"][1][6] == 3
- assert self.data["Sheet1"][4][6] == 11
+ eq_(self.data["Sheet1"][0][6], "Int")
+ eq_(self.data["Sheet1"][1][6], 3)
+ eq_(self.data["Sheet1"][4][6], 11)
# Scientifed not supported
- assert self.data["Sheet1"][1][7] == 100000
+ eq_(self.data["Sheet1"][1][7], 100000)
# Fraction
- assert self.data["Sheet1"][1][8] == 1.25
+ eq_(self.data["Sheet1"][1][8], 1.25)
# Text
- assert self.data["Sheet1"][1][9] == "abc"
+ eq_(self.data["Sheet1"][1][9], "abc")
@raises(IndexError)
def test_no_excessive_trailing_columns(self):
- assert self.data["Sheet1"][2][6] == ""
+ eq_(self.data["Sheet1"][2][6], "")
diff --git a/tests/fixtures/12_day_as_time.ods b/tests/fixtures/12_day_as_time.ods
new file mode 100644
index 0000000..b7f7197
--- /dev/null
+++ b/tests/fixtures/12_day_as_time.ods
Binary files differ
diff --git a/tests/fixtures/comment-in-cell.ods b/tests/fixtures/comment-in-cell.ods
new file mode 100644
index 0000000..b87ce6e
--- /dev/null
+++ b/tests/fixtures/comment-in-cell.ods
Binary files differ
diff --git a/tests/fixtures/issue_27.ods b/tests/fixtures/issue_27.ods
new file mode 100644
index 0000000..86296d2
--- /dev/null
+++ b/tests/fixtures/issue_27.ods
Binary files differ
diff --git a/tests/fixtures/issue_61.ods b/tests/fixtures/issue_61.ods
new file mode 100644
index 0000000..34c917a
--- /dev/null
+++ b/tests/fixtures/issue_61.ods
Binary files differ
diff --git a/tests/fixtures/multilineods.ods b/tests/fixtures/multilineods.ods
index c03f645..3cf556b 100644
--- a/tests/fixtures/multilineods.ods
+++ b/tests/fixtures/multilineods.ods
Binary files differ
diff --git a/tests/fixtures/pyexcel_81_ods_19.ods b/tests/fixtures/pyexcel_81_ods_19.ods
new file mode 100755
index 0000000..a1954e0
--- /dev/null
+++ b/tests/fixtures/pyexcel_81_ods_19.ods
Binary files differ
diff --git a/tests/fixtures/white_space.ods b/tests/fixtures/white_space.ods
new file mode 100644
index 0000000..8e891c2
--- /dev/null
+++ b/tests/fixtures/white_space.ods
Binary files differ
diff --git a/tests/requirements.txt b/tests/requirements.txt
index f13a9b5..7a755ef 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -1,6 +1,11 @@
nose
+mock;python_version<"3"
codecov
coverage
flake8
+moban
+black;python_version>="3.6"
+isort;python_version>="3.6"
+psutil
pyexcel
pyexcel-xls
diff --git a/tests/test_bug_fixes.py b/tests/test_bug_fixes.py
index 67b515f..128934a 100644
--- a/tests/test_bug_fixes.py
+++ b/tests/test_bug_fixes.py
@@ -1,13 +1,21 @@
#!/usr/bin/python
# -*- encoding: utf-8 -*-
import os
+
+import psutil
+import pyexcel as pe
+from nose import SkipTest
+from nose.tools import eq_, raises
+from pyexcel_io.exceptions import IntegerAccuracyLossError
+
from pyexcel_ods import get_data, save_data
-from nose.tools import raises, eq_
+
+IN_TRAVIS = "TRAVIS" in os.environ
def test_bug_fix_for_issue_1():
- data = get_data(os.path.join("tests", "fixtures", "repeated.ods"))
- eq_(data["Sheet1"], [['repeated', 'repeated', 'repeated', 'repeated']])
+ data = get_data(get_fixtures("repeated.ods"))
+ eq_(data["Sheet1"], [["repeated", "repeated", "repeated", "repeated"]])
def test_bug_fix_for_issue_2():
@@ -16,31 +24,13 @@ def test_bug_fix_for_issue_2():
data.update({"Sheet 2": [[u"row 1", u"Héllô!", u"HolÁ!"]]})
save_data("your_file.ods", data)
new_data = get_data("your_file.ods")
- assert new_data["Sheet 2"] == [[u'row 1', u'H\xe9ll\xf4!', u'Hol\xc1!']]
-
-
-def test_date_util_parse():
- from pyexcel_ods.converter import date_value
- value = "2015-08-17T19:20:00"
- d = date_value(value)
- assert d.strftime("%Y-%m-%dT%H:%M:%S") == "2015-08-17T19:20:00"
- value = "2015-08-17"
- d = date_value(value)
- assert d.strftime("%Y-%m-%d") == "2015-08-17"
- value = "2015-08-17T19:20:59.999999"
- d = date_value(value)
- assert d.strftime("%Y-%m-%dT%H:%M:%S") == "2015-08-17T19:20:59"
- value = "2015-08-17T19:20:59.99999"
- d = date_value(value)
- assert d.strftime("%Y-%m-%dT%H:%M:%S") == "2015-08-17T19:20:59"
- value = "2015-08-17T19:20:59.999999999999999"
- d = date_value(value)
- assert d.strftime("%Y-%m-%dT%H:%M:%S") == "2015-08-17T19:20:59"
+ assert new_data["Sheet 2"] == [[u"row 1", u"H\xe9ll\xf4!", u"Hol\xc1!"]]
@raises(Exception)
def test_invalid_date():
from pyexcel_ods.ods import date_value
+
value = "2015-08-"
date_value(value)
@@ -48,31 +38,127 @@ def test_invalid_date():
@raises(Exception)
def test_fake_date_time_10():
from pyexcel_ods.ods import date_value
+
date_value("1234567890")
@raises(Exception)
def test_fake_date_time_19():
from pyexcel_ods.ods import date_value
+
date_value("1234567890123456789")
@raises(Exception)
def test_fake_date_time_20():
from pyexcel_ods.ods import date_value
+
date_value("12345678901234567890")
def test_issue_13():
test_file = "test_issue_13.ods"
- data = [
- [1, 2],
- [],
- [],
- [],
- [3, 4]
- ]
+ data = [[1, 2], [], [], [], [3, 4]]
save_data(test_file, {test_file: data})
written_data = get_data(test_file, skip_empty_rows=False)
eq_(data, written_data[test_file])
os.unlink(test_file)
+
+
+def test_issue_14():
+ # pyexcel issue 61
+ test_file = "issue_61.ods"
+ data = get_data(get_fixtures(test_file), skip_empty_rows=True)
+ eq_(data["S-LMC"], [[u"aaa"], [0]])
+
+
+def test_issue_6():
+ test_file = "12_day_as_time.ods"
+ data = get_data(get_fixtures(test_file), skip_empty_rows=True)
+ eq_(data["Sheet1"][0][0].days, 12)
+
+
+def test_issue_19():
+ test_file = "pyexcel_81_ods_19.ods"
+ data = get_data(get_fixtures(test_file), skip_empty_rows=True)
+ eq_(data["product.template"][1][1], "PRODUCT NAME PMP")
+
+
+def test_issue_83_ods_file_handle():
+ # this proves that odfpy
+ # does not leave a file handle open at all
+ proc = psutil.Process()
+ test_file = get_fixtures("issue_61.ods")
+ open_files_l1 = proc.open_files()
+
+ # start with a csv file
+ data = pe.iget_array(file_name=test_file, library="pyexcel-ods")
+ open_files_l2 = proc.open_files()
+ delta = len(open_files_l2) - len(open_files_l1)
+ # cannot catch open file handle
+ assert delta == 0
+
+ # now the file handle get opened when we run through
+ # the generator
+ list(data)
+ open_files_l3 = proc.open_files()
+ delta = len(open_files_l3) - len(open_files_l1)
+ # cannot catch open file handle
+ assert delta == 0
+
+ # free the fish
+ pe.free_resources()
+ open_files_l4 = proc.open_files()
+ # this confirms that no more open file handle
+ eq_(open_files_l1, open_files_l4)
+
+
+def test_pr_22():
+ test_file = get_fixtures("white_space.ods")
+ data = get_data(test_file)
+ eq_(data["Sheet1"][0][0], "paragraph with tab(\t), space, \nnew line")
+
+
+def test_issue_23():
+ if not IN_TRAVIS:
+ raise SkipTest()
+ pe.get_book(
+ url=(
+ "https://github.com/pyexcel/pyexcel-ods/"
+ + "raw/master/tests/fixtures/white_space.ods"
+ )
+ )
+
+
+def test_issue_24():
+ test_file = get_fixtures("comment-in-cell.ods")
+ data = get_data(test_file)
+ eq_(data["Sheet1"], [["test"]])
+
+
+def test_issue_27():
+ test_file = get_fixtures("issue_27.ods")
+ data = get_data(test_file, skip_empty_rows=True)
+ eq_(data["VGPMX"], [["", "Cost Basis", "0"]])
+
+
+def test_issue_30():
+ test_file = "issue_30.ods"
+ sheet = pe.Sheet()
+ sheet[0, 0] = 999999999999999
+ sheet.save_as(test_file)
+ sheet2 = pe.get_sheet(file_name=test_file)
+ eq_(sheet[0, 0], sheet2[0, 0])
+ os.unlink(test_file)
+
+
+@raises(IntegerAccuracyLossError)
+def test_issue_30_precision_loss():
+ test_file = "issue_30_2.ods"
+ sheet = pe.Sheet()
+ sheet[0, 0] = 9999999999999999
+ sheet.save_as(test_file)
+
+
+def get_fixtures(filename):
+ return os.path.join("tests", "fixtures", filename)
diff --git a/tests/test_filter.py b/tests/test_filter.py
index b74bc4c..67d8876 100644
--- a/tests/test_filter.py
+++ b/tests/test_filter.py
@@ -1,7 +1,7 @@
import os
-from pyexcel_io import get_data, save_data
from nose.tools import eq_
+from pyexcel_io import get_data, save_data
class TestFilter:
@@ -13,49 +13,58 @@ class TestFilter:
[3, 23, 33],
[4, 24, 34],
[5, 25, 35],
- [6, 26, 36]
+ [6, 26, 36],
]
save_data(self.test_file, sample)
self.sheet_name = "pyexcel_sheet1"
def test_filter_row(self):
- filtered_data = get_data(self.test_file, start_row=3,
- library="pyexcel-ods")
+ filtered_data = get_data(
+ self.test_file, start_row=3, library="pyexcel-ods"
+ )
expected = [[4, 24, 34], [5, 25, 35], [6, 26, 36]]
eq_(filtered_data[self.sheet_name], expected)
def test_filter_row_2(self):
- filtered_data = get_data(self.test_file, start_row=3, row_limit=1,
- library="pyexcel-ods")
+ filtered_data = get_data(
+ self.test_file, start_row=3, row_limit=1, library="pyexcel-ods"
+ )
expected = [[4, 24, 34]]
eq_(filtered_data[self.sheet_name], expected)
def test_filter_column(self):
- filtered_data = get_data(self.test_file, start_column=1,
- library="pyexcel-ods")
- expected = [[21, 31], [22, 32], [23, 33],
- [24, 34], [25, 35], [26, 36]]
+ filtered_data = get_data(
+ self.test_file, start_column=1, library="pyexcel-ods"
+ )
+ expected = [[21, 31], [22, 32], [23, 33], [24, 34], [25, 35], [26, 36]]
eq_(filtered_data[self.sheet_name], expected)
def test_filter_column_2(self):
- filtered_data = get_data(self.test_file,
- start_column=1, column_limit=1,
- library="pyexcel-ods")
+ filtered_data = get_data(
+ self.test_file,
+ start_column=1,
+ column_limit=1,
+ library="pyexcel-ods",
+ )
expected = [[21], [22], [23], [24], [25], [26]]
eq_(filtered_data[self.sheet_name], expected)
def test_filter_both_ways(self):
- filtered_data = get_data(self.test_file,
- start_column=1, start_row=3,
- library="pyexcel-ods")
+ filtered_data = get_data(
+ self.test_file, start_column=1, start_row=3, library="pyexcel-ods"
+ )
expected = [[24, 34], [25, 35], [26, 36]]
eq_(filtered_data[self.sheet_name], expected)
def test_filter_both_ways_2(self):
- filtered_data = get_data(self.test_file,
- start_column=1, column_limit=1,
- start_row=3, row_limit=1,
- library="pyexcel-ods")
+ filtered_data = get_data(
+ self.test_file,
+ start_column=1,
+ column_limit=1,
+ start_row=3,
+ row_limit=1,
+ library="pyexcel-ods",
+ )
expected = [[24]]
eq_(filtered_data[self.sheet_name], expected)
diff --git a/tests/test_formatters.py b/tests/test_formatters.py
index 79af6b0..ba78ec2 100644
--- a/tests/test_formatters.py
+++ b/tests/test_formatters.py
@@ -1,51 +1,67 @@
import os
-from unittest import TestCase
from textwrap import dedent
import pyexcel as pe
+from nose.tools import eq_
-class TestAutoDetectInt(TestCase):
+class TestAutoDetectInt:
def setUp(self):
self.content = [[1, 2, 3.1]]
self.test_file = "test_auto_detect_init.ods"
pe.save_as(array=self.content, dest_file_name=self.test_file)
def test_auto_detect_int(self):
- sheet = pe.get_sheet(file_name=self.test_file)
- expected = dedent("""
+ sheet = pe.get_sheet(file_name=self.test_file, library="pyexcel-ods")
+ expected = dedent(
+ """
pyexcel_sheet1:
+---+---+-----+
| 1 | 2 | 3.1 |
- +---+---+-----+""").strip()
- self.assertEqual(str(sheet), expected)
+ +---+---+-----+"""
+ ).strip()
+ eq_(str(sheet), expected)
def test_get_book_auto_detect_int(self):
- book = pe.get_book(file_name=self.test_file)
- expected = dedent("""
+ book = pe.get_book(file_name=self.test_file, library="pyexcel-ods")
+ expected = dedent(
+ """
pyexcel_sheet1:
+---+---+-----+
| 1 | 2 | 3.1 |
- +---+---+-----+""").strip()
- self.assertEqual(str(book), expected)
+ +---+---+-----+"""
+ ).strip()
+ eq_(str(book), expected)
def test_auto_detect_int_false(self):
- sheet = pe.get_sheet(file_name=self.test_file, auto_detect_int=False)
- expected = dedent("""
+ sheet = pe.get_sheet(
+ file_name=self.test_file,
+ auto_detect_int=False,
+ library="pyexcel-ods",
+ )
+ expected = dedent(
+ """
pyexcel_sheet1:
+-----+-----+-----+
| 1.0 | 2.0 | 3.1 |
- +-----+-----+-----+""").strip()
- self.assertEqual(str(sheet), expected)
+ +-----+-----+-----+"""
+ ).strip()
+ eq_(str(sheet), expected)
def test_get_book_auto_detect_int_false(self):
- book = pe.get_book(file_name=self.test_file, auto_detect_int=False)
- expected = dedent("""
+ book = pe.get_book(
+ file_name=self.test_file,
+ auto_detect_int=False,
+ library="pyexcel-ods",
+ )
+ expected = dedent(
+ """
pyexcel_sheet1:
+-----+-----+-----+
| 1.0 | 2.0 | 3.1 |
- +-----+-----+-----+""").strip()
- self.assertEqual(str(book), expected)
+ +-----+-----+-----+"""
+ ).strip()
+ eq_(str(book), expected)
def tearDown(self):
os.unlink(self.test_file)
diff --git a/tests/test_multiline_feature.py b/tests/test_multiline_feature.py
index 7a3afc1..aa2c870 100644
--- a/tests/test_multiline_feature.py
+++ b/tests/test_multiline_feature.py
@@ -1,15 +1,17 @@
import os
+
import pyexcel
def test_reading_multiline_ods():
testfile = os.path.join("tests", "fixtures", "multilineods.ods")
sheet = pyexcel.get_sheet(file_name=testfile)
- assert sheet[0, 0] == '1\n2\n3\n4'
+ assert sheet[0, 0] == "1\n2\n3\n4"
+ assert sheet[1, 0] == "Line 1\n\nLine 2"
def test_writing_multiline_ods():
- content = "2\n3\n4\n993939\na"
+ content = "2\n3\n4\n993939\n\na"
testfile = "writemultiline.ods"
array = [[content, "test"]]
pyexcel.save_as(array=array, dest_file_name=testfile)
diff --git a/tests/test_mutliple_sheets.py b/tests/test_multiple_sheets.py
index bebbf19..6360be6 100644
--- a/tests/test_mutliple_sheets.py
+++ b/tests/test_multiple_sheets.py
@@ -1,9 +1,11 @@
-from base import PyexcelMultipleSheetBase
-import pyexcel
import os
-from nose.tools import raises
import sys
+import pyexcel
+from nose.tools import raises
+
+from base import PyexcelMultipleSheetBase
+
if sys.version_info[0] == 2 and sys.version_info[1] < 7:
from ordereddict import OrderedDict
else:
@@ -54,20 +56,20 @@ class TestAddBooks:
def test_load_a_single_sheet(self):
b1 = pyexcel.get_book(file_name=self.testfile, sheet_name="Sheet1")
assert len(b1.sheet_names()) == 1
- assert b1['Sheet1'].to_array() == self.content['Sheet1']
+ assert b1["Sheet1"].to_array() == self.content["Sheet1"]
def test_load_a_single_sheet2(self):
b1 = pyexcel.load_book(self.testfile, sheet_index=0)
assert len(b1.sheet_names()) == 1
- assert b1['Sheet1'].to_array() == self.content['Sheet1']
+ assert b1["Sheet1"].to_array() == self.content["Sheet1"]
@raises(IndexError)
def test_load_a_single_sheet3(self):
- pyexcel.load_book(self.testfile, sheet_index=10000)
+ pyexcel.get_book(file_name=self.testfile, sheet_index=10000)
@raises(ValueError)
def test_load_a_single_sheet4(self):
- pyexcel.load_book(self.testfile, sheet_name="Not exist")
+ pyexcel.get_book(file_name=self.testfile, sheet_name="Not exist")
def test_delete_sheets(self):
b1 = pyexcel.load_book(self.testfile)
@@ -104,7 +106,7 @@ class TestAddBooks:
b1 = pyexcel.get_book(file_name=self.testfile)
b2 = pyexcel.get_book(file_name=self.testfile2)
b3 = b1 + b2
- content = pyexcel.utils.to_dict(b3)
+ content = b3.dict
sheet_names = content.keys()
assert len(sheet_names) == 6
for name in sheet_names:
@@ -122,7 +124,7 @@ class TestAddBooks:
b1 = pyexcel.BookReader(self.testfile)
b2 = pyexcel.BookReader(self.testfile2)
b1 += b2
- content = pyexcel.utils.to_dict(b1)
+ content = b1.dict
sheet_names = content.keys()
assert len(sheet_names) == 6
for name in sheet_names:
@@ -140,7 +142,7 @@ class TestAddBooks:
b1 = pyexcel.BookReader(self.testfile)
b2 = pyexcel.BookReader(self.testfile2)
b3 = b1 + b2["Sheet3"]
- content = pyexcel.utils.to_dict(b3)
+ content = b3.dict
sheet_names = content.keys()
assert len(sheet_names) == 4
for name in sheet_names:
@@ -158,7 +160,7 @@ class TestAddBooks:
b1 = pyexcel.BookReader(self.testfile)
b2 = pyexcel.BookReader(self.testfile2)
b1 += b2["Sheet3"]
- content = pyexcel.utils.to_dict(b1)
+ content = b1.dict
sheet_names = content.keys()
assert len(sheet_names) == 4
for name in sheet_names:
@@ -176,7 +178,7 @@ class TestAddBooks:
b1 = pyexcel.BookReader(self.testfile)
b2 = pyexcel.BookReader(self.testfile2)
b3 = b1["Sheet1"] + b2["Sheet3"]
- content = pyexcel.utils.to_dict(b3)
+ content = b3.dict
sheet_names = content.keys()
assert len(sheet_names) == 2
assert content["Sheet3"] == self.content["Sheet3"]
@@ -189,7 +191,7 @@ class TestAddBooks:
b1 = pyexcel.BookReader(self.testfile)
b2 = pyexcel.BookReader(self.testfile2)
b3 = b1["Sheet1"] + b2
- content = pyexcel.utils.to_dict(b3)
+ content = b3.dict
sheet_names = content.keys()
assert len(sheet_names) == 4
for name in sheet_names:
@@ -228,17 +230,17 @@ class TestMultiSheetReader:
self.testfile = "file_with_an_empty_sheet.ods"
def test_reader_with_correct_sheets(self):
- r = pyexcel.BookReader(os.path.join("tests", "fixtures",
- self.testfile))
+ r = pyexcel.BookReader(
+ os.path.join("tests", "fixtures", self.testfile)
+ )
assert r.number_of_sheets() == 3
def _produce_ordered_dict():
data_dict = OrderedDict()
- data_dict.update({
- "Sheet1": [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]})
- data_dict.update({
- "Sheet2": [[4, 4, 4, 4], [5, 5, 5, 5], [6, 6, 6, 6]]})
- data_dict.update({
- "Sheet3": [[u'X', u'Y', u'Z'], [1, 4, 7], [2, 5, 8], [3, 6, 9]]})
+ data_dict.update({"Sheet1": [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]})
+ data_dict.update({"Sheet2": [[4, 4, 4, 4], [5, 5, 5, 5], [6, 6, 6, 6]]})
+ data_dict.update(
+ {"Sheet3": [[u"X", u"Y", u"Z"], [1, 4, 7], [2, 5, 8], [3, 6, 9]]}
+ )
return data_dict
diff --git a/tests/test_ods_reader.py b/tests/test_ods_reader.py
index 551bda3..69bb030 100644
--- a/tests/test_ods_reader.py
+++ b/tests/test_ods_reader.py
@@ -1,14 +1,14 @@
import os
-from pyexcel_ods import ods
+
from base import ODSCellTypes
+from pyexcel_ods.odsr import ODSBook
+from pyexcel_ods.odsw import ODSWriter
class TestODSReader(ODSCellTypes):
def setUp(self):
- r = ods.ODSBook()
- r.open(os.path.join("tests",
- "fixtures",
- "ods_formats.ods"))
+ r = ODSBook()
+ r.open(os.path.join("tests", "fixtures", "ods_formats.ods"))
self.data = r.read_all()
for key in self.data.keys():
self.data[key] = list(self.data[key])
@@ -17,17 +17,15 @@ class TestODSReader(ODSCellTypes):
class TestODSWriter(ODSCellTypes):
def setUp(self):
- r = ods.ODSBook()
- r.open(os.path.join("tests",
- "fixtures",
- "ods_formats.ods"))
+ r = ODSBook()
+ r.open(os.path.join("tests", "fixtures", "ods_formats.ods"))
self.data1 = r.read_all()
self.testfile = "odswriter.ods"
- w = ods.ODSWriter()
+ w = ODSWriter()
w.open(self.testfile)
w.write(self.data1)
w.close()
- r2 = ods.ODSBook()
+ r2 = ODSBook()
r2.open(self.testfile)
self.data = r2.read_all()
for key in self.data.keys():
diff --git a/tests/test_stringio.py b/tests/test_stringio.py
index 203d5d5..442a464 100644
--- a/tests/test_stringio.py
+++ b/tests/test_stringio.py
@@ -1,30 +1,32 @@
import os
-from unittest import TestCase
+
import pyexcel
-from base import create_sample_file1
+from nose.tools import eq_
+from base import create_sample_file1
-class TestStringIO(TestCase):
+class TestStringIO:
def test_ods_stringio(self):
- odsfile = "cute.ods"
- create_sample_file1(odsfile)
- with open(odsfile, "rb") as f:
+ testfile = "cute.ods"
+ create_sample_file1(testfile)
+ with open(testfile, "rb") as f:
content = f.read()
- r = pyexcel.get_sheet(file_type="ods", file_content=content)
- result = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 1.1, 1]
- actual = pyexcel.utils.to_array(r.enumerate())
- self.assertEqual(result, actual)
- if os.path.exists(odsfile):
- os.unlink(odsfile)
+ r = pyexcel.get_sheet(
+ file_type="ods", file_content=content, library="pyexcel-ods"
+ )
+ result = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 1.1, 1]
+ actual = list(r.enumerate())
+ eq_(result, actual)
+ if os.path.exists(testfile):
+ os.unlink(testfile)
- def test_xls_output_stringio(self):
- data = [
- [1, 2, 3],
- [4, 5, 6]
- ]
- io = pyexcel.save_as(dest_file_type='ods', array=data)
- r = pyexcel.get_sheet(file_type="ods", file_content=io.getvalue())
+ def test_ods_output_stringio(self):
+ data = [[1, 2, 3], [4, 5, 6]]
+ io = pyexcel.save_as(dest_file_type="ods", array=data)
+ r = pyexcel.get_sheet(
+ file_type="ods", file_content=io.getvalue(), library="pyexcel-ods"
+ )
result = [1, 2, 3, 4, 5, 6]
- actual = pyexcel.utils.to_array(r.enumerate())
- self.assertEqual(result, actual)
+ actual = list(r.enumerate())
+ eq_(result, actual)
diff --git a/tests/test_writer.py b/tests/test_writer.py
index 4ee967e..ab5a976 100644
--- a/tests/test_writer.py
+++ b/tests/test_writer.py
@@ -1,6 +1,8 @@
import os
-from pyexcel_ods import ods
-from base import PyexcelWriterBase, PyexcelHatWriterBase
+
+from base import PyexcelHatWriterBase, PyexcelWriterBase
+from pyexcel_ods.odsr import ODSBook as Reader
+from pyexcel_ods.odsw import ODSWriter as Writer
class TestNativeODSWriter:
@@ -8,28 +10,29 @@ class TestNativeODSWriter:
self.content = {
"Sheet1": [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]],
"Sheet2": [[4, 4, 4, 4], [5, 5, 5, 5], [6, 6, 6, 6]],
- "Sheet3": [[u'X', u'Y', u'Z'], [1, 4, 7], [2, 5, 8], [3, 6, 9]]
+ "Sheet3": [[u"X", u"Y", u"Z"], [1, 4, 7], [2, 5, 8], [3, 6, 9]],
}
- self.testfile = "odswriter.ods"
- writer = ods.ODSWriter()
+ self.testfile = "writer.ods"
+ writer = Writer()
writer.open(self.testfile)
writer.write(self.content)
writer.close()
- reader = ods.ODSBook()
+ reader = Reader()
reader.open(self.testfile)
content = reader.read_all()
for key in content.keys():
content[key] = list(content[key])
assert content == self.content
+ reader.close()
def tearDown(self):
if os.path.exists(self.testfile):
os.unlink(self.testfile)
-class TestODSnCSVWriter(PyexcelWriterBase):
+class TestodsnCSVWriter(PyexcelWriterBase):
def setUp(self):
- self.testfile = "testods.ods"
+ self.testfile = "test.ods"
self.testfile2 = "test.csv"
def tearDown(self):
@@ -39,9 +42,9 @@ class TestODSnCSVWriter(PyexcelWriterBase):
os.unlink(self.testfile2)
-class TestODSHatWriter(PyexcelHatWriterBase):
+class TestodsHatWriter(PyexcelHatWriterBase):
def setUp(self):
- self.testfile = "testhat.ods"
+ self.testfile = "test.ods"
def tearDown(self):
if os.path.exists(self.testfile):