Skip to content

Commit

Permalink
feat(engines): add basic excel validation
Browse files Browse the repository at this point in the history
  • Loading branch information
Jean-Louis Fuchs committed Jun 14, 2022
1 parent ca54651 commit f396ae8
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 3 deletions.
1 change: 1 addition & 0 deletions document_merge_service/api/data/xlsx-not-valid.xlsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
asdf
Binary file added document_merge_service/api/data/xlsx-syntax.xlsx
Binary file not shown.
37 changes: 35 additions & 2 deletions document_merge_service/api/engines.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,13 @@ def merge(self, data, buf):
return buf


_placeholder_match = re.compile(r"^\s*{{\s*([^{}]+)\s*}}\s*$")


class XlsxTemplateEngine:
def __init__(self, template):
self.template = template
self.writer = None

def validate_is_xlsx(self):
try:
Expand All @@ -201,10 +205,39 @@ def validate(self, available_placeholders=None, sample_data=None):
self.validate_template_syntax(available_placeholders, sample_data)

def validate_template_syntax(self, available_placeholders=None, sample_data=None):
pass
# We cannot use jinja to validate because xltpl uses jinja's lexer directly
if not sample_data:
sample_data = {}
buf = io.BytesIO()
try:
self.merge(sample_data, buf)
except TemplateSyntaxError as exc:
arg_str = ";".join(exc.args)
raise exceptions.ValidationError(f"Syntax error in template: {arg_str}")
if available_placeholders:
placeholders = []
for sheet in self.writer.sheet_resource_map.sheet_state_list:
if not sheet.sheet_resource:
continue
tree = sheet.sheet_resource.sheet_tree
self.collect_placeholders(tree._children, placeholders)
missing = set(available_placeholders) - set(placeholders)
if missing:
raise exceptions.ValidationError(
f"Template uses unavailable placeholders: {str(missing)}"
)

def collect_placeholders(self, children, placeholders):
for child in children:
if hasattr(child, "value"):
value = str(child.value)
re_match = _placeholder_match.match(value)
if re_match:
placeholders.append(re_match.group(1))
self.collect_placeholders(child._children, placeholders)

def merge(self, data, buf):
writer = BookWriter(self.template)
self.writer = writer = BookWriter(self.template)

writer.jinja_env.filters.update(get_jinja_filters())
writer.jinja_env.globals.update(dir=dir, getattr=getattr)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import io

import openpyxl
import pytest
from rest_framework import exceptions

from ..data import django_file
from ..engines import XlsxTemplateEngine
Expand All @@ -17,6 +19,8 @@
],
}

_available = ["key0", "key1.subkey1"]


def test_structure():
tmpl = django_file("xlsx-structure.xlsx")
Expand All @@ -31,3 +35,21 @@ def test_structure():
assert ws["A5"].value == "Item: mixed"
assert ws["A6"].value == "Item: list"
assert ws["A7"].value == "Subitem: xdata2"
engine.validate(_available, _structure)
_available.append("huhu")
with pytest.raises(exceptions.ValidationError):
engine.validate(_available, _structure)


def test_syntax_error():
tmpl = django_file("xlsx-syntax.xlsx")
engine = XlsxTemplateEngine(tmpl)
with pytest.raises(exceptions.ValidationError):
engine.validate(_available, _structure)


def test_valid_error():
tmpl = django_file("xlsx-not-valid.xlsx")
engine = XlsxTemplateEngine(tmpl)
with pytest.raises(exceptions.ParseError):
engine.validate(_available, _structure)
2 changes: 1 addition & 1 deletion document_merge_service/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
django_root = environ.Path(__file__) - 2

ENV_FILE = env.str("ENV_FILE", default=django_root(".env"))
if os.path.exists(ENV_FILE):
if os.path.exists(ENV_FILE): # pragma: no cover
environ.Env.read_env(ENV_FILE)

# per default production is enabled for security reasons
Expand Down

0 comments on commit f396ae8

Please sign in to comment.