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