#!/usr/bin/env python # -*- coding: utf-8 -*- from secretary import Renderer, parseString from xml.parsers.expat import ExpatError, ErrorString from datetime import datetime import locale from PIL import Image import re from django.conf import settings RE_UNITS = re.compile("([.0-9]+)([a-z]+)") def parse_value_unit(value): m = RE_UNITS.match(value) if not m: return None, None value, unit = m.groups() value = float(value) return value, unit def replace_line_breaks(value): return (value or "").replace("\r\n", "\n") def capfirst_filter(value): return value[0].upper() + value[1:] if value else value def lowerfirst_filter(value): return value[0].lower() + value[1:] if value else value RE_CAP = re.compile(r"[^-' ]+") SEP = ("un", "une", "le", "la", "les", "lez", "d", "l", "de", "des", "du") def capitalize_filter(value): if not value: return "" value = value.lower() res = "" for m in RE_CAP.finditer(value): start = m.start() if start: res += value[start - 1] v = m.group() if v not in SEP: v = v[0].upper() + v[1:] res += v return res def human_date_filter(value): try: value = datetime.strptime(value, "%Y-%m-%d") except ValueError: return "" language_code = settings.LANGUAGE_CODE.split("-") language_code = language_code[0] + "_" + language_code[1].upper() for language_suffix in (".utf8", ""): try: locale.setlocale(locale.LC_TIME, language_code + language_suffix) break except locale.Error: pass return value.strftime(settings.DATE_FORMAT) def splitpart(value, index, index_end=None, char=",", merge_character=None): if index_end: try: index_end = int(index_end) if not merge_character: # merge is assumed merge_character = char except ValueError: # old filter use - manage compatibility merge_character = char char = index_end index_end = None if not value or (not index and index != 0): return "" if merge_character is True: # old filter use merge_character = char splited = value.split(char) if len(splited) <= index: return "" if not merge_character: return splited[index] if index_end: splited = splited[index:index_end] else: splited = splited[index:] return merge_character.join(splited) class IshtarSecretaryRenderer(Renderer): def __init__(self, *args, **kwargs): super(IshtarSecretaryRenderer, self).__init__(*args, **kwargs) self.media_callback = self.ishtar_media_loader self.media_path = settings.MEDIA_ROOT self.environment.filters["human_date"] = human_date_filter self.environment.filters["capfirst"] = capfirst_filter self.environment.filters["lowerfirst"] = lowerfirst_filter self.environment.filters["capitalize"] = capitalize_filter self.environment.filters["replace_line_breaks"] = replace_line_breaks self.environment.filters["splitpart"] = splitpart def ishtar_media_loader(self, media, *args, **kwargs): res = self.fs_loader(media, *args, **kwargs) if not res or not res[0]: return image_file, mime = res if "width" in kwargs: kwargs["frame_attrs"]["svg:width"] = kwargs["width"] if "height" in kwargs: kwargs["frame_attrs"]["svg:height"] = kwargs["height"] if "keep_ratio" in args: image = Image.open(image_file.name) width, width_unit = parse_value_unit(kwargs["frame_attrs"]["svg:width"]) height, height_unit = parse_value_unit(kwargs["frame_attrs"]["svg:height"]) if "height" not in kwargs and width: new_height = width * image.height / image.width kwargs["frame_attrs"]["svg:height"] = "{}{}".format( new_height, width_unit ) if "width" not in kwargs and height: new_width = height * image.width / image.height kwargs["frame_attrs"]["svg:width"] = "{}{}".format( new_width, height_unit ) return image_file, mime def _render_xml(self, xml_document, **kwargs): # Prepare the xml object to be processed by jinja2 self.log.debug("Rendering XML object") template_string = "" try: self.template_images = dict() self._prepare_document_tags(xml_document) xml_source = xml_document.toxml() xml_source = xml_source.encode("ascii", "xmlcharrefreplace") jinja_template = self.environment.from_string( self._unescape_entities(xml_source.decode("utf-8")) ) result = jinja_template.render(**kwargs) final_xml = parseString(result.encode("ascii", "xmlcharrefreplace")) if self.template_images: self.replace_images(final_xml) return final_xml except ExpatError as e: if "result" not in locals(): result = xml_source ### changes try: near = result.split("\n")[e.lineno - 1][e.offset - 200 : e.offset + 200] except IndexError: near = "..." print(result) ### endchanges raise ExpatError( 'ExpatError "%s" at line %d, column %d\nNear of: "[...]%s[...]"' % (ErrorString(e.code), e.lineno, e.offset, near) ) except: self.log.error( "Error rendering template:\n%s", xml_document.toprettyxml(), exc_info=True, ) self.log.error("Unescaped template was:\n{0}".format(template_string)) raise finally: self.log.debug("Rendering xml object finished")