from django.core.validators import ValidationError
from lxml import etree
from StringIO import StringIO
from django.utils.translation import gettext_lazy as _
import re

class RelaxNG(object):
	"Validate against a Relax NG schema"
	def __init__(self, schema_path, additional_root_element=None):
		self.schema_path = schema_path
		self.additional_root_element = additional_root_element
	
	def raiseValidationError(self, field_data, error_log):
		display_errors = []
		lines = field_data.split('\n')
		for error in error_log:
			# Scrape the lxml error messages to reword them more nicely.
			m = re.search(r'Opening and ending tag mismatch: (.+?) line (\d+?) and (.+?)', error.message)
			if m:
				display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \
					{'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]})
				continue
			m = re.search(r'Did not expect text in element (.+?) content', error.message)
			if m:
				display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \
					{'line':error.line, 'start':lines[int(error.line) - 1][:30]})
				continue
			m = re.search(r'Invalid attribute (.+?) for element (.+?)', error.message)
			if m:
				display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \
					{'attr':m.group(1), 'line':error.line, 'start':lines[int(error.line) - 1][:30]})
				continue
			m = re.search(r'Did not expect element (.+?) there', error.message)
			if m:
				display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \
					{'tag':m.group(1), 'line':error.line, 'start':lines[int(error.line) - 1][:30]})
				continue
			m = re.search(r'Element (.+?) failed to validate attributes', error.message)
			if m:
				display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \
					{'line':error.line, 'start':lines[int(error.line) - 1][:30]})
				continue
			m = re.search(r'Invalid attribute (.+?) for element (.+?)', error.message)
			if m:
				display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \
					{'attr':m.group(1), 'line':error.line, 'start':lines[int(error.line) - 1][:30]})
				continue
			# Failing all those checks, use the default error message.
			display_errors.append('Line %s: %s [%s]' % (error.line, error.message, error.level_name))
		raise ValidationError, display_errors
	
	def __call__(self, field_data, all_data):
		self.errors = []
		if self.additional_root_element:
			field_data = '<%(are)s>%(data)s\n</%(are)s>' % {
				'are': self.additional_root_element,
				'data': field_data
			}
		
		etree.clearErrorLog()
		try:
			doc = etree.parse(StringIO(field_data))
		except etree.XMLSyntaxError, e:
			self.raiseValidationError(field_data, e.error_log)
		etree.clearErrorLog()
		try:
			rng_doc = etree.parse(self.schema_path)
			rng = etree.RelaxNG(rng_doc)
		except (etree.XMLSyntaxError, etree.RelaxNGParseError), e:
			import os.path
			raise ValidationError, [_("Could not load %s for validation, please contact the admin") % os.path.basename(self.schema_path)]
		if not rng(doc):
			self.raiseValidationError(field_data, rng.error_log)

