Merge branch 'wip/github'

This commit is contained in:
Frédéric Péters 2022-03-29 18:54:02 +02:00
commit faa0e04b3e
55 changed files with 2030 additions and 886 deletions

4
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,4 @@
# These are supported funding model platforms
github: chfw
patreon: chfw

8
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,8 @@
With your PR, here is a check list:
- [ ] Has test cases written?
- [ ] Has all code lines tested?
- [ ] Has `make format` been run?
- [ ] Please update CHANGELOG.yml(not CHANGELOG.rst)
- [ ] Has fair amount of documentation if your change is complex
- [ ] Agree on NEW BSD License for your contribution

20
.github/workflows/lint.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
name: lint code
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: lint
run: |
pip install flake8
pip install -r tests/requirements.txt
flake8 --exclude=.moban.d,docs,setup.py --builtins=unicode,xrange,long .
python setup.py checkdocs

29
.github/workflows/moban-update.yml vendored Normal file
View File

@ -0,0 +1,29 @@
on: [push]
jobs:
run_moban:
runs-on: ubuntu-latest
name: synchronize templates via moban
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.head_ref }}
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: '3.7'
- name: check changes
run: |
pip install moban gitfs2 pypifs moban-jinja2-github moban-ansible
moban
git status
git diff --exit-code
- name: Auto-commit
if: failure()
uses: docker://cdssnc/auto-commit-github-action
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: >-
This is an auto-commit, updating project meta data,
such as changelog.rst, contributors.rst

26
.github/workflows/pythonpublish.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: Upload Python Package
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*

