1# 2# Copyright (c), 2016-2020, SISSA (International School for Advanced Studies). 3# All rights reserved. 4# This file is distributed under the terms of the MIT License. 5# See the file 'LICENSE' in the root directory of the present 6# distribution, or http://opensource.org/licenses/MIT. 7# 8# @author Davide Brunato <brunato@sissa.it> 9# 10""" 11This module contains base functions and classes XML Schema components. 12""" 13import re 14from typing import TYPE_CHECKING, cast, Any, Dict, Generic, List, Iterator, Optional, \ 15 Set, Tuple, TypeVar, Union, MutableMapping 16 17import elementpath 18 19from ..exceptions import XMLSchemaValueError, XMLSchemaTypeError 20from ..names import XSD_ANNOTATION, XSD_APPINFO, XSD_DOCUMENTATION, XML_LANG, \ 21 XSD_ANY_TYPE, XSD_ANY_SIMPLE_TYPE, XSD_ANY_ATOMIC_TYPE, XSD_ID, XSD_QNAME, \ 22 XSD_OVERRIDE, XSD_NOTATION_TYPE, XSD_DECIMAL 23from ..etree import is_etree_element, etree_tostring, etree_element 24from ..aliases import ElementType, NamespacesType, SchemaType, BaseXsdType, \ 25 ComponentClassType, ExtraValidatorType, DecodeType, IterDecodeType, \ 26 EncodeType, IterEncodeType 27from ..helpers import get_qname, local_name, get_prefixed_qname 28from ..resources import XMLResource 29from .exceptions import XMLSchemaParseError, XMLSchemaValidationError 30 31if TYPE_CHECKING: 32 from .simple_types import XsdSimpleType 33 from .complex_types import XsdComplexType 34 from .elements import XsdElement 35 from .groups import XsdGroup 36 from .global_maps import XsdGlobals 37 38XSD_TYPE_DERIVATIONS = {'extension', 'restriction'} 39XSD_ELEMENT_DERIVATIONS = {'extension', 'restriction', 'substitution'} 40 41XSD_VALIDATION_MODES = {'strict', 'lax', 'skip'} 42""" 43XML Schema validation modes 44Ref.: https://www.w3.org/TR/xmlschema11-1/#key-va 45""" 46 47 48def check_validation_mode(validation: str) -> None: 49 if validation not in XSD_VALIDATION_MODES: 50 raise XMLSchemaValueError("validation mode can be 'strict', " 51 "'lax' or 'skip': %r" % validation) 52 53 54class XsdValidator: 55 """ 56 Common base class for XML Schema validator, that represents a PSVI (Post Schema Validation 57 Infoset) information item. A concrete XSD validator have to report its validity collecting 58 building errors and implementing the properties. 59 60 :param validation: defines the XSD validation mode to use for build the validator, \ 61 its value can be 'strict', 'lax' or 'skip'. Strict mode is the default. 62 :type validation: str 63 64 :ivar validation: XSD validation mode. 65 :vartype validation: str 66 :ivar errors: XSD validator building errors. 67 :vartype errors: list 68 """ 69 elem: Optional[etree_element] = None 70 namespaces: Any = None 71 errors: List[XMLSchemaParseError] 72 73 def __init__(self, validation: str = 'strict') -> None: 74 self.validation = validation 75 self.errors = [] 76 77 @property 78 def built(self) -> bool: 79 """ 80 Property that is ``True`` if XSD validator has been fully parsed and built, 81 ``False`` otherwise. For schemas the property is checked on all global 82 components. For XSD components check only the building of local subcomponents. 83 """ 84 raise NotImplementedError() 85 86 @property 87 def validation_attempted(self) -> str: 88 """ 89 Property that returns the *validation status* of the XSD validator. 90 It can be 'full', 'partial' or 'none'. 91 92 | https://www.w3.org/TR/xmlschema-1/#e-validation_attempted 93 | https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/#e-validation_attempted 94 """ 95 raise NotImplementedError() 96 97 @property 98 def validity(self) -> str: 99 """ 100 Property that returns the XSD validator's validity. 101 It can be ‘valid’, ‘invalid’ or ‘notKnown’. 102 103 | https://www.w3.org/TR/xmlschema-1/#e-validity 104 | https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/#e-validity 105 """ 106 if self.validation == 'skip': 107 return 'notKnown' 108 elif self.errors or any(comp.errors for comp in self.iter_components()): 109 return 'invalid' 110 elif self.built: 111 return 'valid' 112 else: 113 return 'notKnown' 114 115 def iter_components(self, xsd_classes: ComponentClassType = None) \ 116 -> Iterator[Union['XsdComponent', SchemaType, 'XsdGlobals']]: 117 """ 118 Creates an iterator for traversing all XSD components of the validator. 119 120 :param xsd_classes: returns only a specific class/classes of components, \ 121 otherwise returns all components. 122 """ 123 raise NotImplementedError() 124 125 @property 126 def all_errors(self) -> List[XMLSchemaParseError]: 127 """ 128 A list with all the building errors of the XSD validator and its components. 129 """ 130 errors = [] 131 for comp in self.iter_components(): 132 if comp.errors: 133 errors.extend(comp.errors) 134 return errors 135 136 def copy(self) -> 'XsdValidator': 137 validator: 'XsdValidator' = object.__new__(self.__class__) 138 validator.__dict__.update(self.__dict__) 139 validator.errors = self.errors[:] # shallow copy duplicates errors list 140 return validator 141 142 __copy__ = copy 143 144 def parse_error(self, error: Union[str, Exception], 145 elem: Optional[ElementType] = None, 146 validation: Optional[str] = None) -> None: 147 """ 148 Helper method for registering parse errors. Does nothing if validation mode is 'skip'. 149 Il validation mode is 'lax' collects the error, otherwise raise the error. 150 151 :param error: can be a parse error or an error message. 152 :param elem: the Element instance related to the error, for default uses the 'elem' \ 153 attribute of the validator, if it's present. 154 :param validation: overrides the default validation mode of the validator. 155 """ 156 if validation is not None: 157 check_validation_mode(validation) 158 else: 159 validation = self.validation 160 161 if validation == 'skip': 162 return 163 elif elem is None: 164 elem = self.elem 165 elif not is_etree_element(elem): 166 msg = "the argument 'elem' must be an Element instance, not {!r}." 167 raise XMLSchemaTypeError(msg.format(elem)) 168 169 if isinstance(error, XMLSchemaParseError): 170 error.validator = self 171 error.namespaces = getattr(self, 'namespaces', None) 172 error.elem = elem 173 error.source = getattr(self, 'source', None) 174 elif isinstance(error, Exception): 175 message = str(error).strip() 176 if message[0] in '\'"' and message[0] == message[-1]: 177 message = message.strip('\'"') 178 error = XMLSchemaParseError(self, message, elem) 179 elif isinstance(error, str): 180 error = XMLSchemaParseError(self, error, elem) 181 else: 182 msg = "'error' argument must be an exception or a string, not {!r}." 183 raise XMLSchemaTypeError(msg.format(error)) 184 185 if validation == 'lax': 186 self.errors.append(error) 187 else: 188 raise error 189 190 def validation_error(self, validation: str, 191 error: Union[str, Exception], 192 obj: Any = None, 193 source: Optional[XMLResource] = None, 194 namespaces: Optional[NamespacesType] = None, 195 **_kwargs: Any) -> XMLSchemaValidationError: 196 """ 197 Helper method for generating and updating validation errors. If validation 198 mode is 'lax' or 'skip' returns the error, otherwise raises the error. 199 200 :param validation: an error-compatible validation mode: can be 'lax' or 'strict'. 201 :param error: an error instance or the detailed reason of failed validation. 202 :param obj: the instance related to the error. 203 :param source: the XML resource related to the validation process. 204 :param namespaces: is an optional mapping from namespace prefix to URI. 205 :param _kwargs: keyword arguments of the validation process that are not used. 206 """ 207 check_validation_mode(validation) 208 if isinstance(error, XMLSchemaValidationError): 209 if error.namespaces is None and namespaces is not None: 210 error.namespaces = namespaces 211 if error.source is None and source is not None: 212 error.source = source 213 if error.obj is None and obj is not None: 214 error.obj = obj 215 if error.elem is None and is_etree_element(obj): 216 error.elem = obj 217 elif isinstance(error, Exception): 218 error = XMLSchemaValidationError(self, obj, str(error), source, namespaces) 219 else: 220 error = XMLSchemaValidationError(self, obj, error, source, namespaces) 221 222 if validation == 'strict' and error.elem is not None: 223 raise error 224 return error 225 226 def _parse_xpath_default_namespace(self, elem: ElementType) -> str: 227 """ 228 Parse XSD 1.1 xpathDefaultNamespace attribute for schema, alternative, assert, assertion 229 and selector declarations, checking if the value is conforming to the specification. In 230 case the attribute is missing or for wrong attribute values defaults to ''. 231 """ 232 try: 233 value = elem.attrib['xpathDefaultNamespace'] 234 except KeyError: 235 return '' 236 237 value = value.strip() 238 if value == '##local': 239 return '' 240 elif value == '##defaultNamespace': 241 default_namespace = getattr(self, 'default_namespace', None) 242 return default_namespace if isinstance(default_namespace, str) else '' 243 elif value == '##targetNamespace': 244 target_namespace = getattr(self, 'target_namespace', '') 245 return target_namespace if isinstance(target_namespace, str) else '' 246 elif len(value.split()) == 1: 247 return value 248 else: 249 admitted_values = ('##defaultNamespace', '##targetNamespace', '##local') 250 msg = "wrong value %r for 'xpathDefaultNamespace' attribute, can be (anyURI | %s)." 251 self.parse_error(msg % (value, ' | '.join(admitted_values)), elem) 252 return '' 253 254 255class XsdComponent(XsdValidator): 256 """ 257 Class for XSD components. See: https://www.w3.org/TR/xmlschema-ref/ 258 259 :param elem: ElementTree's node containing the definition. 260 :param schema: the XMLSchema object that owns the definition. 261 :param parent: the XSD parent, `None` means that is a global component that \ 262 has the schema as parent. 263 :param name: name of the component, maybe overwritten by the parse of the `elem` argument. 264 265 :cvar qualified: for name matching, unqualified matching may be admitted only \ 266 for elements and attributes. 267 :vartype qualified: bool 268 """ 269 _REGEX_SPACE = re.compile(r'\s') 270 _REGEX_SPACES = re.compile(r'\s+') 271 _ADMITTED_TAGS: Union[Set[str], Tuple[str, ...], Tuple[()]] = () 272 273 elem: etree_element 274 parent = None 275 name = None 276 ref: Optional['XsdComponent'] = None 277 qualified = True 278 redefine = None 279 _annotation = None 280 _target_namespace: Optional[str] 281 282 def __init__(self, elem: etree_element, 283 schema: SchemaType, 284 parent: Optional['XsdComponent'] = None, 285 name: Optional[str] = None) -> None: 286 287 super(XsdComponent, self).__init__(schema.validation) 288 if name: 289 self.name = name 290 if parent is not None: 291 self.parent = parent 292 self.schema = schema 293 self.maps: XsdGlobals = schema.maps 294 self.elem = elem 295 296 def __setattr__(self, name: str, value: Any) -> None: 297 if name == 'elem': 298 if value.tag not in self._ADMITTED_TAGS: 299 msg = "wrong XSD element {!r} for {!r}, must be one of {!r}" 300 raise XMLSchemaValueError( 301 msg.format(value.tag, self.__class__, self._ADMITTED_TAGS) 302 ) 303 super(XsdComponent, self).__setattr__(name, value) 304 if self.errors: 305 self.errors.clear() 306 self._parse() 307 else: 308 super(XsdComponent, self).__setattr__(name, value) 309 310 @property 311 def xsd_version(self) -> str: 312 return self.schema.XSD_VERSION 313 314 def is_global(self) -> bool: 315 """Returns `True` if the instance is a global component, `False` if it's local.""" 316 return self.parent is None 317 318 def is_override(self) -> bool: 319 """Returns `True` if the instance is an override of a global component.""" 320 if self.parent is not None: 321 return False 322 return any(self.elem in x for x in self.schema.root if x.tag == XSD_OVERRIDE) 323 324 @property 325 def schema_elem(self) -> ElementType: 326 """The reference element of the schema for the component instance.""" 327 return self.elem 328 329 @property 330 def source(self) -> XMLResource: 331 """Property that references to schema source.""" 332 return self.schema.source 333 334 @property 335 def target_namespace(self) -> str: 336 """Property that references to schema's targetNamespace.""" 337 return self.schema.target_namespace if self.ref is None else self.ref.target_namespace 338 339 @property 340 def default_namespace(self) -> Optional[str]: 341 """Property that references to schema's default namespaces.""" 342 return self.schema.namespaces.get('') 343 344 @property 345 def namespaces(self) -> NamespacesType: 346 """Property that references to schema's namespace mapping.""" 347 return self.schema.namespaces 348 349 @property 350 def any_type(self) -> 'XsdComplexType': 351 """Property that references to the xs:anyType instance of the global maps.""" 352 return cast('XsdComplexType', self.maps.types[XSD_ANY_TYPE]) 353 354 @property 355 def any_simple_type(self) -> 'XsdSimpleType': 356 """Property that references to the xs:anySimpleType instance of the global maps.""" 357 return cast('XsdSimpleType', self.maps.types[XSD_ANY_SIMPLE_TYPE]) 358 359 @property 360 def any_atomic_type(self) -> 'XsdSimpleType': 361 """Property that references to the xs:anyAtomicType instance of the global maps.""" 362 return cast('XsdSimpleType', self.maps.types[XSD_ANY_ATOMIC_TYPE]) 363 364 @property 365 def annotation(self) -> Optional['XsdAnnotation']: 366 if '_annotation' not in self.__dict__: 367 for child in self.elem: 368 if child.tag == XSD_ANNOTATION: 369 self._annotation = XsdAnnotation(child, self.schema, self) 370 break 371 elif not callable(child.tag): 372 self._annotation = None 373 break 374 else: 375 self._annotation = None 376 377 return self._annotation 378 379 def __repr__(self) -> str: 380 if self.ref is not None: 381 return '%s(ref=%r)' % (self.__class__.__name__, self.prefixed_name) 382 else: 383 return '%s(name=%r)' % (self.__class__.__name__, self.prefixed_name) 384 385 def _parse(self) -> None: 386 return 387 388 def _parse_reference(self) -> Optional[bool]: 389 """ 390 Helper method for referable components. Returns `True` if a valid reference QName 391 is found without any error, otherwise returns `None`. Sets an id-related name for 392 the component ('nameless_<id of the instance>') if both the attributes 'ref' and 393 'name' are missing. 394 """ 395 ref = self.elem.get('ref') 396 if ref is None: 397 if 'name' in self.elem.attrib: 398 return None 399 elif self.parent is None: 400 self.parse_error("missing attribute 'name' in a global %r" % type(self)) 401 else: 402 self.parse_error( 403 "missing both attributes 'name' and 'ref' in local %r" % type(self) 404 ) 405 elif 'name' in self.elem.attrib: 406 self.parse_error("attributes 'name' and 'ref' are mutually exclusive") 407 elif self.parent is None: 408 self.parse_error("attribute 'ref' not allowed in a global %r" % type(self)) 409 else: 410 try: 411 self.name = self.schema.resolve_qname(ref) 412 except (KeyError, ValueError, RuntimeError) as err: 413 self.parse_error(err) 414 else: 415 if self._parse_child_component(self.elem, strict=False) is not None: 416 self.parse_error("a reference component cannot have " 417 "child definitions/declarations") 418 return True 419 420 return None 421 422 def _parse_child_component(self, elem: ElementType, strict: bool = True) \ 423 -> Optional[ElementType]: 424 child = None 425 for e in elem: 426 if e.tag == XSD_ANNOTATION or callable(e.tag): 427 continue 428 elif not strict: 429 return e 430 elif child is not None: 431 msg = "too many XSD components, unexpected {!r} found at position {}" 432 self.parse_error(msg.format(child, elem[:].index(e)), elem) 433 break 434 else: 435 child = e 436 return child 437 438 def _parse_target_namespace(self) -> None: 439 """ 440 XSD 1.1 targetNamespace attribute in elements and attributes declarations. 441 """ 442 if 'targetNamespace' not in self.elem.attrib: 443 return 444 445 self._target_namespace = self.elem.attrib['targetNamespace'].strip() 446 if 'name' not in self.elem.attrib: 447 self.parse_error("attribute 'name' must be present when " 448 "'targetNamespace' attribute is provided") 449 if 'form' in self.elem.attrib: 450 self.parse_error("attribute 'form' must be absent when " 451 "'targetNamespace' attribute is provided") 452 if self._target_namespace != self.schema.target_namespace: 453 if self.parent is None: 454 self.parse_error("a global %s must have the same namespace as " 455 "its parent schema" % self.__class__.__name__) 456 457 xsd_type = self.get_parent_type() 458 if xsd_type is None or xsd_type.parent is not None: 459 pass 460 elif xsd_type.derivation != 'restriction' or \ 461 getattr(xsd_type.base_type, 'name', None) == XSD_ANY_TYPE: 462 self.parse_error("a declaration contained in a global complexType " 463 "must have the same namespace as its parent schema") 464 465 if self.name is None: 466 pass 467 elif not self._target_namespace: 468 self.name = local_name(self.name) 469 else: 470 self.name = '{%s}%s' % (self._target_namespace, local_name(self.name)) 471 472 @property 473 def local_name(self) -> Optional[str]: 474 """The local part of the name of the component, or `None` if the name is `None`.""" 475 return None if self.name is None else local_name(self.name) 476 477 @property 478 def qualified_name(self) -> Optional[str]: 479 """The name of the component in extended format, or `None` if the name is `None`.""" 480 return None if self.name is None else get_qname(self.target_namespace, self.name) 481 482 @property 483 def prefixed_name(self) -> Optional[str]: 484 """The name of the component in prefixed format, or `None` if the name is `None`.""" 485 return None if self.name is None else get_prefixed_qname(self.name, self.namespaces) 486 487 @property 488 def id(self) -> Optional[str]: 489 """The ``'id'`` attribute of the component tag, ``None`` if missing.""" 490 return self.elem.get('id') 491 492 @property 493 def validation_attempted(self) -> str: 494 return 'full' if self.built else 'partial' 495 496 def build(self) -> None: 497 """ 498 Builds components that are not fully parsed at initialization, like model groups 499 or internal local elements in model groups. Otherwise does nothing. 500 """ 501 502 @property 503 def built(self) -> bool: 504 raise NotImplementedError() 505 506 def is_matching(self, name: Optional[str], default_namespace: Optional[str] = None, 507 **kwargs: Any) -> bool: 508 """ 509 Returns `True` if the component name is matching the name provided as argument, 510 `False` otherwise. For XSD elements the matching is extended to substitutes. 511 512 :param name: a local or fully-qualified name. 513 :param default_namespace: used if it's not None and not empty for completing \ 514 the name argument in case it's a local name. 515 :param kwargs: additional options that can be used by certain components. 516 """ 517 if not name: 518 return self.name == name 519 elif name[0] == '{': 520 return self.qualified_name == name 521 elif not default_namespace: 522 return self.name == name or not self.qualified and self.local_name == name 523 else: 524 qname = '{%s}%s' % (default_namespace, name) 525 return self.qualified_name == qname or not self.qualified and self.local_name == name 526 527 def match(self, name: Optional[str], default_namespace: Optional[str] = None, 528 **kwargs: Any) -> Optional['XsdComponent']: 529 """ 530 Returns the component if its name is matching the name provided as argument, 531 `None` otherwise. 532 """ 533 return self if self.is_matching(name, default_namespace, **kwargs) else None 534 535 def get_matching_item(self, mapping: MutableMapping[str, Any], 536 ns_prefix: str = 'xmlns', 537 match_local_name: bool = False) -> Optional[Any]: 538 """ 539 If a key is matching component name, returns its value, otherwise returns `None`. 540 """ 541 if self.name is None: 542 return None 543 elif not self.target_namespace: 544 return mapping.get(self.name) 545 elif self.qualified_name in mapping: 546 return mapping[cast(str, self.qualified_name)] 547 elif self.prefixed_name in mapping: 548 return mapping[cast(str, self.prefixed_name)] 549 550 # Try a match with other prefixes 551 target_namespace = self.target_namespace 552 suffix = ':%s' % self.local_name 553 554 for k in filter(lambda x: x.endswith(suffix), mapping): 555 prefix = k.split(':')[0] 556 if self.namespaces.get(prefix) == target_namespace: 557 return mapping[k] 558 559 # Match namespace declaration within value 560 ns_declaration = '{}:{}'.format(ns_prefix, prefix) 561 try: 562 if mapping[k][ns_declaration] == target_namespace: 563 return mapping[k] 564 except (KeyError, TypeError): 565 pass 566 else: 567 if match_local_name: 568 return mapping.get(self.local_name) # type: ignore[arg-type] 569 return None 570 571 def get_global(self) -> 'XsdComponent': 572 """Returns the global XSD component that contains the component instance.""" 573 if self.parent is None: 574 return self 575 component = self.parent 576 while component is not self: # pragma: no cover 577 if component.parent is None: 578 return component 579 component = component.parent 580 else: 581 return self 582 583 def get_parent_type(self) -> Optional['XsdType']: 584 """ 585 Returns the nearest XSD type that contains the component instance, 586 or `None` if the component doesn't have an XSD type parent. 587 """ 588 component = self.parent 589 while component is not self and component is not None: 590 if isinstance(component, XsdType): 591 return component 592 component = component.parent 593 return None 594 595 def iter_components(self, xsd_classes: ComponentClassType = None) \ 596 -> Iterator['XsdComponent']: 597 """ 598 Creates an iterator for XSD subcomponents. 599 600 :param xsd_classes: provide a class or a tuple of classes to iterates over only a \ 601 specific classes of components. 602 """ 603 if xsd_classes is None or isinstance(self, xsd_classes): 604 yield self 605 606 def iter_ancestors(self, xsd_classes: ComponentClassType = None)\ 607 -> Iterator['XsdComponent']: 608 """ 609 Creates an iterator for XSD ancestor components, schema excluded. Stops when the component 610 is global or if the ancestor is not an instance of the specified class/classes. 611 612 :param xsd_classes: provide a class or a tuple of classes to iterates over only a \ 613 specific classes of components. 614 """ 615 ancestor = self 616 while True: 617 if ancestor.parent is None: 618 break 619 ancestor = ancestor.parent 620 if xsd_classes is not None and not isinstance(ancestor, xsd_classes): 621 break 622 yield ancestor 623 624 def tostring(self, indent: str = '', max_lines: Optional[int] = None, 625 spaces_for_tab: int = 4) -> Union[str, bytes]: 626 """Serializes the XML elements that declare or define the component to a string.""" 627 return etree_tostring(self.schema_elem, self.namespaces, indent, max_lines, spaces_for_tab) 628 629 630class XsdAnnotation(XsdComponent): 631 """ 632 Class for XSD *annotation* definitions. 633 634 :ivar appinfo: a list containing the xs:appinfo children. 635 :ivar documentation: a list containing the xs:documentation children. 636 637 .. <annotation 638 id = ID 639 {any attributes with non-schema namespace . . .}> 640 Content: (appinfo | documentation)* 641 </annotation> 642 643 .. <appinfo 644 source = anyURI 645 {any attributes with non-schema namespace . . .}> 646 Content: ({any})* 647 </appinfo> 648 649 .. <documentation 650 source = anyURI 651 xml:lang = language 652 {any attributes with non-schema namespace . . .}> 653 Content: ({any})* 654 </documentation> 655 """ 656 _ADMITTED_TAGS = {XSD_ANNOTATION} 657 658 annotation = None 659 660 def __repr__(self) -> str: 661 return '%s(%r)' % (self.__class__.__name__, str(self)[:40]) 662 663 def __str__(self) -> str: 664 return '\n'.join(elementpath.select(self.elem, '*/fn:string()')) 665 666 @property 667 def built(self) -> bool: 668 return True 669 670 def _parse(self) -> None: 671 self.appinfo = [] 672 self.documentation = [] 673 for child in self.elem: 674 if child.tag == XSD_APPINFO: 675 for key in child.attrib: 676 if key != 'source': 677 self.parse_error("wrong attribute %r for appinfo declaration." % key) 678 self.appinfo.append(child) 679 elif child.tag == XSD_DOCUMENTATION: 680 for key in child.attrib: 681 if key not in ['source', XML_LANG]: 682 self.parse_error("wrong attribute %r for documentation declaration." % key) 683 self.documentation.append(child) 684 685 686class XsdType(XsdComponent): 687 """Common base class for XSD types.""" 688 689 abstract = False 690 base_type: Optional[BaseXsdType] = None 691 derivation: Optional[str] = None 692 _final: Optional[str] = None 693 694 @property 695 def final(self) -> str: 696 return self.schema.final_default if self._final is None else self._final 697 698 @property 699 def built(self) -> bool: 700 raise NotImplementedError() 701 702 @property 703 def content_type_label(self) -> str: 704 """The content type classification.""" 705 raise NotImplementedError() 706 707 @property 708 def sequence_type(self) -> str: 709 """The XPath sequence type associated with the content.""" 710 raise NotImplementedError() 711 712 @property 713 def root_type(self) -> BaseXsdType: 714 """ 715 The root type of the type definition hierarchy. For an atomic type 716 is the primitive type. For a list is the primitive type of the item. 717 For a union is the base union type. For a complex type is xs:anyType. 718 """ 719 if getattr(self, 'attributes', None): 720 return cast('XsdComplexType', self.maps.types[XSD_ANY_TYPE]) 721 elif self.base_type is None: 722 if self.is_simple(): 723 return cast('XsdSimpleType', self) 724 return cast('XsdComplexType', self.maps.types[XSD_ANY_TYPE]) 725 726 primitive_type: BaseXsdType 727 try: 728 if self.base_type.is_simple(): 729 primitive_type = self.base_type.primitive_type # type: ignore[union-attr] 730 else: 731 primitive_type = self.base_type.content.primitive_type # type: ignore[union-attr] 732 except AttributeError: 733 # The type has complex or XsdList content 734 return self.base_type.root_type 735 else: 736 return primitive_type 737 738 @property 739 def simple_type(self) -> Optional['XsdSimpleType']: 740 """ 741 Property that is the instance itself for a simpleType. For a 742 complexType is the instance's *content* if this is a simpleType 743 or `None` if the instance's *content* is a model group. 744 """ 745 return None 746 747 @property 748 def model_group(self) -> Optional['XsdGroup']: 749 """ 750 Property that is `None` for a simpleType. For a complexType is 751 the instance's *content* if this is a model group or `None` if 752 the instance's *content* is a simpleType. 753 """ 754 return None 755 756 @staticmethod 757 def is_simple() -> bool: 758 """Returns `True` if the instance is a simpleType, `False` otherwise.""" 759 raise NotImplementedError() 760 761 @staticmethod 762 def is_complex() -> bool: 763 """Returns `True` if the instance is a complexType, `False` otherwise.""" 764 765 def is_atomic(self) -> bool: 766 """Returns `True` if the instance is an atomic simpleType, `False` otherwise.""" 767 return False 768 769 def is_list(self) -> bool: 770 """Returns `True` if the instance is a list simpleType, `False` otherwise.""" 771 return False 772 773 def is_union(self) -> bool: 774 """Returns `True` if the instance is a union simpleType, `False` otherwise.""" 775 return False 776 777 def is_datetime(self) -> bool: 778 """ 779 Returns `True` if the instance is a datetime/duration XSD builtin-type, `False` otherwise. 780 """ 781 return False 782 783 def is_empty(self) -> bool: 784 """Returns `True` if the instance has an empty content, `False` otherwise.""" 785 raise NotImplementedError() 786 787 def is_emptiable(self) -> bool: 788 """Returns `True` if the instance has an emptiable value or content, `False` otherwise.""" 789 raise NotImplementedError() 790 791 def has_simple_content(self) -> bool: 792 """ 793 Returns `True` if the instance has a simple content, `False` otherwise. 794 """ 795 raise NotImplementedError() 796 797 def has_complex_content(self) -> bool: 798 """ 799 Returns `True` if the instance is a complexType with mixed or element-only 800 content, `False` otherwise. 801 """ 802 raise NotImplementedError() 803 804 def has_mixed_content(self) -> bool: 805 """ 806 Returns `True` if the instance is a complexType with mixed content, `False` otherwise. 807 """ 808 raise NotImplementedError() 809 810 def is_element_only(self) -> bool: 811 """ 812 Returns `True` if the instance is a complexType with element-only content, 813 `False` otherwise. 814 """ 815 raise NotImplementedError() 816 817 def is_derived(self, other: Union[BaseXsdType, Tuple[ElementType, SchemaType]], 818 derivation: Optional[str] = None) -> bool: 819 """ 820 Returns `True` if the instance is derived from *other*, `False` otherwise. 821 The optional argument derivation can be a string containing the words 822 'extension' or 'restriction' or both. 823 """ 824 raise NotImplementedError() 825 826 def is_extension(self) -> bool: 827 return self.derivation == 'extension' 828 829 def is_restriction(self) -> bool: 830 return self.derivation == 'restriction' 831 832 def is_blocked(self, xsd_element: 'XsdElement') -> bool: 833 """ 834 Returns `True` if the base type derivation is blocked, `False` otherwise. 835 """ 836 xsd_type = xsd_element.type 837 if self is xsd_type: 838 return False 839 840 block = ('%s %s' % (xsd_element.block, xsd_type.block)).strip() 841 if not block: 842 return False 843 844 _block = {x for x in block.split() if x in ('extension', 'restriction')} 845 return any(self.is_derived(xsd_type, derivation) for derivation in _block) 846 847 def is_dynamic_consistent(self, other: Any) -> bool: 848 return other.name == XSD_ANY_TYPE or self.is_derived(other) or \ 849 hasattr(other, 'member_types') and \ 850 any(self.is_derived(mt) for mt in other.member_types) # pragma: no cover 851 852 def is_key(self) -> bool: 853 return self.name == XSD_ID or self.is_derived(self.maps.types[XSD_ID]) 854 855 def is_qname(self) -> bool: 856 return self.name == XSD_QNAME or self.is_derived(self.maps.types[XSD_QNAME]) 857 858 def is_notation(self) -> bool: 859 return self.name == XSD_NOTATION_TYPE or self.is_derived(self.maps.types[XSD_NOTATION_TYPE]) 860 861 def is_decimal(self) -> bool: 862 return self.name == XSD_DECIMAL or self.is_derived(self.maps.types[XSD_DECIMAL]) 863 864 def text_decode(self, text: str) -> Any: 865 raise NotImplementedError() 866 867 868ST = TypeVar('ST') 869DT = TypeVar('DT') 870 871 872class ValidationMixin(Generic[ST, DT]): 873 """ 874 Mixin for implementing XML data validators/decoders on XSD components. 875 A derived class must implement the methods `iter_decode` and `iter_encode`. 876 """ 877 def validate(self, obj: ST, 878 use_defaults: bool = True, 879 namespaces: Optional[NamespacesType] = None, 880 max_depth: Optional[int] = None, 881 extra_validator: Optional[ExtraValidatorType] = None) -> None: 882 """ 883 Validates XML data against the XSD schema/component instance. 884 885 :param obj: the XML data. Can be a string for an attribute or a simple type \ 886 validators, or an ElementTree's Element otherwise. 887 :param use_defaults: indicates whether to use default values for filling missing data. 888 :param namespaces: is an optional mapping from namespace prefix to URI. 889 :param max_depth: maximum level of validation, for default there is no limit. 890 :param extra_validator: an optional function for performing non-standard \ 891 validations on XML data. The provided function is called for each traversed \ 892 element, with the XML element as 1st argument and the corresponding XSD \ 893 element as 2nd argument. It can be also a generator function and has to \ 894 raise/yield :exc:`XMLSchemaValidationError` exceptions. 895 :raises: :exc:`XMLSchemaValidationError` if the XML data instance is invalid. 896 """ 897 for error in self.iter_errors(obj, use_defaults, namespaces, 898 max_depth, extra_validator): 899 raise error 900 901 def is_valid(self, obj: ST, 902 use_defaults: bool = True, 903 namespaces: Optional[NamespacesType] = None, 904 max_depth: Optional[int] = None, 905 extra_validator: Optional[ExtraValidatorType] = None) -> bool: 906 """ 907 Like :meth:`validate` except that does not raise an exception but returns 908 ``True`` if the XML data instance is valid, ``False`` if it is invalid. 909 """ 910 error = next(self.iter_errors(obj, use_defaults, namespaces, 911 max_depth, extra_validator), None) 912 return error is None 913 914 def iter_errors(self, obj: ST, 915 use_defaults: bool = True, 916 namespaces: Optional[NamespacesType] = None, 917 max_depth: Optional[int] = None, 918 extra_validator: Optional[ExtraValidatorType] = None) \ 919 -> Iterator[XMLSchemaValidationError]: 920 """ 921 Creates an iterator for the errors generated by the validation of an XML data against 922 the XSD schema/component instance. Accepts the same arguments of :meth:`validate`. 923 """ 924 kwargs: Dict[str, Any] = { 925 'use_defaults': use_defaults, 926 'namespaces': namespaces, 927 } 928 if max_depth is not None: 929 kwargs['max_depth'] = max_depth 930 if extra_validator is not None: 931 kwargs['extra_validator'] = extra_validator 932 933 for result in self.iter_decode(obj, **kwargs): 934 if isinstance(result, XMLSchemaValidationError): 935 yield result 936 else: 937 del result 938 939 def decode(self, obj: ST, validation: str = 'strict', **kwargs: Any) -> DecodeType[DT]: 940 """ 941 Decodes XML data. 942 943 :param obj: the XML data. Can be a string for an attribute or for simple type \ 944 components or a dictionary for an attribute group or an ElementTree's \ 945 Element for other components. 946 :param validation: the validation mode. Can be 'lax', 'strict' or 'skip. 947 :param kwargs: optional keyword arguments for the method :func:`iter_decode`. 948 :return: a dictionary like object if the XSD component is an element, a \ 949 group or a complex type; a list if the XSD component is an attribute group; \ 950 a simple data type object otherwise. If *validation* argument is 'lax' a 2-items \ 951 tuple is returned, where the first item is the decoded object and the second item \ 952 is a list containing the errors. 953 :raises: :exc:`XMLSchemaValidationError` if the object is not decodable by \ 954 the XSD component, or also if it's invalid when ``validation='strict'`` is provided. 955 """ 956 check_validation_mode(validation) 957 958 result: Union[DT, XMLSchemaValidationError] 959 errors: List[XMLSchemaValidationError] = [] 960 for result in self.iter_decode(obj, validation, **kwargs): # pragma: no cover 961 if not isinstance(result, XMLSchemaValidationError): 962 return (result, errors) if validation == 'lax' else result 963 elif validation == 'strict': 964 raise result 965 else: 966 errors.append(result) 967 968 return (None, errors) if validation == 'lax' else None 969 970 def encode(self, obj: Any, validation: str = 'strict', **kwargs: Any) -> EncodeType[Any]: 971 """ 972 Encodes data to XML. 973 974 :param obj: the data to be encoded to XML. 975 :param validation: the validation mode. Can be 'lax', 'strict' or 'skip. 976 :param kwargs: optional keyword arguments for the method :func:`iter_encode`. 977 :return: An element tree's Element if the original data is a structured data or \ 978 a string if it's simple type datum. If *validation* argument is 'lax' a 2-items \ 979 tuple is returned, where the first item is the encoded object and the second item \ 980 is a list containing the errors. 981 :raises: :exc:`XMLSchemaValidationError` if the object is not encodable by the XSD \ 982 component, or also if it's invalid when ``validation='strict'`` is provided. 983 """ 984 check_validation_mode(validation) 985 result, errors = None, [] 986 for result in self.iter_encode(obj, validation=validation, **kwargs): # pragma: no cover 987 if not isinstance(result, XMLSchemaValidationError): 988 break 989 elif validation == 'strict': 990 raise result 991 else: 992 errors.append(result) 993 994 return (result, errors) if validation == 'lax' else result 995 996 def iter_decode(self, obj: ST, validation: str = 'lax', **kwargs: Any) \ 997 -> IterDecodeType[DT]: 998 """ 999 Creates an iterator for decoding an XML source to a Python object. 1000 1001 :param obj: the XML data. 1002 :param validation: the validation mode. Can be 'lax', 'strict' or 'skip. 1003 :param kwargs: keyword arguments for the decoder API. 1004 :return: Yields a decoded object, eventually preceded by a sequence of \ 1005 validation or decoding errors. 1006 """ 1007 raise NotImplementedError() 1008 1009 def iter_encode(self, obj: Any, validation: str = 'lax', **kwargs: Any) \ 1010 -> IterEncodeType[Any]: 1011 """ 1012 Creates an iterator for encoding data to an Element tree. 1013 1014 :param obj: The data that has to be encoded. 1015 :param validation: The validation mode. Can be 'lax', 'strict' or 'skip'. 1016 :param kwargs: keyword arguments for the encoder API. 1017 :return: Yields an Element, eventually preceded by a sequence of validation \ 1018 or encoding errors. 1019 """ 1020 raise NotImplementedError() 1021