#!/usr/bin/env python # -*- coding: utf-8 -*- from secretary import Renderer from lxml import etree from xml.dom.minidom import 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) # try to fix xml with mismatched tags parser = etree.XMLParser(recover=True) recovered_xml = etree.fromstring(result.encode("ascii", "xmlcharrefreplace"), parser) final_xml = parseString(etree.tostring(recovered_xml)) 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 - 500 : e.offset + 500] 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")