32
.github/workflows/tests.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: run_tests
on: [push, pull_request]
jobs:
test:
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
os: [macOs-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
name: run tests
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: install
run: |
pip install -r requirements.txt
pip install -r tests/requirements.txt
- name: test
run: |
pip freeze
nosetests --verbosity=3 --with-coverage --cover-package pyexcel_ods --cover-package tests tests --with-doctest --doctest-extension=.rst README.rst docs/source pyexcel_ods
- name: Upload coverage
uses: codecov/codecov-action@v1
with:
name: ${{ matrix.os }} Python ${{ matrix.python-version }}

550
.gitignore vendored
View File

@ -1,6 +1,546 @@
*.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/
share/python-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/
.nox/
.coverage
pyexcel*-info
build
dist
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# 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/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# 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
Thumbs.db:encryptable
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
# network security
/network-security.data
# Vim rules
# Swap
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
# JetBrains rules
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# 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
# Generated files
.idea/**/contentModel.xml
# 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
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# 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
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# 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
sftp-config-alt*.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
*.code-workspace
# Local History for Visual Studio Code
.history/
# 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
## Gcc Patch
/*.gcno
# 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/
.apt_generated_test/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
# Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations:
#.project
# 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
# remove moban hash dictionary
.moban.hashes

10
.isort.cfg Normal file
View File

@ -0,0 +1,10 @@
[settings]
line_length=79
known_first_party=lml, pyexcel_io, odf
known_third_party=nose
indent=' '
multi_line_output=3
length_sort=1
default_section=FIRSTPARTY
no_lines_before=LOCALFOLDER
sections=FUTURE,STDLIB,FIRSTPARTY,THIRDPARTY,LOCALFOLDER

View File

@ -1,16 +0,0 @@
{%extends 'README.rst.jj2' %}
{%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.
{%endblock%}
{%block extras %}
Credits
================================================================================
ODSReader is originally written by `Marco Conti <https://github.com/marcoconti83/read-ods-with-odfpy>`_
{%endblock%}

View File

@ -0,0 +1,30 @@
{%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
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 %}
Credits
================================================================================
ODSReader is originally written by `Marco Conti <https://github.com/marcoconti83/read-ods-with-odfpy>`_
{%endblock%}

View File

@ -0,0 +1,7 @@
{% extends 'setup.py.jj2' %}
{%block platform_block%}
{%endblock%}
{%block compat_block%}
{%endblock%}

View File

@ -1,13 +0,0 @@
{% extends 'setup.py.jj2' %}
{%block platform_block%}
{%endblock%}
{%block compat_block%}
{%endblock%}
{%block additional_classifiers%}
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7'
{%endblock%}}

View File

@ -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%}

View File

@ -0,0 +1,4 @@
{% extends 'tests/test_formatters.py.jj2' %}
{% block test_date_format %}
{% endblock %}

View File

@ -1,11 +0,0 @@
{% extends "travis.yml.jj2" %}
{%block custom_python_versions%}
python:
- 2.6
- 2.7
- 3.3
- 3.4
- 3.5
{%endblock%}

View File

@ -1,17 +1,9 @@
overrides: "git://github.com/pyexcel/pyexcel-mobans!/mobanfile.yaml"
configuration:
configuration_dir: "commons/config"
template_dir:
- "commons/templates"
- ".moban.d"
configuration: pyexcel_ods.yaml
configuration: pyexcel-ods.yml
targets:
- README.rst: README.rst
- setup.py: setup.py
- README.rst: custom_README.rst.jj2
- setup.py: custom_setup.py.jj2
- "docs/source/conf.py": "docs/source/conf.py.jj2"
- .travis.yml: travis.yml
- requirements.txt: requirements.txt
- LICENSE: LICENSE.jj2
- .gitignore: gitignore.jj2
- MANIFEST.in: MANIFEST.in.jj2
- "tests/requirements.txt": "tests/requirements.txt"
- test.sh: test.sh.jj2
- test.bat: test.sh.jj2

View File

@ -1,23 +0,0 @@
sudo: false
language: python
notifications:
email: false
python:
- 2.6
- 2.7
- 3.3
- 3.4
- 3.5
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
script:
- make test
after_success:
codecov

View File

@ -1,11 +1,144 @@
Change log
================================================================================
0.6.0 - 9.10.2020
--------------------------------------------------------------------------------
**added**
#. new style reader and writer plugins. works with pyexcel-io v0.6.2
0.5.6 - 19.03.2019
--------------------------------------------------------------------------------
**added**
#. `#32 <https://github.com/pyexcel/pyexcel-ods/issues/32>`_, fix odfpy pinning
#. `#33 <https://github.com/pyexcel/pyexcel-ods/issues/33>`_, fix
IntegerAccuracyLossError on i586
0.5.5 - 16.03.2019
--------------------------------------------------------------------------------
**added**
#. `#32 <https://github.com/pyexcel/pyexcel-ods/issues/32>`_, fix odfpy pinning
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
********************************************************************************
**Added**
#. support pagination. two pairs: start_row, row_limit and start_column,
column_limit help you deal with large files.
@ -14,115 +147,103 @@ Added
0.2.0 - 01.06.2016
--------------------------------------------------------------------------------
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
********************************************************************************
**Updated**
#. support the auto-import feature of pyexcel-io 0.2.0
0.1.1 - 30.01.2016
--------------------------------------------------------------------------------
Added
********************************************************************************
**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
**Updated**
#. compatibility with pyexcel-io 0.1.1
0.0.12 - 10.10.2015
--------------------------------------------------------------------------------
Updated
********************************************************************************
#. Bug fix: excessive trailing columns with empty values
**Updated**
#. Bug fix: excessive trailing columns with empty values
0.0.11 - 26.09.2015
--------------------------------------------------------------------------------
Updated
********************************************************************************
#. Complete fix for libreoffice datetime field
**Updated**
#. Complete fix for libreoffice datetime field
0.0.10 - 15.09.2015
--------------------------------------------------------------------------------
Updated
********************************************************************************
#. Bug fix: date field could have datetime from libreoffice
**Updated**
#. Bug fix: date field could have datetime from libreoffice
0.0.9 - 21.08.2015
--------------------------------------------------------------------------------
Updated
********************************************************************************
#. Bug fix: utf-8 string throw unicode exceptions
**Updated**
#. 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
**Updated**
#. 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
**Updated**
#. Bug fix: "number-columns-repeated" is now respected
0.0.6 - 21.05.2015
--------------------------------------------------------------------------------
Updated
********************************************************************************
**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
********************************************************************************
**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.
**Updated**
#. 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

10
CONTRIBUTORS.rst Normal file
View File

@ -0,0 +1,10 @@
3 contributors
================================================================================
In alphabetical order:
* `Azamat H. Hackimov <https://github.com/winterheart>`_
* `John Vandenberg <https://github.com/jayvdb>`_
* `Mateusz Konieczny <https://github.com/matkoniecz>`_

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2016 by Onni Software Ltd. and its contributors
Copyright (c) 2015-2020 by Onni Software Ltd. and its contributors
All rights reserved.
Redistribution and use in source and binary forms of the software as well
@ -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.
DAMAGE.

View File

@ -1,2 +1,6 @@
include README.rst
include LICENSE
include CHANGELOG.rst
include CONTRIBUTORS.rst
recursive-include tests *
recursive-include docs *

View File

@ -2,27 +2,72 @@
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/chfw
.. image:: https://codecov.io/github/pyexcel/pyexcel-ods/coverage.png
:target: https://codecov.io/github/pyexcel/pyexcel-ods
.. image:: https://raw.githubusercontent.com/pyexcel/pyexcel-mobans/master/images/awesome-badge.svg
:target: https://awesome-python.com/#specific-formats-processing
.. image:: https://github.com/pyexcel/pyexcel-ods/workflows/run_tests/badge.svg
:target: http://github.com/pyexcel/pyexcel-ods/actions
.. image:: https://codecov.io/gh/pyexcel/pyexcel-ods/branch/master/graph/badge.svg
:target: https://codecov.io/gh/pyexcel/pyexcel-ods
.. image:: https://badge.fury.io/py/pyexcel-ods.svg
:target: https://pypi.org/project/pyexcel-ods
.. image:: https://pepy.tech/badge/pyexcel-ods/month
:target: https://pepy.tech/project/pyexcel-ods
.. image:: https://img.shields.io/gitter/room/gitterHQ/gitter.svg
:target: https://gitter.im/pyexcel/Lobby
.. image:: https://img.shields.io/static/v1?label=continuous%20templating&message=%E6%A8%A1%E7%89%88%E6%9B%B4%E6%96%B0&color=blue&style=flat-square
:target: https://moban.readthedocs.io/en/latest/#at-scale-continous-templating-for-open-source-projects
.. image:: https://img.shields.io/static/v1?label=coding%20style&message=black&color=black&style=flat-square
:target: https://github.com/psf/black
**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
Support the project
================================================================================
If your company has embedded pyexcel and its components into a revenue generating
product, please support me on github, `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.
Known constraints
==================
Fonts, colors and charts are not supported.
Nor to read password protected xls, xlsx and ods files.
Installation
================================================================================
You can install it via pip:
You can install pyexcel-ods via pip:
.. code-block:: bash
@ -33,7 +78,7 @@ 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
@ -43,9 +88,6 @@ Usage
As a standalone library
--------------------------------------------------------------------------------
Write to an ods file
********************************************************************************
.. testcode::
:hide:
@ -62,6 +104,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 +119,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 +153,7 @@ Here's the sample code to write a dictionary to an ods file:
Read from an ods from memory
********************************************************************************
@ -123,6 +172,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 +236,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 +245,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 +315,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 +331,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,28 +339,16 @@ 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::
#. pip install moban
#. git clone https://github.com/pyexcel/pyexcel-commons.git
#. make your changes in `.moban.d` directory, then issue command `moban`
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.
What is 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.
What is pyexcel-commons
---------------------------------
Many information that are shared across pyexcel projects, such as: this developer guide, license info, etc. are stored in `pyexcel-commons` project.
What is .moban.d
---------------------------------
`.moban.d` stores the specific meta data for the library.
How to test your contribution
------------------------------
@ -322,12 +357,23 @@ Although `nose` and `doctest` are both used in code testing, it is adviable that
On Linux/Unix systems, please launch your tests like this::
$ make test
$ make
On Windows systems, please issue this command::
> test.bat
Before you commit
------------------------------
Please run::
$ make format
so as to beautify your code otherwise travis-ci may fail your unit test.
Credits
================================================================================

204
changelog.yml Normal file
View File

@ -0,0 +1,204 @@
name: pyexcel-ods
organisation: pyexcel
releases:
- changes:
- action: added
details:
- 'new style reader and writer plugins. works with pyexcel-io v0.6.2'
date: 9.10.2020
version: 0.6.0
- changes:
- action: added
details:
- '`#32`, fix odfpy pinning'
- '`#33`, fix IntegerAccuracyLossError on i586'
date: 19.03.2019
version: 0.5.6
- changes:
- action: added
details:
- '`#32`, fix odfpy pinning'
date: 16.03.2019
version: 0.5.5
- 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

View File

@ -1,39 +1,86 @@
# -*- 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' +
''
)
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
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 = 'pyexcel-ods'
copyright = '2015-2020 Onni Software Ltd.'
author = 'C.W.'
# The short X.Y version
version = '0.6.0'
# The full version, including alpha/beta/rc tags
release = '0.6.0'
# -- General configuration ---------------------------------------------------
# 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']
source_suffix = '.rst'
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'
# -- 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'
# 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']
htmlhelp_basename = 'pyexcel-odsdoc'
latex_elements = {}
latex_documents = [
('index', 'pyexcel-ods.tex', u'pyexcel-ods Documentation',
'Onni Software Ltd.', 'manual'),
]
man_pages = [
('index', 'pyexcel-ods', u'pyexcel-ods Documentation',
[u'Onni Software Ltd.'], 1)
]
# -- Extension configuration -------------------------------------------------
# -- Options for intersphinx extension ---------------------------------------
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/3/': 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),
})
master_doc = "index"

3
format.sh Normal file
View File

@ -0,0 +1,3 @@
isort $(find pyexcel_ods -name "*.py"|xargs echo) $(find tests -name "*.py"|xargs echo)
black -l 79 pyexcel_ods
black -l 79 tests

2
lint.sh Normal file
View File

@ -0,0 +1,2 @@
pip install flake8
flake8 --exclude=.moban.d,docs,setup.py --builtins=unicode,xrange,long . && python setup.py checkdocs

18
pyexcel-ods.yml Normal file
View File

@ -0,0 +1,18 @@
overrides: "pyexcel.yaml"
name: "pyexcel-ods"
nick_name: ods
version: 0.6.0
current_version: 0.6.0
release: 0.6.0
copyright_year: 2015-2020
file_type: ods
dependencies:
- pyexcel-io>=0.6.2
- odfpy>=1.3.5
test_dependencies:
- pyexcel
- psutil
- pyexcel-xls
description: A wrapper library to read, manipulate and write data in ods format
python_requires: ">=3.6"
min_python_version: "3.6"

View File

@ -1,10 +0,0 @@
overrides: "pyexcel.yaml"
name: "pyexcel-ods"
nick_name: ods
version: 0.2.1
release: 0.2.0
file_type: ods
dependencies:
- pyexcel-io>=0.2.0
- odfpy>=1.3.3
description: A wrapper library to read, manipulate and write data in ods format

View File

@ -2,28 +2,48 @@
pyexcel_ods
~~~~~~~~~~~~~~~~~~~
The lower level ods file format handler using odfpy
:copyright: (c) 2015-2016 by Onni Software Ltd & its contributors
:copyright: (c) 2015-2020 by Onni Software Ltd & its contributors
:license: New BSD License
"""
# flake8: noqa
from pyexcel_io.io import get_data as read_data
from pyexcel_io.io import isstream
from pyexcel_io.io import store_data as write_data
# 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, IOPluginInfoChainV2
from pyexcel_io.io import get_data as read_data, isstream, store_data as write_data
__FILE_TYPE__ = "ods"
IOPluginInfoChain(__name__)
IOPluginInfoChainV2(__name__).add_a_reader(
relative_plugin_class_path="odsr.ODSBook",
locations=["file", "memory"],
file_types=[__FILE_TYPE__],
stream_type="binary",
).add_a_reader(
relative_plugin_class_path="odsr.ODSBookInContent",
locations=["content"],
file_types=[__FILE_TYPE__],
stream_type="binary",
).add_a_writer(
relative_plugin_class_path="odsw.ODSWriter",
locations=["file", "memory"],
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)

View File

@ -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"
}

View File

@ -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,)

130
pyexcel_ods/odsr.py Normal file
View File

@ -0,0 +1,130 @@
"""
pyexcel_ods.odsr
~~~~~~~~~~~~~~~~~~~~~
ods reader
:copyright: (c) 2014-2020 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.
from io import BytesIO
import pyexcel_io.service as service
from odf.text import P
from odf.table import Table, TableRow, TableCell
# Thanks to grt for the fixes
from odf.teletype import extractText
from odf.namespaces import OFFICENS
from odf.opendocument import load
from pyexcel_io.plugin_api import ISheet, IReader, NamedContent
class ODSSheet(ISheet):
"""native ods sheet"""
def __init__(self, sheet, auto_detect_int=True, **keywords):
self._native_sheet = sheet
self._keywords = 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(IReader):
"""read ods book"""
def __init__(self, file_alike_object, _, **keywords):
self._native_book = load(file_alike_object)
self._keywords = keywords
self.content_array = [
NamedContent(table.getAttribute("name"), table)
for table in self._native_book.spreadsheet.getElementsByType(Table)
]
def read_sheet(self, sheet_index):
"""read a sheet at a specified index"""
table = self.content_array[sheet_index].payload
sheet = ODSSheet(table, **self._keywords)
return sheet
def close(self):
self._native_book = None
class ODSBookInContent(ODSBook):
"""
Open xlsx as read only mode
"""
def __init__(self, file_content, file_type, **keywords):
io = BytesIO(file_content)
super().__init__(io, file_type, **keywords)

91
pyexcel_ods/odsw.py Normal file
View File

@ -0,0 +1,91 @@
"""
pyexcel_ods.odsw
~~~~~~~~~~~~~~~~~~~~~
ods writer
:copyright: (c) 2014-2020 by Onni Software Ltd.
:license: New BSD License, see LICENSE for more details
"""
import pyexcel_io.service as converter
from odf.text import P
from odf.table import Table, TableRow, TableCell
from odf.namespaces import OFFICENS
from odf.opendocument import OpenDocumentSpreadsheet
from pyexcel_io.plugin_api import IWriter, ISheetWriter
class ODSSheetWriter(ISheetWriter):
"""
ODS sheet writer
"""
def __init__(self, ods_book, sheet_name):
self._native_book = ods_book
self._native_sheet = Table(name=sheet_name)
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(IWriter):
"""
open document spreadsheet writer
"""
def __init__(self, file_alike_object, file_type, **_):
self.file_alike_object = file_alike_object
self._native_book = OpenDocumentSpreadsheet()
def create_sheet(self, name):
"""
write a row into the file
"""
return ODSSheetWriter(self._native_book, name)
def close(self):
"""
This call writes file
"""
self._native_book.write(self.file_alike_object)
self._native_book = None

View File

@ -1,2 +1,2 @@
pyexcel-io>=0.2.0
odfpy>=1.3.3
pyexcel-io>=0.6.2
odfpy>=1.3.5

View File

@ -1,3 +1 @@
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
https://github.com/pyexcel/pyexcel-io/archive/dev.zip

4
setup.cfg Normal file
View File

@ -0,0 +1,4 @@
[metadata]
description-file = README.rst
[bdist_wheel]
universal = 1

211
setup.py
View File

@ -1,76 +1,203 @@
#!/usr/bin/env python3
"""
Template by pypi-mobans
"""
import os
import sys
import codecs
import locale
import platform
from shutil import rmtree
from setuptools import Command, setup, find_packages
# Work around mbcs bug in distutils.
# http://bugs.python.org/issue10945
# This work around is only if a project supports Python < 3.4
# Work around for locale not being set
try:
from setuptools import setup, find_packages
except ImportError:
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup, find_packages
lc = locale.getlocale()
pf = platform.system()
if pf != "Windows" and lc == (None, None):
locale.setlocale(locale.LC_ALL, "C.UTF-8")
except (ValueError, UnicodeError, locale.Error):
locale.setlocale(locale.LC_ALL, "en_US.UTF-8")
NAME = 'pyexcel-ods'
AUTHOR = 'C.W.'
VERSION = '0.2.1'
EMAIL = 'wangc_2011 (at) hotmail.com'
LICENSE = 'New BSD'
PACKAGES = find_packages(exclude=['ez_setup', 'examples', 'tests'])
NAME = "pyexcel-ods"
AUTHOR = "C.W."
VERSION = "0.6.0"
EMAIL = "info@pyexcel.org"
LICENSE = "New BSD"
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.6.0.tar.gz" % URL
FILES = ["README.rst", "CHANGELOG.rst"]
KEYWORDS = [
'excel',
'python',
'pyexcel',
"python",
]
INSTALL_REQUIRES = [
'pyexcel-io>=0.2.0',
'odfpy>=1.3.3',
]
EXTRAS_REQUIRE = {
}
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'
"Topic :: Software Development :: Libraries",
"Programming Language :: Python",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
]
PYTHON_REQUIRES = ">=3.6"
INSTALL_REQUIRES = [
"pyexcel-io>=0.6.2",
"odfpy>=1.3.5",
]
SETUP_COMMANDS = {}
PACKAGES = find_packages(exclude=["ez_setup", "examples", "tests", "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)
HERE = os.path.abspath(os.path.dirname(__file__))
GS_COMMAND = ("gease pyexcel-ods v0.6.0 " +
"Find 0.6.0 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)
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)
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
if __name__ == '__main__':
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__":
setup(
test_suite="tests",
name=NAME,
author=AUTHOR,
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,
python_requires=PYTHON_REQUIRES,
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
)

View File

@ -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

View File

@ -1,11 +1,13 @@
import os
import os # noqa
import datetime # 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 +19,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 +39,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 +53,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 +76,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], "")

BIN
tests/fixtures/12_day_as_time.ods vendored Normal file

Binary file not shown.

BIN
tests/fixtures/comment-in-cell.ods vendored Normal file

Binary file not shown.

BIN
tests/fixtures/issue_27.ods vendored Normal file

Binary file not shown.

BIN
tests/fixtures/issue_61.ods vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
tests/fixtures/pyexcel_81_ods_19.ods vendored Executable file

Binary file not shown.

BIN
tests/fixtures/white_space.ods vendored Normal file

Binary file not shown.

View File

@ -1,6 +1,14 @@
nose
mock;python_version<"3"
codecov
coverage
flake8
black
isort
collective.checkdocs
pygments
moban
moban_jinja2_github
pyexcel
psutil
pyexcel-xls

View File

@ -1,13 +1,21 @@
#!/usr/bin/python
# -*- encoding: utf-8 -*-
import os
import psutil
import pyexcel as pe
from pyexcel_ods import get_data, save_data
from nose.tools import raises, eq_
from pyexcel_io.exceptions import IntegerAccuracyLossError
from nose import SkipTest
from nose.tools import eq_, raises
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)

View File

@ -1,6 +1,7 @@
import os
from pyexcel_io import get_data, save_data
from nose.tools import eq_
@ -13,49 +14,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)

View File

@ -1,51 +1,68 @@
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)

View File

@ -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)

View File

@ -1,9 +1,11 @@
from base import PyexcelMultipleSheetBase
import pyexcel
import os
from nose.tools import raises
import sys
import pyexcel
from base import PyexcelMultipleSheetBase
from nose.tools import raises
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

View File

@ -1,14 +1,16 @@
import os
from pyexcel_ods import ods
from base import ODSCellTypes
from pyexcel_ods.odsr import ODSBook
from pyexcel_ods.odsw import ODSWriter
from pyexcel_io.reader import Reader
class TestODSReader(ODSCellTypes):
def setUp(self):
r = ods.ODSBook()
r.open(os.path.join("tests",
"fixtures",
"ods_formats.ods"))
r = Reader("ods")
r.reader_class = 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,19 +19,17 @@ class TestODSReader(ODSCellTypes):
class TestODSWriter(ODSCellTypes):
def setUp(self):
r = ods.ODSBook()
r.open(os.path.join("tests",
"fixtures",
"ods_formats.ods"))
r = Reader("ods")
r.reader_class = ODSBook
r.open(os.path.join("tests", "fixtures", "ods_formats.ods"))
r.close()
self.data1 = r.read_all()
self.testfile = "odswriter.ods"
w = ods.ODSWriter()
w.open(self.testfile)
w = ODSWriter(self.testfile, "ods")
w.write(self.data1)
w.close()
r2 = ods.ODSBook()
r2.open(self.testfile)
self.data = r2.read_all()
r.open(self.testfile)
self.data = r.read_all()
for key in self.data.keys():
self.data[key] = list(self.data[key])

View File

@ -1,30 +1,32 @@
import os
from unittest import TestCase
import pyexcel
from base import create_sample_file1
from nose.tools import eq_
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)

View File

@ -1,6 +1,8 @@
import os
from pyexcel_ods import ods
from base import PyexcelWriterBase, PyexcelHatWriterBase
from pyexcel_io import get_data
from pyexcel_ods.odsw import ODSWriter as Writer
class TestNativeODSWriter:
@ -8,18 +10,13 @@ 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()
writer.open(self.testfile)
self.testfile = "writer.ods"
writer = Writer(self.testfile, "ods")
writer.write(self.content)
writer.close()
reader = ods.ODSBook()
reader.open(self.testfile)
content = reader.read_all()
for key in content.keys():
content[key] = list(content[key])
content = get_data(self.testfile, library="pyexcel-ods")
assert content == self.content
def tearDown(self):
@ -27,9 +24,9 @@ class TestNativeODSWriter:
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 +36,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):