Package xmlschema_acue :: Package validators :: Module identities

Source Code for Module xmlschema_acue.validators.identities

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (c), 2016-2019, SISSA (International School for Advanced Studies). 
  4  # All rights reserved. 
  5  # This file is distributed under the terms of the MIT License. 
  6  # See the file 'LICENSE' in the root directory of the present 
  7  # distribution, or http://opensource.org/licenses/MIT. 
  8  # 
  9  # @author Davide Brunato <brunato@sissa.it> 
 10  # 
 11  """ 
 12  This module contains classes for other XML Schema identity constraints. 
 13  """ 
 14  from __future__ import unicode_literals 
 15  import re 
 16  from collections import Counter 
 17  from elementpath import Selector, XPath1Parser, ElementPathSyntaxError, ElementPathKeyError 
 18   
 19  from xmlschema_acue.exceptions import XMLSchemaValueError 
 20  from xmlschema_acue.qnames import XSD_UNIQUE, XSD_KEY, XSD_KEYREF, XSD_SELECTOR, XSD_FIELD 
 21  from xmlschema_acue.helpers import get_qname, qname_to_prefixed 
 22  from xmlschema_acue.etree import etree_getpath 
 23  from xmlschema_acue.regex import get_python_regex 
 24   
 25  from xmlschema_acue.validators.exceptions import XMLSchemaValidationError 
 26  from xmlschema_acue.validators.xsdbase import XsdComponent 
 27   
 28  XSD_IDENTITY_XPATH_SYMBOLS = { 
 29      'processing-instruction', 'following-sibling', 'preceding-sibling', 
 30      'ancestor-or-self', 'attribute', 'following', 'namespace', 'preceding', 
 31      'ancestor', 'position', 'comment', 'parent', 'child', 'false', 'text', 'node', 
 32      'true', 'last', 'not', 'and', 'mod', 'div', 'or', '..', '//', '!=', '<=', '>=', '(', ')', 
 33      '[', ']', '.', '@', ',', '/', '|', '*', '-', '=', '+', '<', '>', ':', '(end)', '(name)', 
 34      '(string)', '(float)', '(decimal)', '(integer)', '::' 
 35  } 
