From 9a20e305441ef86e2d7a81e956546f862af0f818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20=C3=89vrard?= Date: Tue, 3 Sep 2024 15:16:30 +0200 Subject: [PATCH] Allow setting zip file generation options when generating opendocument files Closes #72 --- CHANGELOG | 2 ++ doc/indepthexample.rst | 26 +++++++++++++++++++++++ relatorio/templates/opendocument.py | 32 ++++++++++++++++++++++------- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 18e2565..4e983ec 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Allow setting zip file generation options on opendocument templates + 0.10.2 - 20240421 ----------------- * Defer files added to serializer before call diff --git a/doc/indepthexample.rst b/doc/indepthexample.rst index efd8a66..1987f93 100644 --- a/doc/indepthexample.rst +++ b/doc/indepthexample.rst @@ -49,6 +49,32 @@ And thus here is our invoice, generated through relatorio: .. image:: basic_generated.png +Advanced zip options +-------------------- + +It can happen that the document being generated result in a file which +uncompressed size is bigger than 2GiB. The default settings of Python's +`zipfile library`_ do not allow to generate those files. + +To circumvent this issue, opendocument templates support additional parameters +to the ``generate`` method. Those parameters are: + +:_relatorio_compresslevel: This parameter defines the *compresslevel* parameter + of the underlying Zipfile_ call. + +:_relatorio_chunksize: This parameter defines the size of each chunk of data + streamed to the underlying Zipfile_ object. + +:_relatorio_zip64: This parameter forces the handling of ZIP64 extensions. + It should be set to true if the size of the file may be + bigger than 2GiB. + +:_relatorio_compression_method: This parameter defines the *compression* + paramater of the underlygin Zipfile_ call. + +.. _`zipfile library`: https://docs.python.org/3/library/zipfile.html +.. _Zipfile: https://docs.python.org/3/library/zipfile.html#zipfile.ZipFile + One step further: OpenOffice Calc and OpenOffice Impress templates ------------------------------------------------------------------ diff --git a/relatorio/templates/opendocument.py b/relatorio/templates/opendocument.py index 5f0b705..52fc4bd 100644 --- a/relatorio/templates/opendocument.py +++ b/relatorio/templates/opendocument.py @@ -859,9 +859,19 @@ def _guess_type(self, val): attrs['{%s}value-type' % self.namespaces['calcext']] = type_ return attrs - def generate(self, *args, **kwargs): + def generate(self, *args, + _relatorio_compresslevel=None, + _relatorio_chunksize=64, + _relatorio_zip64=False, + _relatorio_compression_method=zipfile.ZIP_DEFLATED, + **kwargs): "creates the RelatorioStream." - serializer = OOSerializer(self._source, self._files) + serializer = OOSerializer( + self._source, self._files, + compresslevel=_relatorio_compresslevel, + zip64=_relatorio_zip64, + chunksize=_relatorio_chunksize, + compression_method=_relatorio_compression_method) kwargs['__relatorio_make_href'] = ImageHref(serializer, kwargs) kwargs['__relatorio_make_dimension'] = ImageDimension(self.namespaces) kwargs['__relatorio_guess_type'] = self._guess_type @@ -1039,9 +1049,10 @@ def remove_file_entry(self, path): class _AbstractZipWriteSplitStream(object): - def __init__(self, zipfile, chunksize=64): + def __init__(self, zipfile, chunksize=64, zip64=False): self.zipfile = zipfile self.chunksize = chunksize + self.zip64 = zip64 def open(self, zinfo): raise NotImplementedError @@ -1088,7 +1099,8 @@ def write(self, data): def flush(self): if not self._fp: - self._fp = self.zipfile.open(self._zinfo, mode='w') + self._fp = self.zipfile.open( + self._zinfo, mode='w', force_zip64=self.zip64) self._fp.write(b''.join(self._buffer)) self._buffer.clear() else: @@ -1116,12 +1128,17 @@ def write(self, data): class OOSerializer: - def __init__(self, source, files, chunksize=64): + def __init__(self, source, files, chunksize=64, + compresslevel=None, zip64=False, + compression_method=zipfile.ZIP_DEFLATED): self.inzip = get_zip_file(source) self.manifest = Manifest(self.inzip.read(MANIFEST)) self.xml_serializer = genshi.output.XMLSerializer() self._files = files self.chunksize = chunksize + self.compresslevel = None + self.zip64 = zip64 + self.compression_method = compression_method self.outzip = None self._deferred = [] @@ -1131,7 +1148,8 @@ def __call__(self, stream, method=None, encoding='utf-8', out=None): else: result = out self.outzip = zipfile.ZipFile( - result, mode='w', compression=zipfile.ZIP_DEFLATED) + result, mode='w', compression=self.compression_method, + compresslevel=self.compresslevel) files = {} now = time.localtime()[:6] manifest_info = None @@ -1152,7 +1170,7 @@ def __call__(self, stream, method=None, encoding='utf-8', out=None): else: self.outzip.writestr(f_info, self.inzip.read(f_info.filename)) - writer = _ZipWriteSplitStream(self.outzip, self.chunksize) + writer = _ZipWriteSplitStream(self.outzip, self.chunksize, self.zip64) output_encode( self.xml_serializer(writer(stream)), encoding=encoding, out=writer)