Django

Code

Ticket #3094: validators.3.py

File validators.3.py, 5.4 kB (added by David Danier <goliath.mailinglist@gmx.de>, 4 months ago)
Line 
1 from StringIO import StringIO
2 from django.utils.translation import ugettext as _
3 from lxml import etree
4 import re
5
6 # example XmlField:
7 #class XmlField(forms.CharField):
8 #       widget = forms.Textarea
9 #       
10 #       def __init__(self, schema_path=None, additional_root_element=None, *args, **kwargs):
11 #               super(XmlField, self).__init__(*args, **kwargs)
12 #               self.additional_root_element = additional_root_element
13 #               self.schema_path = schema_path
14 #       
15 #       def clean(self, value):
16 #               from ....validators import RelaxNGValidator
17 #               super(XmlField, self).clean(value)
18 #               schema_path = self.schema_path
19 #               xml_validator = RelaxNGValidator(schema_path, self.additional_root_element)
20 #               return xml_validator.forms_validate(value)
21
22 # copy of django.core.validators.ValidationError, cut most of the code out
23 # needed to throw different exceptions for new/old-forms
24 class ValidationError(Exception):
25         def __init__(self, messages): # messages must be a list
26                 self.messages = messages
27
28 # for rnc-files see:
29 # rnc2rng: http://www.gnosis.cx/download/relax/
30 class RelaxNGValidator(object):
31         "Validate against a Relax NG schema"
32         def __init__(self, schema_path, additional_root_element=None):
33                 self.schema_path = schema_path
34                 self.additional_root_element = additional_root_element
35        
36         def raiseValidationError(self, xml_data, error_log):
37                 display_errors = []
38                 if self.additional_root_element:
39                         adjust_line = -1
40                 else:
41                         adjust_line = 0
42                 lines = xml_data.split('\n')
43                 for error in error_log:
44                         # Scrape the lxml error messages to reword them more nicely.
45                         m = re.search(r'Opening and ending tag mismatch: (.+?) line (\d+?) and (.+?)$', error.message)
46                         if m:
47                                 display_errors.append(_(u'Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \
48                                         {'tag':m.group(1).replace('/', ''), 'line':int(m.group(2)) + adjust_line, 'start':lines[int(m.group(2)) - 1][:30]})
49                                 continue
50                         m = re.search(r'Did not expect text in element (.+?) content', error.message)
51                         if m:
52                                 display_errors.append(_(u'Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \
53                                         {'line':error.line + adjust_line, 'start':lines[int(error.line) - 1][:30]})
54                                 continue
55                         m = re.search(r'Specification mandate value for attribute (.+?)$', error.message)
56                         if m:
57                                 display_errors.append(_(u'"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \
58                                         {'attr':m.group(1), 'line':error.line + adjust_line, 'start':lines[int(error.line) - 1][:30]})
59                                 continue
60                         m = re.search(r'Invalid attribute (.+?) for element (.+?)$', error.message)
61                         if m:
62                                 display_errors.append(_(u'"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \
63                                         {'attr':m.group(1), 'line':error.line + adjust_line, 'start':lines[int(error.line) - 1][:30]})
64                                 continue
65                         m = re.search(r'Did not expect element (.+?) there', error.message)
66                         if m:
67                                 display_errors.append(_(u'"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \
68                                         {'tag':m.group(1), 'line':error.line + adjust_line, 'start':lines[int(error.line) - 1][:30]})
69                                 continue
70                         m = re.search(r'Element (.+?) failed to validate attributes', error.message)
71                         if m:
72                                 display_errors.append(_(u'A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \
73                                         {'line':error.line + adjust_line, 'start':lines[int(error.line) - 1][:30]})
74                                 continue
75                         m = re.search(r'Invalid attribute (.+?) for element (.+?)$', error.message)
76                         if m:
77                                 display_errors.append(_(u'The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \
78                                         {'attr':m.group(1), 'line':error.line + adjust_line, 'start':lines[int(error.line) - 1][:30]})
79                                 continue
80                         # Failing all those checks, use the default error message.
81                         display_errors.append(u'Line %s: %s [%s]' % (error.line + adjust_line, error.message, error.level_name))
82                 raise ValidationError, display_errors
83        
84         def validate(self, xml_data):
85                 self.errors = []
86                 if self.additional_root_element:
87                         xml_data = '<%(are)s>\n%(data)s\n</%(are)s>' % {
88                                 'are': self.additional_root_element,
89                                 'data': xml_data
90                         }
91                
92                 etree.clearErrorLog()
93                 try:
94                         doc = etree.parse(StringIO(xml_data))
95                 except etree.XMLSyntaxError, e:
96                         self.raiseValidationError(xml_data, e.error_log)
97                 etree.clearErrorLog()
98                
99                 schema_path = self.schema_path
100                 if schema_path:
101                         if not schema_path[0] == '/':
102                                 import os.path
103                                 schema_path = os.path.join(os.path.dirname(__file__), "rng/%s" % schema_path)
104                        
105                         try:
106                                 rng_doc = etree.parse(schema_path)
107                                 rng = etree.RelaxNG(rng_doc)
108                         except (etree.XMLSyntaxError, etree.RelaxNGParseError), e:
109                                 import os.path
110                                 raise ValidationError, [_(u"Could not load %s for validation, please contact the admin") % os.path.basename(self.schema_path)]
111                         if not rng(doc):
112                                 self.raiseValidationError(xml_data, rng.error_log)
113        
114         # oldforms-way of using this
115         def __call__(self, field_data, all_data):
116                 from django.core.validators import ValidationError as CoreValidationError
117                 try:
118                         self.validate(field_data)
119                 except ValidationError, e:
120                         raise CoreValidationError(e.messages)
121        
122         # newforms-way of using this
123         def forms_validate(self, value):
124                 from django.newforms.util import ValidationError as FormsValidationError
125                 from django.utils.encoding import smart_unicode
126                 try:
127                         self.validate(value)
128                 except ValidationError, e:
129                         raise FormsValidationError(e.messages)
130                 return smart_unicode(value)