Skip to content

Commit

Permalink
✨ use loxun XMLWriter module and Entr'ouvert ods writer. This is take…
Browse files Browse the repository at this point in the history
…n from https://github.com/kennethreitz/tablib/pull/244 and adapted for pyexcel
  • Loading branch information
chfw committed Aug 4, 2018
1 parent 3f90ebb commit a6aea03
Show file tree
Hide file tree
Showing 10 changed files with 395 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .moban.d/tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% extends 'tests/requirements.txt.jj2' %}
{%block extras %}
pyexcel
pyexcel-xls
pyexcel-ods3
{%endblock%}
6 changes: 4 additions & 2 deletions pyexcel-odsw.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
overrides: "pyexcel.yaml"
name: "pyexcel-odsw"
nick_name: ""
nick_name: "odsw"
file_type: "ods"
version: "0.0.1"
current_version: "0.0.1"
release: "0.0.1"
dependencies: []
dependencies:
- loxun
description: "write an ods file using constant memory"
4 changes: 2 additions & 2 deletions pyexcel_odsw/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
"""
from ._version import __version__, __author__ # flake8: noqa
from pyexcel_io.plugins import IOPluginInfoChain
from pyexcel_io.io isstream, store_data as write_data
from pyexcel_io.io import isstream, store_data as write_data


__FILE_TYPE__ = 'ods'

IOPluginInfoChain(__name__).add_a_writer(
relative_plugin_class_path='w.ODSWriter',
relative_plugin_class_path='odsw.ODSWriter',
file_types=[__FILE_TYPE__],
stream_type='binary'
)
Expand Down
150 changes: 150 additions & 0 deletions pyexcel_odsw/entrouvert_odsw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2005-2016 Entr'ouvert
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.

import tempfile
import zipfile

import loxun


OFFICE_NS = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
TABLE_NS = 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'
TEXT_NS = 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'
XLINK_NS = 'http://www.w3.org/1999/xlink'
STYLE_NS = 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'


class ODSWorkbook(object):
OPENED = 1
CLOSED = 2
INSHEET = 3
INROW = 4

def __init__(self, output):
z = self.z = zipfile.ZipFile(output, 'w')
z.writestr('mimetype', 'application/vnd.oasis.opendocument.spreadsheet')
z.writestr('META-INF/manifest.xml', '''<?xml version="1.0" encoding="UTF-8"?>
<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">
<manifest:file-entry manifest:full-path="/" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/>
<manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/>
<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>
<manifest:file-entry manifest:full-path="META-INF/manifest.xml" manifest:media-type="text/xml"/>
<manifest:file-entry manifest:full-path="mimetype" manifest:media-type="text/plain"/>
</manifest:manifest>''')
z.writestr('styles.xml', '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0">
</office:document-styles>''')
self.content = tempfile.NamedTemporaryFile()
xml = self.xmlwriter = loxun.XmlWriter(self.content, pretty=False)
xml.addNamespace('office', OFFICE_NS)
xml.addNamespace('style', STYLE_NS)
xml.addNamespace('table', TABLE_NS)
xml.addNamespace('xlink', XLINK_NS)
xml.addNamespace('text', TEXT_NS)
# add bold style for headers
xml.startTag('office:document-content')
xml.startTag('office:automatic-styles')
xml.startTag('style:style', {
'style:family': 'paragraph',
'style:name': 'bold',
'style:display-name': 'bold',
})
xml.tag('style:text-properties', {
'style:font-weight-complex': 'bold',
'style:font-weight': 'bold',
'style:font-weight-asian': 'bold'
})
xml.endTag()
xml.endTag()
xml.startTag('office:body')
xml.startTag('office:spreadsheet')
self.status = self.OPENED

def close(self):
assert self.status == self.OPENED
self.status = self.CLOSED
xml = self.xmlwriter
xml.endTag()
xml.endTag()
xml.endTag()
self.z.write(self.content.name, 'content.xml')
self.content.close()
self.z.close()
del self.z
del self.xmlwriter
del self.content

def start_sheet(self, columns, title=None):
assert self.status == self.OPENED
self.status = self.INSHEET
xml = self.xmlwriter
attribs = {}
if title:
attribs['table:name'] = title
xml.startTag('table:table', attribs)
for i in range(columns):
xml.tag('table:table-column')

def end_sheet(self):
assert self.status == self.INSHEET
self.status = self.OPENED
self.xmlwriter.endTag()

def add_headers(self, headers):
self.add_row(headers, {
'table:style-name': 'bold',
'table:default-cell-style-name': 'bold',
}, hint='header')

def add_row(self, row, attribs={}, hint=None):
self.start_row(attribs)
for cell in row:
self.add_cell(cell, hint=hint)
self.end_row()

def start_row(self, attribs={}):
assert self.status == self.INSHEET
self.status = self.INROW
self.xmlwriter.startTag('table:table-row', attribs)

def end_row(self):
assert self.status == self.INROW
self.status = self.INSHEET
self.xmlwriter.endTag()

def add_cell(self, content, hint=None):
assert self.status == self.INROW
content = unicode(content)
self.xmlwriter.startTag('table:table-cell', {
'office:value-type': 'string',
})
self.xmlwriter.startTag('text:p')
attribs = {}
if hint == 'header':
attribs['text:style-name'] = 'bold'
self.xmlwriter.startTag('text:span', attribs)
self.xmlwriter.text(content)
self.xmlwriter.endTag()
self.xmlwriter.endTag()
self.xmlwriter.endTag()

def __del__(self):
if getattr(self, 'content', None) is not None:
try:
self.content.close()
except:
pass
43 changes: 39 additions & 4 deletions pyexcel_odsw/odsw.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,61 @@
"""
pyexcel_odsw
~~~~~~~~~~~~~~~~~~~
The lower level ods file format handler
:copyright: (c) 2018 by Onni Software Ltd. & its contributors
:license: NEW BSD License
"""
import types

from pyexcel_io.book import BookWriter
from pyexcel_io.sheet import SheetWriter
from pyexcel_io._compact import text_type

import entrouvert_odsw as ods


class ODSSheetWriter(SheetWriter):
"""
ods sheet writer
"""
def __init__(self, native_book, name, **keywords):
super(ODSSheetWriter, self).__init__(
native_book, None, name, **keywords)
self.sheet_name = name

def set_sheet_name(self, name):
self.current_row = 0

def write_array(self, table):
to_write_data = table
if isinstance(to_write_data, types.GeneratorType):
to_write_data = list(table)
rows = len(to_write_data)
if rows < 1:
return
columns = max([len(row) for row in to_write_data])
self._native_book.start_sheet(columns,
self.sheet_name)
super(ODSSheetWriter, self).write_array(to_write_data)
self._native_book.end_sheet()

def write_row(self, array):
"""
write a row into the file
"""
pass
import pdb; pdb.set_trace()
cells = []
for cell in array:
try:
cell = text_type(cell, errors="ignore")
except TypeError:
pass
cells.append(cell)
self._native_book.add_row(cells)

self.current_row = 1


class ODSWriter(BookWriter):
Expand All @@ -35,14 +70,14 @@ def open(self, file_name, **keywords):
"""
Open a file for writing
"""
pass
self._native_book = ods.ODSWorkbook(file_name)

def create_sheet(self, name):
return ODSSheetWriter(self._native_book,
self._native_book.add_worksheet(name), name)
name)

def close(self):
"""
This call actually save the file
"""
pass
self._native_book.close()
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
loxun
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@
]

INSTALL_REQUIRES = [
'loxun',
]
SETUP_COMMANDS = {}


PACKAGES = find_packages(exclude=['ez_setup', 'examples', 'tests'])
EXTRAS_REQUIRE = {}
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)
Expand Down
Loading

0 comments on commit a6aea03

Please sign in to comment.