Package xmlschema_acue ::
Package validators ::
Module groups
1
2
3
4
5
6
7
8
9
10
11 """
12 This module contains classes for XML Schema model groups.
13 """
14 from __future__ import unicode_literals
15
16 from xmlschema_acue.compat import unicode_type
17 from xmlschema_acue.exceptions import XMLSchemaValueError
18 from xmlschema_acue.etree import etree_element
19 from xmlschema_acue.qnames import XSD_GROUP, XSD_SEQUENCE, XSD_ALL, XSD_CHOICE, XSD_COMPLEX_TYPE, \
20 XSD_ELEMENT, XSD_ANY, XSD_RESTRICTION, XSD_EXTENSION
21 from xmlschema_acue.helpers import get_qname, local_name
22 from xmlschema_acue.converters import XMLSchemaConverter
23
24 from xmlschema_acue.validators.exceptions import XMLSchemaValidationError, XMLSchemaChildrenValidationError
25 from xmlschema_acue.validators.xsdbase import ValidationMixin, XsdComponent, XsdType
26 from xmlschema_acue.validators.elements import XsdElement
27 from xmlschema_acue.validators.wildcards import XsdAnyElement
28 from xmlschema_acue.validators.models import MAX_MODEL_DEPTH, ParticleMixin, ModelGroup, ModelVisitor
29
30 ANY_ELEMENT = etree_element(
31 XSD_ANY,
32 attrib={
33 'namespace': '##any',
34 'processContents': 'lax',
35 'minOccurs': '0',
36 'maxOccurs': 'unbounded'
37 })
38
39
40 -class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
41 """
42 A class for XSD 1.0 model group definitions.
43
44 <group
45 id = ID
46 maxOccurs = (nonNegativeInteger | unbounded) : 1
47 minOccurs = nonNegativeInteger : 1
48 name = NCName
49 ref = QName
50 {any attributes with non-schema namespace . . .}>
51 Content: (annotation?, (all | choice | sequence)?)
52 </group>
53
54 <all
55 id = ID
56 maxOccurs = 1 : 1
57 minOccurs = (0 | 1) : 1
58 {any attributes with non-schema namespace . . .}>
59 Content: (annotation?, element*)
60 </all>
61
62 <choice
63 id = ID
64 maxOccurs = (nonNegativeInteger | unbounded) : 1
65 minOccurs = nonNegativeInteger : 1
66 {any attributes with non-schema namespace . . .}>
67 Content: (annotation?, (element | group | choice | sequence | any)*)
68 </choice>
69
70 <sequence
71 id = ID
72 maxOccurs = (nonNegativeInteger | unbounded) : 1
73 minOccurs = nonNegativeInteger : 1
74 {any attributes with non-schema namespace . . .}>
75 Content: (annotation?, (element | group | choice | sequence | any)*)
76 </sequence>
77 """
78 mixed = False
79 model = None
80 redefine = None
81 _admitted_tags = {
82 XSD_COMPLEX_TYPE, XSD_EXTENSION, XSD_RESTRICTION, XSD_GROUP, XSD_SEQUENCE, XSD_ALL, XSD_CHOICE
83 }
84
85 - def __init__(self, elem, schema, parent, name=None):
90
92 if self.name is None:
93 return '%s(model=%r, occurs=%r)' % (self.__class__.__name__, self.model, self.occurs)
94 elif self.ref is None:
95 return '%s(name=%r, model=%r, occurs=%r)' % (
96 self.__class__.__name__, self.prefixed_name, self.model, self.occurs
97 )
98 else:
99 return '%s(ref=%r, model=%r, occurs=%r)' % (
100 self.__class__.__name__, self.prefixed_name, self.model, self.occurs
101 )
102
104 group = object.__new__(self.__class__)
105 group.__dict__.update(self.__dict__)
106 group.errors = self.errors[:]
107 group._group = self._group[:]
108 return group
109
110 __copy__ = copy
111
113 super(XsdGroup, self)._parse()
114 self.clear()
115 elem = self.elem
116 self._parse_particle(elem)
117
118 if elem.tag == XSD_GROUP:
119
120 name = elem.get('name')
121 ref = elem.get('ref')
122 if name is None:
123 if ref is not None:
124
125 if self.parent is None:
126 self.parse_error("a group reference cannot be global")
127
128 try:
129 self.name = self.schema.resolve_qname(ref)
130 except ValueError as err:
131 self.parse_error(err, elem)
132 return
133
134 try:
135 xsd_group = self.schema.maps.lookup_group(self.name)
136 except KeyError:
137 self.parse_error("missing group %r" % self.prefixed_name)
138 xsd_group = self.schema.create_any_content_group(self, self.name)
139
140 if isinstance(xsd_group, tuple):
141
142 self.parse_error("Circular definitions detected for group %r:" % self.ref, xsd_group[0])
143 self.model = 'sequence'
144 self.mixed = True
145 self.append(XsdAnyElement(ANY_ELEMENT, self.schema, self))
146 else:
147 self.model = xsd_group.model
148 if self.model == 'all':
149 if self.max_occurs != 1:
150 self.parse_error("maxOccurs must be 1 for 'all' model groups")
151 if self.min_occurs not in (0, 1):
152 self.parse_error("minOccurs must be (0 | 1) for 'all' model groups")
153 if self.schema.XSD_VERSION == '1.0' and isinstance(self.parent, XsdGroup):
154 self.parse_error("in XSD 1.0 the 'all' model group cannot be nested")
155 self.append(xsd_group)
156 else:
157 self.parse_error("missing both attributes 'name' and 'ref'")
158 return
159 elif ref is None:
160
161 self.name = get_qname(self.target_namespace, name)
162 content_model = self._parse_component(elem)
163 if self.parent is not None:
164 self.parse_error("attribute 'name' not allowed for a local group")
165 else:
166 if 'minOccurs' in elem.attrib:
167 self.parse_error("attribute 'minOccurs' not allowed for a global group")
168 if 'maxOccurs' in elem.attrib:
169 self.parse_error("attribute 'maxOccurs' not allowed for a global group")
170 if 'minOccurs' in content_model.attrib:
171 self.parse_error(
172 "attribute 'minOccurs' not allowed for the model of a global group", content_model
173 )
174 if 'maxOccurs' in content_model.attrib:
175 self.parse_error(
176 "attribute 'maxOccurs' not allowed for the model of a global group", content_model
177 )
178 if content_model.tag not in {XSD_SEQUENCE, XSD_ALL, XSD_CHOICE}:
179 self.parse_error('unexpected tag %r' % content_model.tag, content_model)
180 return
181
182 else:
183 self.parse_error("found both attributes 'name' and 'ref'")
184 return
185 elif elem.tag in {XSD_SEQUENCE, XSD_ALL, XSD_CHOICE}:
186
187 if 'name' in elem.attrib:
188 self.parse_error("attribute 'name' not allowed for a local group")
189 content_model = elem
190 self.name = None
191 elif elem.tag in {XSD_COMPLEX_TYPE, XSD_EXTENSION, XSD_RESTRICTION}:
192 self.name = self.model = None
193 return
194 else:
195 self.parse_error('unexpected tag %r' % elem.tag, elem)
196 return
197
198 self._parse_content_model(elem, content_model)
199
200 - def _parse_content_model(self, elem, content_model):
201 self.model = local_name(content_model.tag)
202 if self.model == 'all':
203 if self.max_occurs != 1:
204 self.parse_error("maxOccurs must be 1 for 'all' model groups")
205 if self.min_occurs not in (0, 1):
206 self.parse_error("minOccurs must be (0 | 1) for 'all' model groups")
207
208 for child in self._iterparse_components(content_model):
209 if child.tag == XSD_ELEMENT:
210
211 self.append((child, self.schema))
212 elif content_model.tag == XSD_ALL:
213 self.parse_error("'all' model can contains only elements.", elem)
214 elif child.tag == XSD_ANY:
215 self.append(XsdAnyElement(child, self.schema, self))
216 elif child.tag in (XSD_SEQUENCE, XSD_CHOICE):
217 self.append(XsdGroup(child, self.schema, self))
218 elif child.tag == XSD_GROUP:
219 try:
220 ref = self.schema.resolve_qname(child.attrib['ref'])
221 except KeyError:
222 self.parse_error("missing attribute 'ref' in local group", child)
223 continue
224
225 if ref != self.name:
226 xsd_group = XsdGroup(child, self.schema, self)
227 if xsd_group.model == 'all':
228 self.parse_error("'all' model can appears only at 1st level of a model group")
229 else:
230 self.append(xsd_group)
231 elif self.redefine is None:
232 self.parse_error("Circular definition detected for group %r:" % self.ref, elem)
233 else:
234 if child.get('minOccurs', '1') != '1' or child.get('maxOccurs', '1') != '1':
235 self.parse_error(
236 "Redefined group reference cannot have minOccurs/maxOccurs other than 1:", elem
237 )
238 self.append(self.redefine)
239 else:
240 continue
241
242 - def children_validation_error(self, validation, elem, index, particle, occurs=0, expected=None,
243 source=None, namespaces=None, **_kwargs):
244 """
245 Helper method for generating model validation errors. Incompatible with 'skip' validation mode.
246 Il validation mode is 'lax' returns the error, otherwise raise the error.
247
248 :param validation: the validation mode. Can be 'lax' or 'strict'.
249 :param elem: the instance Element.
250 :param index: the child index.
251 :param particle: the XSD component (subgroup or element) associated to the child.
252 :param occurs: the child tag occurs.
253 :param expected: the expected element tags/object names.
254 :param source: the XML resource related to the validation process.
255 :param namespaces: is an optional mapping from namespace prefix to URI.
256 :param _kwargs: keyword arguments of the validation process that are not used.
257 """
258 if validation == 'skip':
259 raise XMLSchemaValueError("validation mode 'skip' incompatible with error generation.")
260
261 error = XMLSchemaChildrenValidationError(self, elem, index, particle, occurs, expected, source, namespaces)
262 if validation == 'strict':
263 raise error
264 else:
265 return error
266
277
278 @property
293
294 @property
296 return self.elem if self.name else self.parent.elem
297
298 @property
300 if self.built:
301 return 'full'
302 elif any([item.validation_attempted == 'partial' for item in self]):
303 return 'partial'
304 else:
305 return 'none'
306
307 @property
309 return self.elem.get('ref')
310
312 if xsd_classes is None or isinstance(self, xsd_classes):
313 yield self
314 for item in self:
315 if item.parent is None:
316 continue
317 elif item.parent is not self.parent and isinstance(item.parent, XsdType) and item.parent.parent is None:
318 continue
319 for obj in item.iter_components(xsd_classes):
320 yield obj
321
323 if self.model == model:
324 return True
325 elif self.model == 'all' and model == 'choice' and len(self) > 1:
326 return False
327 elif model == 'all' and self.model == 'choice' and len(self) > 1:
328 return False
329 if model == 'sequence' and self.model != 'sequence' and len(self) > 1:
330 return False
331
333 return not self.mixed and not self
334
363
398
400 if not self.has_occurs_restriction(other):
401 return False
402 check_occurs = other.max_occurs != 0
403 check_emptiable = other.model != 'choice'
404
405
406 other_iterator = iter(other.iter_model())
407 for item in self.iter_model():
408 while True:
409 try:
410 other_item = next(other_iterator)
411 except StopIteration:
412 return False
413 if other_item is item or item.is_restriction(other_item, check_occurs):
414 break
415 elif check_emptiable and not other_item.is_emptiable():
416 return False
417
418 if not check_emptiable:
419 return True
420
421 while True:
422 try:
423 other_item = next(other_iterator)
424 except StopIteration:
425 return True
426 else:
427 if not other_item.is_emptiable():
428 return False
429
431 if not self.has_occurs_restriction(other):
432 return False
433
434 check_occurs = other.max_occurs != 0
435 restriction_items = list(self)
436
437 for other_item in other.iter_model():
438 for item in restriction_items:
439 if other_item is item or item.is_restriction(other_item, check_occurs):
440 break
441 else:
442 if not other_item.is_emptiable():
443 return False
444 continue
445 restriction_items.remove(item)
446
447 return not bool(restriction_items)
448
450 if self.parent is None and other.parent is not None and self.schema.XSD_VERSION == '1.0':
451 return False
452
453 check_occurs = other.max_occurs != 0
454 restriction_items = list(self)
455 max_occurs = 0
456 other_max_occurs = 0
457
458 for other_item in other.iter_model():
459 for item in restriction_items:
460
461 if other_item is item or item.is_restriction(other_item, check_occurs):
462 if max_occurs is not None:
463 if item.max_occurs is None:
464 max_occurs = None
465 else:
466 max_occurs += item.max_occurs
467
468 if other_max_occurs is not None:
469 if other_item.max_occurs is None:
470 other_max_occurs = None
471 else:
472 other_max_occurs = max(other_max_occurs, other_item.max_occurs)
473 break
474 else:
475 continue
476 restriction_items.remove(item)
477
478 if restriction_items:
479 return False
480 elif other_max_occurs is None:
481 if other.max_occurs:
482 return True
483 other_max_occurs = 0
484 elif other.max_occurs is None:
485 if other_max_occurs:
486 return True
487 other_max_occurs = 0
488 else:
489 other_max_occurs *= other.max_occurs
490
491 if max_occurs is None:
492 return self.max_occurs == 0
493 elif self.max_occurs is None:
494 return max_occurs == 0
495 else:
496 return other_max_occurs >= max_occurs * self.max_occurs
497
499 if depth <= MAX_MODEL_DEPTH:
500 for item in self:
501 if isinstance(item, XsdGroup):
502 for e in item.iter_elements(depth+1):
503 yield e
504 else:
505 yield item
506 for e in self.maps.substitution_groups.get(item.name, ()):
507 yield e
508
510 """
511 Sort elements by group order, that maybe partial in case of 'all' or 'choice' ordering.
512 The not matching elements are appended at the end.
513 """
514 def sorter(elem):
515 for e in elements_order:
516 if e.is_matching(elem.tag, default_namespace):
517 return elements_order[e]
518 return len(elements_order)
519
520 elements_order = {e: p for p, e in enumerate(self.iter_elements())}
521 return sorted(elements, key=sorter)
522
523 - def iter_decode(self, elem, validation='lax', converter=None, **kwargs):
524 """
525 Creates an iterator for decoding an Element content.
526
527 :param elem: the Element that has to be decoded.
528 :param validation: the validation mode, can be 'lax', 'strict' or 'skip.
529 :param converter: an :class:`XMLSchemaConverter` subclass or instance.
530 :param kwargs: keyword arguments for the decoding process.
531 :return: yields a list of 3-tuples (key, decoded data, decoder), eventually \
532 preceded by a sequence of validation or decoding errors.
533 """
534 def not_whitespace(s):
535 return s is not None and s.strip()
536
537 result_list = []
538 cdata_index = 1
539
540 if validation != 'skip' and not self.mixed:
541
542 if not_whitespace(elem.text) or any([not_whitespace(child.tail) for child in elem]):
543 if len(self) == 1 and isinstance(self[0], XsdAnyElement):
544 pass
545 else:
546 reason = "character data between child elements not allowed!"
547 yield self.validation_error(validation, reason, elem, **kwargs)
548 cdata_index = 0
549
550 if cdata_index and elem.text is not None:
551 text = unicode_type(elem.text.strip())
552 if text:
553 result_list.append((cdata_index, text, None))
554 cdata_index += 1
555
556 model = ModelVisitor(self)
557 errors = []
558
559 if not isinstance(converter, XMLSchemaConverter):
560 converter = self.schema.get_converter(converter, **kwargs)
561 default_namespace = converter.get('')
562
563 for index, child in enumerate(elem):
564 if callable(child.tag):
565 continue
566
567 if not default_namespace or child.tag[0] == '{':
568 tag = child.tag
569 else:
570 tag = '{%s}%s' % (default_namespace, child.tag)
571
572 while model.element is not None:
573 if tag in model.element.names or model.element.name is None \
574 and model.element.is_matching(tag, default_namespace):
575 xsd_element = model.element
576 else:
577 for xsd_element in model.element.iter_substitutes():
578 if tag in xsd_element.names:
579 break
580 else:
581 for particle, occurs, expected in model.advance(False):
582 errors.append((index, particle, occurs, expected))
583 model.clear()
584 model.broken = True
585 break
586 continue
587
588 for particle, occurs, expected in model.advance(True):
589 errors.append((index, particle, occurs, expected))
590 break
591 else:
592 for xsd_element in self.iter_elements():
593 if tag in xsd_element.names or xsd_element.name is None \
594 and xsd_element.is_matching(child.tag, default_namespace):
595 if not model.broken:
596 model.broken = True
597 errors.append((index, xsd_element, 0, []))
598 break
599 else:
600 errors.append((index, self, 0, None))
601 xsd_element = None
602 if not model.broken:
603 model.broken = True
604
605 if xsd_element is None:
606
607 continue
608
609 for result in xsd_element.iter_decode(child, validation, converter, **kwargs):
610 if isinstance(result, XMLSchemaValidationError):
611 yield result
612 else:
613 result_list.append((child.tag, result, xsd_element))
614
615 if cdata_index and child.tail is not None:
616 tail = unicode_type(child.tail.strip())
617 if tail:
618 if result_list and isinstance(result_list[-1][0], int):
619 tail = result_list[-1][1] + ' ' + tail
620 result_list[-1] = result_list[-1][0], tail, None
621 else:
622 result_list.append((cdata_index, tail, None))
623 cdata_index += 1
624
625 if model.element is not None:
626 index = len(elem)
627 for particle, occurs, expected in model.stop():
628 errors.append((index, particle, occurs, expected))
629
630 if validation != 'skip' and errors:
631 for model_error in errors:
632 yield self.children_validation_error(validation, elem, *model_error, **kwargs)
633
634 yield result_list
635
636 - def iter_encode(self, element_data, validation='lax', converter=None, **kwargs):
637 """
638 Creates an iterator for encoding data to a list containing Element data.
639
640 :param element_data: an ElementData instance with unencoded data.
641 :param validation: the validation mode: can be 'lax', 'strict' or 'skip'.
642 :param converter: an :class:`XMLSchemaConverter` subclass or instance.
643 :param kwargs: Keyword arguments for the encoding process.
644 :return: Yields a couple with the text of the Element and a list of 3-tuples \
645 (key, decoded data, decoder), eventually preceded by a sequence of validation \
646 or encoding errors.
647 """
648 if not element_data.content:
649 yield element_data.content
650 return
651
652 if not isinstance(converter, XMLSchemaConverter):
653 converter = self.schema.get_converter(converter, **kwargs)
654
655 errors = []
656 text = None
657 children = []
658 level = kwargs.get('level', 0)
659 indent = kwargs.get('indent', 4)
660 padding = '\n' + ' ' * indent * level
661 default_namespace = converter.get('')
662 losslessly = converter.losslessly
663
664 model = ModelVisitor(self)
665 cdata_index = 0
666
667 for index, (name, value) in enumerate(element_data.content):
668 if isinstance(name, int):
669 if not children:
670 text = padding + value if text is None else text + value + padding
671 elif children[-1].tail is None:
672 children[-1].tail = padding + value
673 else:
674 children[-1].tail += value + padding
675 cdata_index += 1
676 continue
677
678 if not default_namespace or name[0] == '{':
679 tag = name
680 else:
681 tag = '{%s}%s' % (default_namespace, name)
682
683 while model.element is not None:
684 if tag in model.element.names or model.element.name is None \
685 and model.element.is_matching(tag, default_namespace):
686 xsd_element = model.element
687 else:
688 for xsd_element in model.element.iter_substitutes():
689 if tag in xsd_element.names:
690 break
691 else:
692 for particle, occurs, expected in model.advance():
693 errors.append((index - cdata_index, particle, occurs, expected))
694 continue
695
696 if isinstance(xsd_element, XsdAnyElement):
697 value = get_qname(default_namespace, name), value
698 for result in xsd_element.iter_encode(value, validation, converter, **kwargs):
699 if isinstance(result, XMLSchemaValidationError):
700 yield result
701 else:
702 children.append(result)
703
704 for particle, occurs, expected in model.advance(True):
705 errors.append((index - cdata_index, particle, occurs, expected))
706 break
707 else:
708 if losslessly:
709 errors.append((index - cdata_index, self, 0, []))
710
711 for xsd_element in self.iter_elements():
712 if tag in xsd_element.names or xsd_element.name is None \
713 and xsd_element.is_matching(name, default_namespace):
714 if isinstance(xsd_element, XsdAnyElement):
715 value = get_qname(default_namespace, name), value
716 for result in xsd_element.iter_encode(value, validation, converter, **kwargs):
717 if isinstance(result, XMLSchemaValidationError):
718 yield result
719 else:
720 children.append(result)
721 break
722 else:
723 if validation != 'skip':
724 reason = '%r does not match any declared element of the model group.' % name
725 yield self.validation_error(validation, reason, value, **kwargs)
726
727 if model.element is not None:
728 index = len(element_data.content) - cdata_index
729 for particle, occurs, expected in model.stop():
730 errors.append((index, particle, occurs, expected))
731
732
733 if errors and validation != 'strict':
734 children = self.sort_children(children, default_namespace)
735
736 if children:
737 if children[-1].tail is None:
738 children[-1].tail = padding[:-indent] or '\n'
739 else:
740 children[-1].tail = children[-1].tail.strip() + (padding[:-indent] or '\n')
741
742 if validation != 'skip' and errors:
743 attrib = {k: unicode_type(v) for k, v in element_data.attributes.items()}
744 if validation == 'lax' and converter.etree_element_class is not etree_element:
745 child_tags = [converter.etree_element(e.tag, attrib=e.attrib) for e in children]
746 elem = converter.etree_element(element_data.tag, text, child_tags, attrib)
747 else:
748 elem = converter.etree_element(element_data.tag, text, children, attrib)
749
750 for index, particle, occurs, expected in errors:
751 yield self.children_validation_error(validation, elem, index, particle, occurs, expected, **kwargs)
752
753 yield text, children
754
756 """
757 Update group occurrences.
758
759 :param counter: a Counter object that trace occurrences for elements and groups.
760 """
761 if self.model in ('sequence', 'all'):
762 if all(counter[item] for item in self if not item.is_emptiable()):
763 counter[self] += 1
764 for item in self:
765 counter[item] = 0
766 elif self.model == 'choice':
767 if any(counter[item] for item in self):
768 counter[self] += 1
769 for item in self:
770 counter[item] = 0
771 else:
772 raise XMLSchemaValueError("the group %r has no model!" % self)
773
776 """
777 A class for XSD 1.1 model group definitions. The XSD 1.1 model groups differ
778 from XSD 1.0 groups for the 'all' model, that can contains also other groups.
779
780 <all
781 id = ID
782 maxOccurs = (0 | 1) : 1
783 minOccurs = (0 | 1) : 1
784 {any attributes with non-schema namespace . . .}>
785 Content: (annotation?, (element | any | group)*)
786 </all>
787 """
788 - def _parse_content_model(self, elem, content_model):
789 self.model = local_name(content_model.tag)
790 if self.model == 'all':
791 if self.max_occurs != 1:
792 self.parse_error("maxOccurs must be (0 | 1) for 'all' model groups")
793 if self.min_occurs not in (0, 1):
794 self.parse_error("minOccurs must be (0 | 1) for 'all' model groups")
795
796 for child in self._iterparse_components(content_model):
797 if child.tag == XSD_ELEMENT:
798
799 self.append((child, self.schema))
800 elif child.tag == XSD_ANY:
801 self.append(XsdAnyElement(child, self.schema, self))
802 elif child.tag in (XSD_SEQUENCE, XSD_CHOICE, XSD_ALL):
803 self.append(XsdGroup(child, self.schema, self))
804 elif child.tag == XSD_GROUP:
805 try:
806 ref = self.schema.resolve_qname(child.attrib['ref'])
807 except KeyError:
808 self.parse_error("missing attribute 'ref' in local group", child)
809 continue
810
811 if ref != self.name:
812 self.append(XsdGroup(child, self.schema, self))
813 elif self.redefine is None:
814 self.parse_error("Circular definition detected for group %r:" % self.ref, elem)
815 else:
816 if child.get('minOccurs', '1') != '1' or child.get('maxOccurs', '1') != '1':
817 self.parse_error(
818 "Redefined group reference cannot have minOccurs/maxOccurs other than 1:", elem
819 )
820 self.append(self.redefine)
821 else:
822 continue
823