diff options
-rw-r--r-- | ishtar_common/models.py | 5 | ||||
-rw-r--r-- | ishtar_common/ooo_replace.py | 186 |
2 files changed, 189 insertions, 2 deletions
diff --git a/ishtar_common/models.py b/ishtar_common/models.py index eb643ff1c..6c153fb9c 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -48,7 +48,7 @@ from django.contrib import admin from simple_history.models import HistoricalRecords as BaseHistoricalRecords -from oook_replace.oook_replace import oook_replace +from ishtar_common.ooo_replace import ooo_replace from ishtar_common.model_merging import merge_model_objects from ishtar_common.utils import get_cache @@ -861,7 +861,8 @@ class DocumentTemplate(models.Model): slugify(self.name.replace(' ', '_').lower()) + u'-' +\ datetime.date.today().strftime('%Y-%m-%d') +\ u"." + self.template.name.split('.')[-1] - missing = oook_replace(self.template, output_name, c_object.get_values()) + values = c_object.get_values() + missing = ooo_replace(self.template, output_name, values) return output_name class Department(models.Model): diff --git a/ishtar_common/ooo_replace.py b/ishtar_common/ooo_replace.py new file mode 100644 index 000000000..4c487dd17 --- /dev/null +++ b/ishtar_common/ooo_replace.py @@ -0,0 +1,186 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (C) 2013-2015 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# See the file COPYING for details. + +import locale, re +from zipfile import ZipFile, ZIP_DEFLATED +from cStringIO import StringIO +from xml.etree.cElementTree import ElementTree, fromstring + +from django.conf import settings + +OOO_NS = "{urn:oasis:names:tc:opendocument:xmlns:text:1.0}" + +def _set_value_from_formula(value, context, default_value): + value = value.strip() + if value.startswith("ooow:") and len(value) >= 5: + value = value[5:] + if value.startswith('"') and value.endswith('"') and len(value) > 1: + value = value[1:-1] + elif value in context: + value = _format_value(context[value], default_value) + else: + value = None + return value + +def _parse_condition(condition, context, default_value): + # parse only == and != operator + operator = "" + if "!=" in condition: + operator = "!=" + elif "==" in condition: + operator = "==" + else: + return + var1, var2 = condition.split(operator) + var1 = _set_value_from_formula(var1, context, default_value) + var2 = _set_value_from_formula(var2, context, default_value) + res = var1 == var2 + if operator == '!=': + res = not res + return res + +def _format_value(value, default_value): + if hasattr(value, 'strftime'): + c_locale = settings.LANGUAGE_CODE.split('-') + if len(c_locale) == 2: + c_locale[1] = c_locale[1].upper() + c_locale = "_".join(c_locale) + if locale.getlocale()[0] != c_locale: + for loc in (c_locale, c_locale+'.utf8'): + try: + locale.setlocale(locale.LC_ALL, loc) + break + except: + pass + try: + if settings.DATE_FORMAT: + value = value.strftime(settings.DATE_FORMAT).lower() + else: + value = value.strftime('%x') + except ValueError: + value = unicode(value) + if locale.getlocale()[1]: + value = value.decode(locale.getlocale()[1]) + value = unicode(value) if value else default_value + return value + +VAR_EXPR = u"###%sVAR %s###" +WHOLE_KEY_FILTER = u"((?:(?: )*(?:<[^#>]*>)*(?: )*(?:[-a-zA-Z0-9_])*(?: )*)*)" +WHOLE_KEY_FILTER = u"([^#]*)" +RE_VAR = re.compile(VAR_EXPR % (WHOLE_KEY_FILTER, WHOLE_KEY_FILTER)) +IF_EXPR = u"###%sIF %s###(.*)###ENDIF###" +RE_IF = re.compile(IF_EXPR % (WHOLE_KEY_FILTER, WHOLE_KEY_FILTER)) +TAG_FILTER = re.compile(u"(<[^<^>]*>)") +KEY_FILTER = re.compile(u"([-a-zA-Z0-9_]*)") + +def _filter_key(base_key): + # return (key, extra_marker) + # manage strange key such as: + # test_<text:span text:style-name="T1">date</text:span> + key = base_key[:] + key = key.strip() + tags, new_key = '', key[:] + for tag in TAG_FILTER.findall(key): + tags += tag + new_key = new_key.replace(tag, '') + full_key = '' + for k in KEY_FILTER.findall(new_key): + if not k: + continue + full_key += k + return full_key, tags + +def _custom_parsing(context, value, default_value=''): + """ + ###VAR nom_var### for displaying a variable name + ###IF nom_var### ###ENDIF### for conditionnal display + Be carreful nested condition are not yet managed! + """ + for regexp, sub_exp, if_cond in ((RE_IF, IF_EXPR, True), + (RE_VAR, VAR_EXPR, False)): + for base_key in regexp.findall(value[:]): + v, val = "", None + if if_cond: # the value inside the if is parsed + pre_tag, base_key, val = base_key + else: + pre_tag, base_key = base_key + key, extra_markers = _filter_key(base_key) + v = '' + if pre_tag: + v = pre_tag + if key in context and context[key]: + if if_cond: + v += _custom_parsing(context, val, default_value) + else: + v += _format_value(context[key], default_value) + # to preserve a consistent OOO file put extra_markers + if extra_markers: + v += extra_markers + value = re.sub(sub_exp % (pre_tag, base_key), v, value) + return value + +def ooo_replace(infile, outfile, context, default_value=''): + inzip = ZipFile(infile, 'r', ZIP_DEFLATED) + outzip = ZipFile(outfile, 'w', ZIP_DEFLATED) + + # regular ooo parsing + content = ElementTree(fromstring(inzip.read('content.xml'))) + missing_keys = set() + for xp in ('variable-set', 'variable-get'): + for p in content.findall(".//"+OOO_NS+xp): + name = p.get(OOO_NS+"name") + if name in context: + value = context[name] + p.text = _format_value(value, default_value) + else: + if default_value != None: + p.text = default_value + missing_keys.add(name) + for p in content.findall(".//"+OOO_NS+"conditional-text"): + condition = p.get(OOO_NS+"condition") + res = 'true' if _parse_condition(condition, context, default_value) \ + else 'false' + value = p.get(OOO_NS+'string-value-if-' + res) + value = _format_value(value, default_value) + if value.strip() in context: + value = context[value.strip()] + p.text = value + + # raw content parsing + str_io = StringIO() + content.write(str_io) + value = str_io.getvalue() + value = _custom_parsing(context, value, default_value).encode('utf-8') + + for f in inzip.infolist(): + if f.filename == 'content.xml': + outzip.writestr('content.xml', value) + else: + outzip.writestr(f, inzip.read(f.filename)) + inzip.close() + outzip.close() + return missing_keys + +if __name__ == '__main__': + infile = "../archaeological_files/tests/AR_dossier_DRAC_modele_ishtar_1-MOD.odt" + outfile = "../archaeological_files/tests/AR_dossier_DRAC_modele_ishtar-test.odt" + rep = {"file_incharge_surname":u"Yann", + "file_incharge_name":u"Le Jeune", + "fileact_ref":u"ref"} + ooo_replace(infile, outfile, rep, default_value="") |