Skip to content

Commit a6aea03

Browse files
committed
✨ use loxun XMLWriter module and Entr'ouvert ods writer. This is taken from https://github.com/kennethreitz/tablib/pull/244 and adapted for pyexcel
1 parent 3f90ebb commit a6aea03

File tree

10 files changed

+395
-11
lines changed

10 files changed

+395
-11
lines changed

Diff for: .moban.d/tests/requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% extends 'tests/requirements.txt.jj2' %}
22
{%block extras %}
33
pyexcel
4-
pyexcel-xls
4+
pyexcel-ods3
55
{%endblock%}

Diff for: pyexcel-odsw.yml

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
overrides: "pyexcel.yaml"
22
name: "pyexcel-odsw"
3-
nick_name: ""
3+
nick_name: "odsw"
4+
file_type: "ods"
45
version: "0.0.1"
56
current_version: "0.0.1"
67
release: "0.0.1"
7-
dependencies: []
8+
dependencies:
9+
- loxun
810
description: "write an ods file using constant memory"

Diff for: pyexcel_odsw/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
"""
88
from ._version import __version__, __author__ # flake8: noqa
99
from pyexcel_io.plugins import IOPluginInfoChain
10-
from pyexcel_io.io isstream, store_data as write_data
10+
from pyexcel_io.io import isstream, store_data as write_data
1111

1212

1313
__FILE_TYPE__ = 'ods'
1414

1515
IOPluginInfoChain(__name__).add_a_writer(
16-
relative_plugin_class_path='w.ODSWriter',
16+
relative_plugin_class_path='odsw.ODSWriter',
1717
file_types=[__FILE_TYPE__],
1818
stream_type='binary'
1919
)

Diff for: pyexcel_odsw/entrouvert_odsw.py

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (C) 2005-2016 Entr'ouvert
4+
#
5+
# This program is free software; you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation; either version 2 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program; if not, see <http://www.gnu.org/licenses/>.
17+
18+
import tempfile
19+
import zipfile
20+
21+
import loxun
22+
23+
24+
OFFICE_NS = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
25+
TABLE_NS = 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'
26+
TEXT_NS = 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'
27+
XLINK_NS = 'http://www.w3.org/1999/xlink'
28+
STYLE_NS = 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'
29+
30+
31+
class ODSWorkbook(object):
32+
OPENED = 1
33+
CLOSED = 2
34+
INSHEET = 3
35+
INROW = 4
36+
37+
def __init__(self, output):
38+
z = self.z = zipfile.ZipFile(output, 'w')
39+
z.writestr('mimetype', 'application/vnd.oasis.opendocument.spreadsheet')
40+
z.writestr('META-INF/manifest.xml', '''<?xml version="1.0" encoding="UTF-8"?>
41+
<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">
42+
<manifest:file-entry manifest:full-path="/" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/>
43+
<manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/>
44+
<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>
45+
<manifest:file-entry manifest:full-path="META-INF/manifest.xml" manifest:media-type="text/xml"/>
46+
<manifest:file-entry manifest:full-path="mimetype" manifest:media-type="text/plain"/>
47+
</manifest:manifest>''')
48+
z.writestr('styles.xml', '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
49+
<office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0">
50+
</office:document-styles>''')
51+
self.content = tempfile.NamedTemporaryFile()
52+
xml = self.xmlwriter = loxun.XmlWriter(self.content, pretty=False)
53+
xml.addNamespace('office', OFFICE_NS)
54+
xml.addNamespace('style', STYLE_NS)
55+
xml.addNamespace('table', TABLE_NS)
56+
xml.addNamespace('xlink', XLINK_NS)
57+
xml.addNamespace('text', TEXT_NS)
58+
# add bold style for headers
59+
xml.startTag('office:document-content')
60+
xml.startTag('office:automatic-styles')
61+
xml.startTag('style:style', {
62+
'style:family': 'paragraph',
63+
'style:name': 'bold',
64+
'style:display-name': 'bold',
65+
})
66+
xml.tag('style:text-properties', {
67+
'style:font-weight-complex': 'bold',
68+
'style:font-weight': 'bold',
69+
'style:font-weight-asian': 'bold'
70+
})
71+
xml.endTag()
72+
xml.endTag()
73+
xml.startTag('office:body')
74+
xml.startTag('office:spreadsheet')
75+
self.status = self.OPENED
76+
77+
def close(self):
78+
assert self.status == self.OPENED
79+
self.status = self.CLOSED
80+
xml = self.xmlwriter
81+
xml.endTag()
82+
xml.endTag()
83+
xml.endTag()
84+
self.z.write(self.content.name, 'content.xml')
85+
self.content.close()
86+
self.z.close()
87+
del self.z
88+
del self.xmlwriter
89+
del self.content
90+
91+
def start_sheet(self, columns, title=None):
92+
assert self.status == self.OPENED
93+
self.status = self.INSHEET
94+
xml = self.xmlwriter
95+
attribs = {}
96+
if title:
97+
attribs['table:name'] = title
98+
xml.startTag('table:table', attribs)
99+
for i in range(columns):
100+
xml.tag('table:table-column')
101+
102+
def end_sheet(self):
103+
assert self.status == self.INSHEET
104+
self.status = self.OPENED
105+
self.xmlwriter.endTag()
106+
107+
def add_headers(self, headers):
108+
self.add_row(headers, {
109+
'table:style-name': 'bold',
110+
'table:default-cell-style-name': 'bold',
111+
}, hint='header')
112+
113+
def add_row(self, row, attribs={}, hint=None):
114+
self.start_row(attribs)
115+
for cell in row:
116+
self.add_cell(cell, hint=hint)
117+
self.end_row()
118+
119+
def start_row(self, attribs={}):
120+
assert self.status == self.INSHEET
121+
self.status = self.INROW
122+
self.xmlwriter.startTag('table:table-row', attribs)
123+
124+
def end_row(self):
125+
assert self.status == self.INROW
126+
self.status = self.INSHEET
127+
self.xmlwriter.endTag()
128+
129+
def add_cell(self, content, hint=None):
130+
assert self.status == self.INROW
131+
content = unicode(content)
132+
self.xmlwriter.startTag('table:table-cell', {
133+
'office:value-type': 'string',
134+
})
135+
self.xmlwriter.startTag('text:p')
136+
attribs = {}
137+
if hint == 'header':
138+
attribs['text:style-name'] = 'bold'
139+
self.xmlwriter.startTag('text:span', attribs)
140+
self.xmlwriter.text(content)
141+
self.xmlwriter.endTag()
142+
self.xmlwriter.endTag()
143+
self.xmlwriter.endTag()
144+
145+
def __del__(self):
146+
if getattr(self, 'content', None) is not None:
147+
try:
148+
self.content.close()
149+
except:
150+
pass

Diff for: pyexcel_odsw/odsw.py

+39-4
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,61 @@
11
"""
22
pyexcel_odsw
33
~~~~~~~~~~~~~~~~~~~
4+
45
The lower level ods file format handler
6+
57
:copyright: (c) 2018 by Onni Software Ltd. & its contributors
68
:license: NEW BSD License
79
"""
10+
import types
11+
812
from pyexcel_io.book import BookWriter
913
from pyexcel_io.sheet import SheetWriter
14+
from pyexcel_io._compact import text_type
15+
16+
import entrouvert_odsw as ods
1017

1118

1219
class ODSSheetWriter(SheetWriter):
1320
"""
1421
ods sheet writer
1522
"""
23+
def __init__(self, native_book, name, **keywords):
24+
super(ODSSheetWriter, self).__init__(
25+
native_book, None, name, **keywords)
26+
self.sheet_name = name
27+
1628
def set_sheet_name(self, name):
1729
self.current_row = 0
1830

31+
def write_array(self, table):
32+
to_write_data = table
33+
if isinstance(to_write_data, types.GeneratorType):
34+
to_write_data = list(table)
35+
rows = len(to_write_data)
36+
if rows < 1:
37+
return
38+
columns = max([len(row) for row in to_write_data])
39+
self._native_book.start_sheet(columns,
40+
self.sheet_name)
41+
super(ODSSheetWriter, self).write_array(to_write_data)
42+
self._native_book.end_sheet()
43+
1944
def write_row(self, array):
2045
"""
2146
write a row into the file
2247
"""
23-
pass
48+
import pdb; pdb.set_trace()
49+
cells = []
50+
for cell in array:
51+
try:
52+
cell = text_type(cell, errors="ignore")
53+
except TypeError:
54+
pass
55+
cells.append(cell)
56+
self._native_book.add_row(cells)
57+
58+
self.current_row = 1
2459

2560

2661
class ODSWriter(BookWriter):
@@ -35,14 +70,14 @@ def open(self, file_name, **keywords):
3570
"""
3671
Open a file for writing
3772
"""
38-
pass
73+
self._native_book = ods.ODSWorkbook(file_name)
3974

4075
def create_sheet(self, name):
4176
return ODSSheetWriter(self._native_book,
42-
self._native_book.add_worksheet(name), name)
77+
name)
4378

4479
def close(self):
4580
"""
4681
This call actually save the file
4782
"""
48-
pass
83+
self._native_book.close()

Diff for: requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
loxun

Diff for: setup.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@
3737
]
3838

3939
INSTALL_REQUIRES = [
40+
'loxun',
4041
]
4142
SETUP_COMMANDS = {}
4243

4344

4445
PACKAGES = find_packages(exclude=['ez_setup', 'examples', 'tests'])
45-
EXTRAS_REQUIRE = {}
46+
EXTRAS_REQUIRE = {
47+
}
4648
# You do not need to read beyond this line
4749
PUBLISH_COMMAND = '{0} setup.py sdist bdist_wheel upload -r pypi'.format(
4850
sys.executable)

0 commit comments

Comments
 (0)