| 1 |
from django.core.validators import ValidationError |
|---|
| 2 |
from lxml import etree |
|---|
| 3 |
from StringIO import StringIO |
|---|
| 4 |
from django.utils.translation import gettext_lazy as _ |
|---|
| 5 |
import re |
|---|
| 6 |
|
|---|
| 7 |
class RelaxNG(object): |
|---|
| 8 |
"Validate against a Relax NG schema" |
|---|
| 9 |
def __init__(self, schema_path, additional_root_element=None): |
|---|
| 10 |
self.schema_path = schema_path |
|---|
| 11 |
self.additional_root_element = additional_root_element |
|---|
| 12 |
|
|---|
| 13 |
def raiseValidationError(self, field_data, error_log): |
|---|
| 14 |
display_errors = [] |
|---|
| 15 |
lines = field_data.split('\n') |
|---|
| 16 |
for error in error_log: |
|---|
| 17 |
# Scrape the lxml error messages to reword them more nicely. |
|---|
| 18 |
m = re.search(r'Opening and ending tag mismatch: (.+?) line (\d+?) and (.+?)', error.message) |
|---|
| 19 |
if m: |
|---|
| 20 |
display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \ |
|---|
| 21 |
{'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]}) |
|---|
| 22 |
continue |
|---|
| 23 |
m = re.search(r'Did not expect text in element (.+?) content', error.message) |
|---|
| 24 |
if m: |
|---|
| 25 |
display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \ |
|---|
| 26 |
{'line':error.line, 'start':lines[int(error.line) - 1][:30]}) |
|---|
| 27 |
continue |
|---|
| 28 |
m = re.search(r'Invalid attribute (.+?) for element (.+?)', error.message) |
|---|
| 29 |
if m: |
|---|
| 30 |
display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \ |
|---|
| 31 |
{'attr':m.group(1), 'line':error.line, 'start':lines[int(error.line) - 1][:30]}) |
|---|
| 32 |
continue |
|---|
| 33 |
m = re.search(r'Did not expect element (.+?) there', error.message) |
|---|
| 34 |
if m: |
|---|
| 35 |
display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \ |
|---|
| 36 |
{'tag':m.group(1), 'line':error.line, 'start':lines[int(error.line) - 1][:30]}) |
|---|
| 37 |
continue |
|---|
| 38 |
m = re.search(r'Element (.+?) failed to validate attributes', error.message) |
|---|
| 39 |
if m: |
|---|
| 40 |
display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \ |
|---|
| 41 |
{'line':error.line, 'start':lines[int(error.line) - 1][:30]}) |
|---|
| 42 |
continue |
|---|
| 43 |
m = re.search(r'Invalid attribute (.+?) for element (.+?)', error.message) |
|---|
| 44 |
if m: |
|---|
| 45 |
display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \ |
|---|
| 46 |
{'attr':m.group(1), 'line':error.line, 'start':lines[int(error.line) - 1][:30]}) |
|---|
| 47 |
continue |
|---|
| 48 |
# Failing all those checks, use the default error message. |
|---|
| 49 |
display_errors.append('Line %s: %s [%s]' % (error.line, error.message, error.level_name)) |
|---|
| 50 |
raise ValidationError, display_errors |
|---|
| 51 |
|
|---|
| 52 |
def __call__(self, field_data, all_data): |
|---|
| 53 |
self.errors = [] |
|---|
| 54 |
if self.additional_root_element: |
|---|
| 55 |
field_data = '<%(are)s>%(data)s\n</%(are)s>' % { |
|---|
| 56 |
'are': self.additional_root_element, |
|---|
| 57 |
'data': field_data |
|---|
| 58 |
} |
|---|
| 59 |
|
|---|
| 60 |
etree.clearErrorLog() |
|---|
| 61 |
try: |
|---|
| 62 |
doc = etree.parse(StringIO(field_data)) |
|---|
| 63 |
except etree.XMLSyntaxError, e: |
|---|
| 64 |
self.raiseValidationError(field_data, e.error_log) |
|---|
| 65 |
etree.clearErrorLog() |
|---|
| 66 |
try: |
|---|
| 67 |
rng_doc = etree.parse(self.schema_path) |
|---|
| 68 |
rng = etree.RelaxNG(rng_doc) |
|---|
| 69 |
except (etree.XMLSyntaxError, etree.RelaxNGParseError), e: |
|---|
| 70 |
import os.path |
|---|
| 71 |
raise ValidationError, [_("Could not load %s for validation, please contact the admin") % os.path.basename(self.schema_path)] |
|---|
| 72 |
if not rng(doc): |
|---|
| 73 |
self.raiseValidationError(field_data, rng.error_log) |
|---|