| Home | Top | ← | → | Overview | Module | Class | Index | Help |
|
About |
|---|
|
||||
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 }
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()
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
55
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
82
83 @property
86
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
100
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
127
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
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
184
185 @property
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
197
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
213
217
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
231 return '%s(name=%r, refer=%r)' % (
232 self.__class__.__name__, self.prefixed_name, getattr(self.refer, 'prefixed_name', None)
233 )
234
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
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
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
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
| Home | Top | ← | → | Overview | Module | Class | Index | Help |
|
About |
|---|
| Copyright(C) 2019 Arno-Can Uestuensoez @Ingenieurbuero Arno-Can Uestuensoez | https://arnocan.wordpress.com |
| Generated by Epydoc 4.0.4 / Python-3.8 / fedora27 on Fri Dec 13 15:25:37 2019 | http://epydoc.sourceforge.net |