def _check_known_element(el, ref, errs, warns): if ref is None: is_profile = False try: ref = load_reference(el.name, el.classname, el.version) except ChildNotFound: errs.append(ValidationError("Invalid element found: {}".format(el))) else: is_profile = ref[0] == 'mp' if is_profile: # it is a message profile ref = ref[1:] # ref[0] is 'mp' if ref[0] in ('sequence', 'choice'): element_children = {c.name for c in el.children if not c.is_z_element()} valid_children, valid_children_refs = _get_valid_children_info(ref, is_profile) z_children = [c for c in el.children if c.is_z_element()] # check that the children are all allowed children if not element_children <= valid_children: errs.append(ValidationError("Invalid children detected for {}: {}". format(el, list(element_children - valid_children)))) # iterates the valid children for child_ref in valid_children_refs: # it gets the structure of the children to check child_name, cardinality = _get_child_reference_info(child_ref, is_profile) try: # it gets all the occurrences of the children of a type children = el.children.get(child_name) except Exception: # TODO: it is due to the lack of element in the official reference files... should # we raise an exception here? pass else: _check_repetitions(el, children, cardinality, child_name, errs) # calls validation for every children for c in children: ref = child_ref if is_profile else None _is_valid(c, ref, errs, warns) # finally calls validation for z_elements for c in z_children: _is_valid(c, None, errs, warns) else: _check_table_compliance(el, ref, is_profile, warns) _check_length(el, ref, is_profile, warns) if el.datatype == 'varies': # TODO: it should check the real rule return True _check_datatype(el, ref, is_profile, errs) # For complex datatypes element, the reference is the one of the datatype if not is_base_datatype(el.datatype, el.version): # Component just to search in the datatypes.... ref = load_reference(el.datatype, 'Component', el.version) _is_valid(el, ref, errs, warns)
def parse_subcomponents(text, component_datatype='ST', version=None, encoding_chars=None, validation_level=None): """ Parse the given ER7-encoded subcomponents and return a list of :class:`SubComponent <hl7apy.core.SubComponent>` instances. :type text: ``str`` :param text: the ER7-encoded string containing the components to be parsed :type component_datatype: ``str`` :param component_datatype: the datatype of the subcomponents (e.g. ST) :type version: ``str`` :param version: the HL7 version (e.g. "2.5"), or ``None`` to use the default (see :func:`set_default_version <hl7apy.set_default_version>`) :type encoding_chars: ``dict`` :param encoding_chars: a dictionary containing the encoding chars or None to use the default (see :func:`set_default_encoding_chars <hl7apy.set_default_encoding_chars>`) :type validation_level: ``int`` :param validation_level: the validation level. Possible values are those defined in :class:`VALIDATION_LEVEL <hl7apy.consts.VALIDATION_LEVEL>` class or ``None`` to use the default validation level (see :func:`set_default_validation_level <hl7apy.set_default_validation_level>`) :return: a list of :class:`SubComponent <hl7apy.core.SubComponent>` instances >>> subcomponents= "ID&TEST&&AHAH" >>> cwe = parse_subcomponents(subcomponents, component_datatype="CWE") >>> print(cwe) [<SubComponent CWE_1>, <SubComponent CWE_2>, <SubComponent CWE_4>] >>> c = Component(datatype='CWE') >>> c.children = cwe >>> print(c.to_er7()) ID&TEST&&AHAH >>> subs = parse_subcomponents(subcomponents) >>> print(subs) [<SubComponent ST>, <SubComponent ST>, <SubComponent ST>, <SubComponent ST>] >>> c.children = subs >>> print(c.to_er7()) &&&&&&&&&ID&TEST&&AHAH """ version = _get_version(version) encoding_chars = _get_encoding_chars(encoding_chars, version) validation_level = _get_validation_level(validation_level) subcomp_sep = encoding_chars['SUBCOMPONENT'] subcomponents = [] for index, subcomponent in enumerate(text.split(subcomp_sep)): if is_base_datatype(component_datatype, version) or component_datatype is None: subcomponent_name = None subcomponent_datatype = component_datatype if component_datatype is not None else 'ST' else: subcomponent_name = "{0}_{1}".format(component_datatype, index+1) subcomponent_datatype = None if subcomponent.strip() or subcomponent_name is None: subcomponents.append(parse_subcomponent(subcomponent, subcomponent_name, subcomponent_datatype, version, validation_level)) return subcomponents
def parse_subcomponents(text, component_datatype='ST', version=None, encoding_chars=None, validation_level=None): """ Parse the given ER7-encoded subcomponents and return a list of :class:`SubComponent <hl7apy.core.SubComponent>` instances. :type text: ``str`` :param text: the ER7-encoded string containing the components to be parsed :type component_datatype: ``str`` :param component_datatype: the datatype of the subcomponents (e.g. ST) :type version: ``str`` :param version: the HL7 version (e.g. "2.5"), or ``None`` to use the default (see :func:`set_default_version <hl7apy.set_default_version>`) :type encoding_chars: ``dict`` :param encoding_chars: a dictionary containing the encoding chars or None to use the default (see :func:`set_default_encoding_chars <hl7apy.set_default_encoding_chars>`) :type validation_level: ``int`` :param validation_level: the validation level. Possible values are those defined in :class:`VALIDATION_LEVEL <hl7apy.consts.VALIDATION_LEVEL>` class or ``None`` to use the default validation level (see :func:`set_default_validation_level <hl7apy.set_default_validation_level>`) :return: a list of :class:`SubComponent <hl7apy.core.SubComponent>` instances >>> subcomponents= "ID&TEST&&AHAH" >>> cwe = parse_subcomponents(subcomponents, component_datatype="CWE") >>> print(cwe) [<SubComponent CWE_1>, <SubComponent CWE_2>, <SubComponent CWE_4>] >>> c = Component(datatype='CWE') >>> c.children = cwe >>> print(c.to_er7()) ID&TEST&&AHAH >>> subs = parse_subcomponents(subcomponents) >>> print(subs) [<SubComponent ST>, <SubComponent ST>, <SubComponent ST>, <SubComponent ST>] >>> c.children = subs >>> print(c.to_er7()) &&&&&&&&&ID&TEST&&AHAH """ version = _get_version(version) encoding_chars = _get_encoding_chars(encoding_chars) validation_level = _get_validation_level(validation_level) subcomp_sep = encoding_chars['SUBCOMPONENT'] subcomponents = [] for index, subcomponent in enumerate(text.split(subcomp_sep)): if is_base_datatype(component_datatype, version) or component_datatype is None: subcomponent_name = None subcomponent_datatype = component_datatype if component_datatype is not None else 'ST' else: subcomponent_name = "{0}_{1}".format(component_datatype, index+1) subcomponent_datatype = None if subcomponent.strip() or subcomponent_name is None: subcomponents.append(parse_subcomponent(subcomponent, subcomponent_name, subcomponent_datatype, version, validation_level)) return subcomponents
def validate(element): """ Checks if the :class:`hl7apy.core.Element` is a valid HL7 message according to the official structures. It checks if it follows the max and min number of occurrences for every child and if it doesn't contain children not allowed. :param element: :class:`hl7apy.core.Element` :return: The element to be validated """ from hl7apy.core import is_base_datatype def _is_valid(ref, element): children = set([ child.name for child in element.children ]) valid_children = set([ child[0] for child in ref[1] ]) if not children <= valid_children: return False for child in ref[1]: child_name, cardinality = child min, max = cardinality children = element.children.get(child_name) num_children = len(children) if max != -1: if num_children < min or num_children > max: return False else: if num_children < min: return False for el in children: if not Validator.validate(el): return False return True if element.is_unknown(): return False ref = load_reference(element.name, element.classname, element.version) if ref[0] in ('sequence', 'choice'): return _is_valid(ref, element) else: # it's an element that has a datatype if element.datatype != ref[1]: return False if element.is_unknown(): return False if element.datatype == 'varies': #TODO: it should check the real rule return True if not is_base_datatype(element.datatype, element.version): # Component just to search in the datatypes.... ref = load_reference(element.datatype, 'Component', element.version) return _is_valid(ref, element) return True
def _check_z_element(el, errs, warns): if el.classname == 'Field': if is_base_datatype(el.datatype, el.version) or \ el.datatype == 'varies': return True elif el.datatype is not None: # if the datatype the is a complex datatype, the z element must follow the correct # structure of that datatype # Component just to search in the datatypes.... ref = load_reference(el.datatype, 'Component', el.version) _check_known_element(el, ref, errs, warns) for c in el.children: _is_valid(c, None, errs, warns) return True
def parse_field(text, name=None, version=None, encoding_chars=None, validation_level=None, reference=None, force_varies=False): """ Parse the given ER7-encoded field and return an instance of :class:`hl7apy.core.Field`. :type text: ``basestring`` :param text: the ER7-encoded string containing the fields to be parsed :type name: ``basestring`` :param name: the field name (e.g. MSH_7) :type version: ``basestring`` :param version: the HL7 version (e.g. "2.5"), or ``None`` to use the default (see :func:`hl7apy.set_default_version`) :type encoding_chars: ``dict`` :param encoding_chars: a dictionary containing the encoding chars or None to use the default (see :func:`hl7apy.set_default_encoding_chars`) :param validation_level: the validation level. Possible values are those defined in :class:`hl7apy.consts.VALIDATION_LEVEL` class or ``None`` to use the default validation level (see :func:`hl7apy.set_default_validation_level`) :type reference: ``dict`` :param reference: a dictionary containing the element structure returned by :func:`hl7apy.load_reference` or :func:`hl7apy.find_reference` :return: an instance of :class:`hl7apy.core.Field` >>> field = "NUCLEAR^NELDA^W" >>> nk1_2 = parse_field(field, name="NK1_2") >>> print nk1_2 <Field NK1_2 (NAME) of type XPN> >>> print nk1_2.to_er7() NUCLEAR^NELDA^W >>> unknown = parse_field(field) >>> print unknown <Field of type None> >>> print unknown.to_er7() NUCLEAR^NELDA^W """ version = _get_version(version) encoding_chars = _get_encoding_chars(encoding_chars) if force_varies: reference = ('leaf', 'varies', None, None) try: field = Field(name, version=version, validation_level=validation_level, reference=reference) except InvalidName: field = Field(version=version, validation_level=validation_level, reference=reference) if name in ('MSH_1', 'MSH_2'): s = SubComponent(datatype='ST', value=text) c = Component(datatype='ST') c.add(s) field.add(c) else: children = parse_components(text, field.datatype, version, encoding_chars, validation_level) if Validator.is_quiet(validation_level) and is_base_datatype(field.datatype, version) and \ len(children) > 1: field.datatype = None field.children = children return field
def parse_component(text, name=None, datatype='ST', version=None, encoding_chars=None, validation_level=None, reference=None): """ Parse the given ER7-encoded component and return an instance of :class:`hl7apy.core.Component`. :type text: ``basestring`` :param text: the ER7-encoded string containing the components to be parsed :type name: ``basestring`` :param name: the component's name (e.g. XPN_2) :type datatype: ``basestring`` :param datatype: the datatype of the component (e.g. ST) :type version: ``basestring`` :param version: the HL7 version (e.g. "2.5"), or ``None`` to use the default (see :func:`hl7apy.set_default_version`) :type encoding_chars: ``dict`` :param encoding_chars: a dictionary containing the encoding chars or None to use the default (see :func:`hl7apy.set_default_encoding_chars`) :param validation_level: the validation level. Possible values are those defined in :class:`hl7apy.consts.VALIDATION_LEVEL` class or ``None`` to use the default validation level (see :func:`hl7apy.set_default_validation_level`) :type reference: ``dict`` :param reference: a dictionary containing the element structure returned by :func:`hl7apy.load_reference` or :func:`hl7apy.find_reference` :return: an instance of :class:`hl7apy.core.Component` >>> component = "GATEWAY&1.3.6.1.4.1.21367.2011.2.5.17" >>> cx_4 = parse_component(component, name="CX_4") >>> print cx_4 <Component CX_4 (ASSIGNING_AUTHORITY) of type None> >>> print cx_4.to_er7() GATEWAY&1.3.6.1.4.1.21367.2011.2.5.17 >>> print parse_component(component) <Component ST (None) of type None> """ version = _get_version(version) encoding_chars = _get_encoding_chars(encoding_chars) try: component = Component(name, datatype, version=version, validation_level=validation_level, reference=reference) except InvalidName as e: if Validator.is_strict(validation_level): raise e component = Component(datatype, version=version, validation_level=validation_level, reference=reference) children = parse_subcomponents(text, component.datatype, version, encoding_chars, validation_level) if Validator.is_quiet(component.validation_level) and is_base_datatype(component.datatype, version) and \ len(children) > 1: component.datatype = None component.children = children return component
def parse_components(text, field_datatype='ST', version=None, encoding_chars=None, validation_level=None): """ Parse the given ER7-encoded components and return a list of :class:`hl7apy.core.Component` instances. :type text: ``basestring`` :param text: the ER7-encoded string containing the components to be parsed :type field_datatype: ``basestring`` :param field_datatype: the datatype of the components (e.g. ST) :type version: ``basestring`` :param version: the HL7 version (e.g. "2.5"), or ``None`` to use the default (see :func:`hl7apy.set_default_version`) :type encoding_chars: ``dict`` :param encoding_chars: a dictionary containing the encoding chars or None to use the default (see :func:`hl7apy.set_default_encoding_chars`) :param validation_level: the validation level. Possible values are those defined in :class:`hl7apy.consts.VALIDATION_LEVEL` class or ``None`` to use the default validation level (see :func:`hl7apy.set_default_validation_level`) :return: a list of :class:`hl7apy.core.Component` instances >>> components = "NUCLEAR^NELDA^W^^TEST" >>> xpn = parse_components(components, field_datatype="XPN") >>> print xpn [<Component XPN_1 (FAMILY_NAME) of type FN>, <Component XPN_2 (GIVEN_NAME) of type ST>, <Component XPN_3 (SECOND_AND_FURTHER_GIVEN_NAMES_OR_INITIALS_THEREOF) of type ST>, <Component XPN_5 (PREFIX_E_G_DR_) of type ST>] >>> print parse_components(components) [<Component ST (None) of type ST>, <Component ST (None) of type ST>, <Component ST (None) of type ST>, <Component ST (None) of type ST>, <Component ST (None) of type ST>] """ version = _get_version(version) encoding_chars = _get_encoding_chars(encoding_chars) component_sep = encoding_chars['COMPONENT'] components = [] for index, component in enumerate(text.split(component_sep)): if is_base_datatype(field_datatype, version): component_datatype = field_datatype component_name = None elif field_datatype is None or field_datatype == 'varies': component_datatype = None component_name = 'VARIES_{0}'.format(index+1) else: component_name = "{0}_{1}".format(field_datatype, index+1) component_datatype = None if component.strip() or component_name is None or component_name.startswith("VARIES_"): components.append(parse_component(component, component_name, component_datatype, version, encoding_chars, validation_level)) return components
def parse_component(text, name=None, datatype='ST', version=None, encoding_chars=None, validation_level=None, reference=None): """ Parse the given ER7-encoded component and return an instance of :class:`Component <hl7apy.core.Component>`. :type text: ``str`` :param text: the ER7-encoded string containing the components to be parsed :type name: ``str`` :param name: the component's name (e.g. XPN_2) :type datatype: ``str`` :param datatype: the datatype of the component (e.g. ST) :type version: ``str`` :param version: the HL7 version (e.g. "2.5"), or ``None`` to use the default (see :func:`set_default_version <hl7apy.set_default_version>`) :type encoding_chars: ``dict`` :param encoding_chars: a dictionary containing the encoding chars or None to use the default (see :func:`set_default_encoding_chars <hl7apy.set_default_encoding_chars>`) :type validation_level: ``int`` :param validation_level: the validation level. Possible values are those defined in :class:`VALIDATION_LEVEL <hl7apy.consts.VALIDATION_LEVEL>` class or ``None`` to use the default validation level (see :func:`set_default_validation_level <hl7apy.set_default_validation_level>`) :type reference: ``dict`` :param reference: a dictionary containing the element structure returned by :func:`load_reference <hl7apy.load_reference>` or :func:`find_reference <hl7apy.find_reference>` or belonging to a message profile :return: an instance of :class:`Component <hl7apy.core.Component>` >>> component = "GATEWAY&1.3.6.1.4.1.21367.2011.2.5.17" >>> cx_4 = parse_component(component, name="CX_4") >>> print(cx_4) <Component CX_4 (ASSIGNING_AUTHORITY) of type None> >>> print(cx_4.to_er7()) GATEWAY&1.3.6.1.4.1.21367.2011.2.5.17 >>> print(parse_component(component)) <Component ST (None) of type None> """ version = _get_version(version) encoding_chars = _get_encoding_chars(encoding_chars) validation_level = _get_validation_level(validation_level) try: component = Component(name, datatype, version=version, validation_level=validation_level, reference=reference) except InvalidName as e: if Validator.is_strict(validation_level): raise e component = Component(datatype, version=version, validation_level=validation_level, reference=reference) children = parse_subcomponents(text, component.datatype, version, encoding_chars, validation_level) if Validator.is_tolerant(component.validation_level) and is_base_datatype(component.datatype, version) and \ len(children) > 1: component.datatype = None component.children = children return component
def parse_components(text, field_datatype='ST', version=None, encoding_chars=None, validation_level=None, references=None): """ Parse the given ER7-encoded components and return a list of :class:`Component <hl7apy.core.Component>` instances. :type text: ``str`` :param text: the ER7-encoded string containing the components to be parsed :type field_datatype: ``str`` :param field_datatype: the datatype of the components (e.g. ST) :type version: ``str`` :param version: the HL7 version (e.g. "2.5"), or ``None`` to use the default (see :func:`set_default_version <hl7apy.set_default_version>`) :type encoding_chars: ``dict`` :param encoding_chars: a dictionary containing the encoding chars or None to use the default (see :func:`set_default_encoding_chars <hl7apy.set_default_encoding_chars>`) :type validation_level: ``int`` :param validation_level: the validation level. Possible values are those defined in :class:`VALIDATION_LEVEL <hl7apy.consts.VALIDATION_LEVEL>` class or ``None`` to use the default validation level (see :func:`set_default_validation_level <hl7apy.set_default_validation_level>`) :type references: ``list`` :param references: A list of the references of the :class:`Component <hl7apy.core.Component>`'s children :return: a list of :class:`Component <hl7apy.core.Component>` instances >>> components = "NUCLEAR^NELDA^W^^TEST" >>> xpn = parse_components(components, field_datatype="XPN") >>> print(xpn) [<Component XPN_1 (FAMILY_NAME) of type FN>, <Component XPN_2 (GIVEN_NAME) of type ST>, \ <Component XPN_3 (SECOND_AND_FURTHER_GIVEN_NAMES_OR_INITIALS_THEREOF) of type ST>, \ <Component XPN_5 (PREFIX_E_G_DR) of type ST>] >>> print(parse_components(components)) [<Component ST (None) of type ST>, <Component ST (None) of type ST>, <Component ST (None) of type ST>, \ <Component ST (None) of type ST>, <Component ST (None) of type ST>] """ version = _get_version(version) encoding_chars = _get_encoding_chars(encoding_chars) validation_level = _get_validation_level(validation_level) component_sep = encoding_chars['COMPONENT'] components = [] for index, component in enumerate(text.split(component_sep)): if is_base_datatype(field_datatype, version): component_datatype = field_datatype component_name = None elif field_datatype is None or field_datatype == 'varies': component_datatype = None component_name = 'VARIES_{0}'.format(index+1) else: component_name = "{0}_{1}".format(field_datatype, index+1) component_datatype = None try: reference = references[component_name]['ref'] \ if None not in (references, component_name) else None except KeyError: reference = None if component.strip() or component_name is None or component_name.startswith("VARIES_"): components.append(parse_component(component, component_name, component_datatype, version, encoding_chars, validation_level, reference)) return components
def parse_field(text, name=None, version=None, encoding_chars=None, validation_level=None, reference=None, force_varies=False): """ Parse the given ER7-encoded field and return an instance of :class:`Field <hl7apy.core.Field>`. :type text: ``str`` :param text: the ER7-encoded string containing the fields to be parsed :type name: ``str`` :param name: the field name (e.g. MSH_7) :type version: ``str`` :param version: the HL7 version (e.g. "2.5"), or ``None`` to use the default (see :func:`set_default_version <hl7apy.set_default_version>`) :type encoding_chars: ``dict`` :param encoding_chars: a dictionary containing the encoding chars or None to use the default (see :func:`set_default_encoding_chars <hl7apy.set_default_encoding_chars>`) :type validation_level: ``int`` :param validation_level: the validation level. Possible values are those defined in :class:`VALIDATION_LEVEL <hl7apy.consts.VALIDATION_LEVEL>` class or ``None`` to use the default validation level (see :func:`set_default_validation_level <hl7apy.set_default_validation_level>`) :type reference: ``dict`` :param reference: a dictionary containing the element structure returned by :func:`load_reference <hl7apy.load_reference>` or :func:`find_reference <hl7apy.find_reference>` or belonging to a message profile :type force_varies: ``boolean`` :param force_varies: flag that force the fields to use a varies structure when no reference is found. It is used when a segment ends with a field of type varies that thus support infinite children :return: an instance of :class:`Field <hl7apy.core.Field>` >>> field = "NUCLEAR^NELDA^W" >>> nk1_2 = parse_field(field, name="NK1_2") >>> print(nk1_2) <Field NK1_2 (NAME) of type XPN> >>> print(nk1_2.to_er7()) NUCLEAR^NELDA^W >>> unknown = parse_field(field) >>> print(unknown) <Field of type None> >>> print(unknown.to_er7()) NUCLEAR^NELDA^W """ version = _get_version(version) encoding_chars = _get_encoding_chars(encoding_chars) validation_level = _get_validation_level(validation_level) try: field = Field(name, version=version, validation_level=validation_level, reference=reference) except InvalidName: if force_varies: reference = ('leaf', 'varies', None, None) field = Field(name, version=version, validation_level=validation_level, reference=reference) else: field = Field(version=version, validation_level=validation_level, reference=reference) if name in ('MSH_1', 'MSH_2'): s = SubComponent(datatype='ST', value=text, validation_level=validation_level, version=version) c = Component(datatype='ST', validation_level=validation_level, version=version) c.add(s) field.add(c) else: children = parse_components(text, field.datatype, version, encoding_chars, validation_level, field.structure_by_name) if Validator.is_tolerant(validation_level) and is_base_datatype(field.datatype, version) and \ len(children) > 1: field.datatype = None field.children = children return field
def parse_field(text, name=None, version=None, encoding_chars=None, validation_level=None, reference=None, force_varies=False): """ Parse the given ER7-encoded field and return an instance of :class:`Field <hl7apy.core.Field>`. :type text: ``str`` :param text: the ER7-encoded string containing the fields to be parsed :type name: ``str`` :param name: the field name (e.g. MSH_7) :type version: ``str`` :param version: the HL7 version (e.g. "2.5"), or ``None`` to use the default (see :func:`set_default_version <hl7apy.set_default_version>`) :type encoding_chars: ``dict`` :param encoding_chars: a dictionary containing the encoding chars or None to use the default (see :func:`set_default_encoding_chars <hl7apy.set_default_encoding_chars>`) :type validation_level: ``int`` :param validation_level: the validation level. Possible values are those defined in :class:`VALIDATION_LEVEL <hl7apy.consts.VALIDATION_LEVEL>` class or ``None`` to use the default validation level (see :func:`set_default_validation_level <hl7apy.set_default_validation_level>`) :type reference: ``dict`` :param reference: a dictionary containing the element structure returned by :func:`load_reference <hl7apy.load_reference>` or :func:`find_reference <hl7apy.find_reference>` or belonging to a message profile :type force_varies: ``boolean`` :param force_varies: flag that force the fields to use a varies structure when no reference is found. It is used when a segment ends with a field of type varies that thus support infinite children :return: an instance of :class:`Field <hl7apy.core.Field>` >>> field = "NUCLEAR^NELDA^W" >>> nk1_2 = parse_field(field, name="NK1_2") >>> print(nk1_2) <Field NK1_2 (NAME) of type XPN> >>> print(nk1_2.to_er7()) NUCLEAR^NELDA^W >>> unknown = parse_field(field) >>> print(unknown) <Field of type None> >>> print(unknown.to_er7()) NUCLEAR^NELDA^W """ version = _get_version(version) encoding_chars = _get_encoding_chars(encoding_chars, version) validation_level = _get_validation_level(validation_level) try: field = Field(name, version=version, validation_level=validation_level, reference=reference) except InvalidName: if force_varies: reference = ('leaf', None, 'varies', None, None, -1) field = Field(name, version=version, validation_level=validation_level, reference=reference) else: field = Field(version=version, validation_level=validation_level, reference=reference) if name in ('MSH_1', 'MSH_2'): s = SubComponent(datatype='ST', value=text, validation_level=validation_level, version=version) c = Component(datatype='ST', validation_level=validation_level, version=version) c.add(s) field.add(c) else: children = parse_components(text, field.datatype, version, encoding_chars, validation_level, field.structure_by_name) if Validator.is_tolerant(validation_level) and is_base_datatype(field.datatype, version) and \ len(children) > 1: field.datatype = None field.children = children return field