#!/usr/bin/env python3 # -*- coding: utf-8 -*- import re from secretary import Renderer from lxml import etree from xml.dom.minidom import parseString from xml.parsers.expat import ExpatError, ErrorString from PIL import Image from django.conf import settings from .jinja_filters import capfirst_filter, capitalize_filter, \ euro_format, float_format, human_date_filter, lowerfirst_filter, \ number_to_words, replace_line_breaks, short_date_filter 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 add_filter(value1, value2): try: return float(value1 or 0) + float(value2 or 0) except ValueError: return 0 def sub_filter(value1, value2): try: return float(value1 or 0) - float(value2 or 0) except ValueError: return 0 def multiply_filter(value1, value2): try: return float(value1 or 0) * float(value2 or 0) except ValueError: return 0 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["float_format"] = float_format self.environment.filters["euro_format"] = euro_format self.environment.filters["number_to_words"] = number_to_words self.environment.filters["replace_line_breaks"] = replace_line_breaks self.environment.filters["splitpart"] = splitpart self.environment.filters["multiply"] = multiply_filter self.environment.filters["add"] = add_filter self.environment.filters["sub"] = sub_filter self.environment.filters["short_date"] = short_date_filter 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")