36 37 38 -class XsdIdentityXPathParser(XPath1Parser):
39 symbol_table = {k: v for k, v in XPath1Parser.symbol_table.items() if k in XSD_IDENTITY_XPATH_SYMBOLS} 40 SYMBOLS = XSD_IDENTITY_XPATH_SYMBOLS
41 42 43 XsdIdentityXPathParser.build_tokenizer()
44 45 46 -class XsdSelector(XsdComponent):
47 _admitted_tags = {XSD_SELECTOR} 48 pattern = re.compile(get_python_regex( 49 r"(\.//)?(((child::)?((\i\c*:)?(\i\c*|\*)))|\.)(/(((child::)?((\i\c*:)?(\i\c*|\*)))|\.))*(\|" 50 r"(\.//)?(((child::)?((\i\c*:)?(\i\c*|\*)))|\.)(/(((child::)?((\i\c*:)?(\i\c*|\*)))|\.))*)*" 51 )) 52
53 - def __init__(self, elem, schema, parent):
54 super(XsdSelector, self).__init__(elem, schema, parent)
55
56 - def _parse(self):
57 super(XsdSelector, self)._parse() 58 try: 59 self.path = self.elem.attrib['xpath'] 60 except KeyError: 61 self.parse_error("'xpath' attribute required:", self.elem) 62 self.path = "*" 63 else: 64 if not self.pattern.match(self.path.replace(' ', '')): 65 self.parse_error("Wrong XPath expression for an xs:selector") 66 67 try: 68 self.xpath_selector = Selector(self.path, self.namespaces, parser=XsdIdentityXPathParser) 69 except (ElementPathSyntaxError, ElementPathKeyError) as err: 70 self.parse_error(err) 71 self.xpath_selector = Selector('*', self.namespaces, parser=XsdIdentityXPathParser) 72 73 # XSD 1.1 xpathDefaultNamespace attribute 74 if self.schema.XSD_VERSION > '1.0': 75 if 'xpathDefaultNamespace' in self.elem.attrib: 76 self.xpath_default_namespace = self._parse_xpath_default_namespace(self.elem) 77 else: 78 self.xpath_default_namespace = self.schema.xpath_default_namespace
79
80 - def __repr__(self):
81 return '%s(path=%r)' % (self.__class__.__name__, self.path)
82 83 @property
84 - def built(self):
85 return True
86
87 88 -class XsdFieldSelector(XsdSelector):
89 _admitted_tags = {XSD_FIELD} 90 pattern = re.compile(get_python_regex( 91 r"(\.//)?((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)/)*((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)|" 92 r"((attribute::|@)((\i\c*:)?(\i\c*|\*))))(\|(\.//)?((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)/)*" 93 r"((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)|((attribute::|@)((\i\c*:)?(\i\c*|\*)))))*" 94 ))
95
96 97 -class XsdIdentity(XsdComponent):
98 - def __init__(self, elem, schema, parent):
99 super(XsdIdentity, self).__init__(elem, schema, parent)
100
101 - def _parse(self):
102 super(XsdIdentity, self)._parse() 103 elem = self.elem 104 try: 105 self.name = get_qname(self.target_namespace, elem.attrib['name']) 106 except KeyError: 107 self.parse_error("missing required attribute 'name'", elem) 108 self.name = None 109 110 child = self._parse_component(elem, required=False, strict=False) 111 if child is None or child.tag != XSD_SELECTOR: 112 self.parse_error("missing 'selector' declaration.", elem) 113 self.selector = None 114 else: 115 self.selector = XsdSelector(child, self.schema, self) 116 117 self.fields = [] 118 for child in self._iterparse_components(elem, start=int(self.selector is not None)): 119 if child.tag == XSD_FIELD: 120 self.fields.append(XsdFieldSelector(child, self.schema, self)) 121 else: 122 self.parse_error("element %r not allowed here:" % child.tag, elem)
123
124 - def iter_elements(self):
125 for xsd_element in self.selector.xpath_selector.iter_select(self.parent): 126 yield xsd_element
127
128 - def get_fields(self, context, decoders=None):
129 """ 130 Get fields for a schema or instance context element. 131 132 :param context: Context Element or XsdElement 133 :param decoders: Context schema fields decoders. 134 :return: A tuple with field values. An empty field is replaced by `None`. 135 """ 136 fields = [] 137 for k, field in enumerate(self.fields): 138 result = field.xpath_selector.select(context) 139 if not result: 140 if isinstance(self, XsdKey): 141 raise XMLSchemaValueError("%r key field must have a value!" % field) 142 else: 143 fields.append(None) 144 elif len(result) == 1: 145 if decoders is None or decoders[k] is None: 146 fields.append(result[0]) 147 else: 148 fields.append(decoders[k].decode(result[0], validation="skip")) 149 else: 150 raise XMLSchemaValueError("%r field selects multiple values!" % field) 151 return tuple(fields)
152
153 - def iter_values(self, elem):
154 """ 155 Iterate field values, excluding empty values (tuples with all `None` values). 156 157 :param elem: Instance XML element. 158 :return: N-Tuple with value fields. 159 """ 160 current_path = '' 161 xsd_fields = None 162 for e in self.selector.xpath_selector.iter_select(elem): 163 path = etree_getpath(e, elem) 164 if current_path != path: 165 # Change the XSD context only if the path is changed 166 current_path = path 167 xsd_element = self.parent.find(path) 168 xsd_fields = self.get_fields(xsd_element) 169 170 if all(fld is None for fld in xsd_fields): 171 continue 172 173 try: 174 fields = self.get_fields(e, decoders=xsd_fields) 175 except XMLSchemaValueError as err: 176 yield XMLSchemaValidationError(self, e, reason=str(err)) 177 else: 178 if any(fld is not None for fld in fields): 179 yield fields
180 181 @property
182 - def built(self):
183 return self.selector.built and all([f.built for f in self.fields])
184 185 @property
186 - def validation_attempted(self):
187 if self.built: 188 return 'full' 189 elif self.selector.built or any([f.built for f in self.fields]): 190 return 'partial' 191 else: 192 return 'none'
193
194 - def __call__(self, *args, **kwargs):
195 for error in self.validator(*args, **kwargs): 196 yield error
197
198 - def validator(self, elem):
199 values = Counter() 200 for v in self.iter_values(elem): 201 if isinstance(v, XMLSchemaValidationError): 202 yield v 203 else: 204 values[v] += 1 205 206 for value, count in values.items(): 207 if count > 1: 208 yield XMLSchemaValidationError(self, elem, reason="duplicated value %r." % value)
209
210 211 -class XsdUnique(XsdIdentity):
212 _admitted_tags = {XSD_UNIQUE}
213
214 215 -class XsdKey(XsdIdentity):
216 _admitted_tags = {XSD_KEY}
217
218 219 -class XsdKeyref(XsdIdentity):
220 """ 221 Implementation of xs:keyref. 222 223 :ivar refer: reference to a *xs:key* declaration that must be in the same element \ 224 or in a descendant element. 225 """ 226 _admitted_tags = {XSD_KEYREF} 227 refer = None 228 refer_path = '.' 229
230 - def __repr__(self):
231 return '%s(name=%r, refer=%r)' % ( 232 self.__class__.__name__, self.prefixed_name, getattr(self.refer, 'prefixed_name', None) 233 )
234
235 - def _parse(self):
236 super(XsdKeyref, self)._parse() 237 try: 238 self.refer = self.schema.resolve_qname(self.elem.attrib['refer']) 239 except KeyError: 240 self.parse_error("missing required attribute 'refer'") 241 except ValueError as err: 242 self.parse_error(err)
243
244 - def parse_refer(self):
245 if self.refer is None: 246 return # attribute or key/unique identity constraint missing 247 elif isinstance(self.refer, (XsdKey, XsdUnique)): 248 return # referenced key/unique identity constraint already set 249 250 try: 251 self.refer = self.parent.constraints[self.refer] 252 except KeyError: 253 try: 254 self.refer = self.maps.constraints[self.refer] 255 except KeyError: 256 self.parse_error("key/unique identity constraint %r is missing" % self.refer) 257 return 258 259 if not isinstance(self.refer, (XsdKey, XsdUnique)): 260 self.parse_error("reference to a non key/unique identity constraint %r" % self.refer) 261 elif len(self.refer.fields) != len(self.fields): 262 self.parse_error("field cardinality mismatch between %r and %r" % (self, self.refer)) 263 elif self.parent is not self.refer.parent: 264 refer_path = self.refer.parent.get_path(ancestor=self.parent) 265 if refer_path is None: 266 # From a note in par. 3.11.5 Part 1 of XSD 1.0 spec: "keyref identity-constraints may be 267 # defined on domains distinct from the embedded domain of the identity-constraint they 268 # reference, or the domains may be the same but self-embedding at some depth. In either 269 # case the node table for the referenced identity-constraint needs to propagate upwards, 270 # with conflict resolution." 271 refer_path = self.parent.get_path(ancestor=self.refer.parent, reverse=True) 272 if refer_path is None: 273 refer_path = self.parent.get_path(reverse=True) + '/' + self.refer.parent.get_path() 274 275 self.refer_path = refer_path
276
277 - def get_refer_values(self, elem):
278 values = set() 279 for e in elem.iterfind(self.refer_path): 280 for v in self.refer.iter_values(e): 281 if not isinstance(v, XMLSchemaValidationError): 282 values.add(v) 283 return values
284
285 - def validator(self, elem):
286 if self.refer is None: 287 return 288 289 refer_values = None 290 for v in self.iter_values(elem): 291 if isinstance(v, XMLSchemaValidationError): 292 yield v 293 continue 294 295 if refer_values is None: 296 try: 297 refer_values = self.get_refer_values(elem) 298 except XMLSchemaValueError as err: 299 yield XMLSchemaValidationError(self, elem, str(err)) 300 continue 301 302 if v not in refer_values: 303 reason = "Key %r with value %r not found for identity constraint of element %r." \ 304 % (self.prefixed_name, v, qname_to_prefixed(elem.tag, self.namespaces)) 305 yield XMLSchemaValidationError(validator=self, obj=elem, reason=reason)
306