class Group(Element):
    """
    Class representing an HL7 segment group

    :type name: ``str``
    :param name: the HL7 name of the message (e.g. RSP_K21_QUERY_RESPONSE)

    :type parent: an instance of :class:`Message <hl7apy.core.Message>`, :class:`Group <hl7apy.core.Group>`
        or None
    :param parent: the parent

    :param reference: the reference structure (see :func:`load_reference <hl7apy.load_reference>`)

    :type version: ``str``
    :param version: the HL7 version (e.g. "2.5"), or ``None`` to use the default
        (see :func:`get_default_version <hl7apy.get_default_version>`)

    :param validation_level: the validation level. Possible values are those defined in
        :class:`VALIDATION_LEVEL <hl7apy.consts.VALIDATION_LEVEL>` or ``None`` to use the default validation
        level (see :func:`get_default_validation_level <hl7apy.get_default_validation_level>`)
    """
    child_parser = ('parse_segment', 'parse_segments')

    def __init__(self, name=None, parent=None, reference=None, version=None,
                 validation_level=None, traversal_parent=None):

        # self.child_classes = (Segment, Group)
        self.child_classes = {"SEG": Segment, "GRP": Group}
        super(Group, self).__init__(name, parent, reference, version, validation_level, traversal_parent)
        if self.name is None and Validator.is_strict(self.validation_level):
            raise OperationNotAllowed("Cannot instantiate an unknown Element with strict validation")

    def add_segment(self, name):
        """
        Create an instance of :class:`Segment <hl7apy.core.Segment>` having the given name

        :param name: the name of the segment to be created (e.g. PID)
        :return: an instance of :class:`Segment <hl7apy.core.Segment>`

        >>> m = Message('QBP_Q11')
        >>> qpd = m.add_segment('QPD')
        >>> print(qpd)
        <Segment QPD>
        >>> print(qpd in m.children)
        True
        """
        return self.children.create_element(name)

    def add_group(self, name):
        """
        Create an instance of :class:`Group <hl7apy.core.Group>` having the given name

        :param name: the name of the group to be created (e.g. OML_O33_PATIENT)
        :return: an instance of :class:`Group <hl7apy.core.Group>`

        >>> m = Message('OML_O33')
        >>> patient = m.add_group('OML_O33_PATIENT')
        >>> print(patient)
        <Group OML_O33_PATIENT>
        >>> print(patient in m.children)
        True
        """

        return self.children.create_element(name)

    def find_child_reference(self, name):
        name = name.upper()
        if isinstance(self.structure_by_name, MutableMapping):
            element = self.structure_by_name.get(name) or self.structure_by_longname.get(name)
        else:
            element = None
        if element is None:  # not found in self.structure
            if _valid_z_segment_name(name):
                element = {'cls': Segment, 'name': name, 'ref': ('sequence', ())}
            else:
                element = find_reference(name, self.child_classes.values(), self.version)
                if Validator.is_strict(self.validation_level):  # cannot be created if validation is strict
                    raise ChildNotValid(name, self)
        return element

    def parse_child(self, text, child_name=None, reference=None):
        ref = self.find_child_reference(child_name)
        if ref['cls'] == Group:
            g = Group(child_name, validation_level=self.validation_level, version=self.version,
                      reference=ref['ref'])
            g.value = text
            return g
        else:
            # Check that the value starts with the correct name of the segment.
            # If it doesn't, it sets the reference to None to force the initializer to search again for the
            # segment reference. # This is done to avoid situation like m.zip = 'PAP|||asb|' which results in
            # this call to parse childparse_child(self, 'PAP|||asb|', 'zip', ('sequence', ()))
            if text[:3] != child_name:
                reference = None
            kwargs = {'encoding_chars': self.encoding_chars, 'reference': reference}
            return Element.parse_child(self, text, **kwargs)

    def parse_children(self, text, find_groups=True, **kwargs):
        try:
            kwargs = {'references': self.reference, 'find_groups': find_groups}
        except AttributeError:
            kwargs = {'references': None, 'find_groups': False}

        children = super(Group, self).parse_children(text, **kwargs)
        self.children = children

    def _set_value(self, value):
        self.parse_children(value)

    def _get_value(self):
        return self.to_er7()

    value = property(_get_value, _set_value)

    def _is_valid_child(self, child):
        if child.name is None and Validator.is_strict(self.validation_level):
            return False
        return Element._is_valid_child(self, child)

    def _get_children(self, trailing=False):
        if Validator.is_strict(self.validation_level):
            children = self.children.get_ordered_children()
        else:
            children = self.children.get_children()
        if not trailing:
            children = _remove_trailing(children)
        return children class Field(SupportComplexDataType):
    """
    Class representing an HL7 field.

    :type name: ``str``
    :param name: the HL7 name of the field (e.g. PID_5)

    :type datatype: ``str``
    :param datatype: the datatype of the field (e.g. CE)

    :type parent: an instance of :class:`hl7apy.core.Segment` or None
    :param parent: the parent

    :param reference: the reference structure (see :func:`load_reference <hl7apy.load_reference>`)

    :type version: ``str``
    :param version: the HL7 version (e.g. "2.5"), or ``None`` to use the default
        (see :func:`get_default_version <hl7apy.get_default_version>`)

    :param validation_level: the validation level. Possible values are those defined in
        :class:`VALIDATION_LEVEL <hl7apy.consts.VALIDATION_LEVEL>` or ``None`` to use the default validation
        level (see :func:`get_default_validation_level <hl7apy.get_default_validation_level>`)

    :type traversal_parent: an instance of :class:`Segment <hl7apy.core.Segment>` or None
    :param traversal_parent: the temporary parent used during traversal
    """
    child_classes = {"CMP": Component}
    child_parser = ('parse_component', 'parse_components')

    def __init__(self, name=None, datatype=None, parent=None, reference=None,
                 version=None, validation_level=None, traversal_parent=None):

        SupportComplexDataType.__init__(self)

        if name is None and Validator.is_strict(validation_level) and datatype != 'varies':
            raise OperationNotAllowed("Cannot instantiate an unknown Element with strict validation")

        if datatype == 'varies' and reference is None:
            reference = ('leaf', None, 'varies', None, None, -1)

        try:
            Element.__init__(self, name, parent, reference, version,
                             validation_level, traversal_parent)
        except InvalidName:
            if _valid_z_field_name(name):
                datatype = datatype or 'ST'
                if is_base_datatype(datatype, version):
                    reference = ('leaf', None, datatype, None, None, -1)
                else:
                    if version is None:
                        version = get_default_version()
                    dt_struct = load_reference(datatype, "Datatypes_Structs", version)
                    reference = ('sequence', dt_struct, datatype, None, None, -1)
                Element.__init__(self, name, parent, reference, version,
                                 validation_level, traversal_parent)
            else:
                raise

        if datatype is not None and Validator.is_strict(validation_level) and \
                datatype != 'varies' and datatype != self.datatype:
            raise OperationNotAllowed("Cannot assign a different datatype with strict validation")

        if datatype is not None:  # force the datatype to be the one chosen by the user
            self.datatype = datatype
        elif self.name is None:  # if it is unknown and no datatype has been given
            self.datatype = None

    def add_component(self, name):
        """
        Create an instance of :class:`Component <hl7apy.core.Component>` having the given name

        :param name: the name of the component to be created (e.g. XPN_2)
        :return: an instance of :class:`Component <hl7apy.core.Component>`

        >>> s = Field('PID_5')
        >>> print(s.add_component('XPN_2'))
        <Component XPN_2 (GIVEN_NAME) of type ST>
        """
        return self.children.create_element(name)

    def find_child_reference(self, name):
        if is_base_datatype(self.datatype, self.version):
            # create reference in case of base datatypes
            if name == self.datatype:
                element = {'cls': Component,
                           'name': self.datatype,
                           'ref': ('leaf', None, self.datatype, None, None, -1)}
                return element
            raise ChildNotFound(name)
        elif self.datatype == 'varies' and _valid_child_name(name, self.datatype):
            # create reference for children in case of datatype VARIES
            element = {'cls': Component,
                       'name': name,
                       'ref': ('leaf', None, 'varies', None, None, -1)}
            return element

        return super(Field, self).find_child_reference(name)

    def add(self, obj):
        """
        Add an instance of :class:`Component <hl7apy.core.Component>` to the list of children

        :param obj: an instance of :class:`Component <hl7apy.core.Component>`

        >>> f = Field('PID_5')
        >>> f.xpn_1 = 'EVERYMAN'
        >>> c = Component('XPN_2')
        >>> c.value = 'ADAM'
        >>> f.add(c)
        >>> print(f.to_er7())
        EVERYMAN^ADAM
        """
        # base datatype components can't have more than one child
        if self.name and is_base_datatype(self.datatype, self.version) and \
                len(self.children) >= 1:
            raise MaxChildLimitReached(self, obj, 1)

        return super(Field, self).add(obj)

    def parse_child(self, text, child_name=None, reference=None):
        kwargs = {'encoding_chars': self.encoding_chars, 'reference': reference, 'name': child_name}
        if reference is not None:
            kwargs['datatype'] = reference[2]
        return super(Field, self).parse_child(text, **kwargs)

    def parse_children(self, text, **kwargs):
        kwargs = {'field_datatype': self.datatype, 'references': self.structure_by_name}
        return super(Field, self).parse_children(text, **kwargs)

    def to_er7(self, encoding_chars=None, trailing_children=False):
        """
        Return the ER7-encoded string

        :type encoding_chars: ``dict``
        :param encoding_chars: a dictionary containing the encoding chars or None to use the default
            (see :func:`get_default_encoding <hl7apy.get_default_encoding_chars>`)

        :type trailing_children: ``bool``
        :param trailing_children: if ``True``, trailing children will be added even if their value is None

        :return: the ER7-encoded string

        >>> msh_9 = Field("MSH_9")
        >>> msh_9.value = "ADT^A01^ADT_A01"
        >>> print(msh_9.to_er7())
        ADT^A01^ADT_A01
        """
        if encoding_chars is None:
            encoding_chars = self.encoding_chars
        if self.is_named('MSH_1'):
            try:
                return self.msh_1_1.children[0].value.value
            except IndexError:
                return self.msh_1_1.children[0].value
        elif self.is_named('MSH_2'):
            try:
                return self.msh_2_1.children[0].value.value
            except IndexError:
                return self.msh_2_1.children[0].value
        return super(Field, self).to_er7(encoding_chars, trailing_children)

    def is_z_element(self):
        return self.name is not None and _valid_z_field_name(self.name)

    def _set_value(self, value):
        if self.name in ('MSH_1', 'MSH_2'):
            c = Component(datatype='ST', version=self.version,
                          validation_level=self.validation_level)
            c.value = value
            self.add(c)
        else:
            super(Field, self)._set_value(value)

    def _get_value(self):
        return super(Field, self)._get_value()

    value = property(_get_value, _set_value)

    def _get_children(self, trailing=False):
        if self.datatype == 'varies':
            children = [self.children.indexes['VARIES_{0}'.format(i + 1)] for i in xrange(len(self.children))]
            children = _remove_trailing(children)
            children.extend([[c] for c in self.children if c.is_unknown()])
            return children
        else:
            return SupportComplexDataType._get_children(self)

    def _get_traversal_children(self, name):
        """
        Retrieve component and subcomponent indexes from the given traversal path
        (e.g. PID_1_2 -> component=2, subcomponent=None)
        """
        name = name.upper()
        parts = name.split('_')
        try:
            assert 3 <= len(parts) <= 4
            prefix = "{0}_{1}".format(parts[0], parts[1])
            component = int(parts[2])
            subcomponent = int(parts[3]) if len(parts) == 4 else None
        except (AssertionError, ValueError):
            return None, None
        else:
            if prefix != self.name:
                return None, None
        return component, subcomponent

    def _do_traversal(self, mode, name, value=None):
        try:
            if mode == 'get':
                return super(Field, self).__getattr__(name)
            elif mode == 'set':
                super(Field, self).__setattr__(name, value)
            else:
                super(Field, self).__delattr__(name)
        except ChildNotFound as e:
            component, subcomponent = self._get_traversal_children(name)
            if component is None:
                raise ChildNotFound(name)

            if is_base_datatype(self.datatype, self.version):
                if subcomponent is not None or component != 1:
                    raise ChildNotFound(name)
                component_name = self.datatype
            else:
                component_name = '{0}_{1}'.format(self.datatype, component)
            if subcomponent is None:
                if mode == 'get':
                    return getattr(self, component_name)
                elif mode == 'set':
                    setattr(self, component_name, value)
                else:
                    delattr(self, component_name)
            else:
                component = getattr(self, component_name)
                component_ref = self.structure_by_name[component_name]['ref']
                component_datatype = component_ref[2]
                subcomponent_name = '{0}_{1}'.format(component_datatype, subcomponent)
                try:
                    if mode == 'get':
                        return getattr(component, subcomponent_name)
                    elif mode == 'set':
                        setattr(component, subcomponent_name, value)
                    else:
                        delattr(component, subcomponent_name)
                except ChildNotFound:
                    raise ChildNotFound(name)

    def __getattr__(self, name):
        return self._do_traversal('get', name)

    def __setattr__(self, name, value):
        self._do_traversal('set', name, value)

    def __delattr__(self, name):
        self._do_traversal('del', name) class Component(SupportComplexDataType, CanBeVaries):
    """
    Class representing an HL7 component.

    :type name: ``str``
    :param name: the HL7 name of the component (e.g. XPN_2)

    :type datatype: ``str``
    :param datatype: the datatype of the component (e.g. CE)

    :type parent: an instance of :class:`hl7apy.core.Field` or None
    :param parent: the parent

    :param reference: the reference structure (see :func:`load_reference <hl7apy.load_reference>`)

    :type version: ``str``
    :param version: the HL7 version (e.g. "2.5"), or ``None`` to use the default
        (see :func:`get_default_version <hl7apy.get_default_version>`)

    :type validation_level: ``int``
    :param validation_level: the validation level. Possible values are those defined in
        :class:`VALIDATION_LEVEL <hl7apy.consts.VALIDATION_LEVEL>` or ``None`` to use the default validation
        level (see :func:`get_default_validation_level <hl7apy.get_default_validation_level>`)

    :type traversal_parent: an instance of :class:`Field hl7apy.core.Field` or None
    :param traversal_parent: the temporary parent used during traversal
    """
    child_classes = {"CMP": SubComponent}
    child_parser = ('parse_subcomponent', 'parse_subcomponents')

    def __init__(self, name=None, datatype=None, parent=None, reference=None,
                 version=None, validation_level=None, traversal_parent=None):

        SupportComplexDataType.__init__(self)

        # if datatype == 'varies' and reference is None:
        #     reference = ('leaf', None, 'varies', None, None, -1)

        CanBeVaries.__init__(self, name, datatype, parent, reference,
                             version, validation_level, traversal_parent)

        if self.is_unknown() and Validator.is_strict(validation_level) and \
                not is_base_datatype(self.datatype, self.version) and self.datatype != 'varies':
            raise OperationNotAllowed("Cannot instantiate an unknown Element with strict validation")

    def add_subcomponent(self, name):
        """
        Create an instance of :class:`SubComponent <hl7apy.core.SubComponent>` having the given name

        :param name: the name of the subcomponent to be created (e.g. CE_1)
        :return: an instance of :class:`SubComponent <hl7apy.core.SubComponent>`

        >>> c = Component(datatype='CE')
        >>> ce_1 = c.add_subcomponent('CE_1')
        >>> print(ce_1)
        <SubComponent CE_1>
        >>> print(ce_1 in c.children)
        True
        """
        if self.is_unknown() and is_base_datatype(self.datatype):
            # An unknown component can't have a child
            raise ChildNotValid(name, self)
        return self.children.create_element(name)

    def add(self, obj):
        """
        Add an instance of :class:`SubComponent <hl7apy.core.SubComponent>` to the list of children

        :param obj: an instance of :class:`SubComponent <hl7apy.core.SubComponent>`

        >>> c = Component('CX_10')
        >>> s = SubComponent(name='CWE_1', value='EXAMPLE_ID')
        >>> s2 = SubComponent(name='CWE_4', value='ALT_ID')
        >>> c.add(s)
        >>> c.add(s2)
        >>> print(c.to_er7())
        EXAMPLE_ID&&&ALT_ID
        """
        # base datatype components can't have more than one child
        if self.name and is_base_datatype(self.datatype, self.version) and \
                len(self.children) >= 1:
            raise MaxChildLimitReached(self, obj, 1)

        # the name is different from the datatype (i.e. the name has been forced to be equal to the datatype)
        try:
            if obj.name and obj.name != obj.datatype:
                try:
                    if not _valid_child_name(obj.name, self.datatype):
                        raise ChildNotValid(obj.name, self)
                except AttributeError:
                    pass
        except ChildNotFound:  # obj.datatype causes ChildNotFound for some Elements (Message, Groups etc)
            raise ChildNotValid(obj, self)

        return super(Component, self).add(obj)

    def parse_child(self, text, child_name=None, reference=None):
        kwargs = {'name': child_name}
        if reference is not None:
            kwargs['datatype'] = reference[2]
        return super(Component, self).parse_child(text, **kwargs)

    def parse_children(self, text, **kwargs):
        kwargs = {'component_datatype': self.datatype, 'encoding_chars': self.encoding_chars}
        return super(Component, self).parse_children(text, **kwargs) class SubComponent(CanBeVaries):
    """
    Class representing an HL7 subcomponent.

    :type name: ``str``
    :param name: the HL7 name of the subcomponent (e.g. CWE_1)

    :type datatype: ``str``
    :param datatype: the datatype of the component (e.g. ST)

    :type value: ``str`` or instance of :class:`BaseDataType <hl7apy.base_datatypes.BaseDataType>`
    :param value: the value of the subcomponent (e.g. ADT_A01)

    :type parent: an instance of :class:`Component <hl7apy.core.Component>` or None
    :param parent: the parent

    :param reference: the reference structure (see :func:`load_refernce <hl7apy.load_reference>`)

    :type version: ``str``
    :param version: the HL7 version (e.g. "2.5"), or ``None`` to use the default
        (see :func:`get_default_version <hl7apy.get_default_version>`)

    :type validation_level: ``int``
    :param validation_level: the validation level. Possible values are defined in
        :class:`VALIDATION_LEVEL <hl7apy.consts.VALIDATION_LEVEL>` class or ``None`` to use the default
        validation level (see :func:`get_default_validation_level <hl7apy.get_default_validation_level>`)

    :type traversal_parent: an instance of :class:`Component <hl7apy.core.Component>` or None
    :param traversal_parent: the temporary parent used during traversal
    """
    child_classes = {"CMP": None}
    cls_attrs = Element.cls_attrs + ['datatype', '_datatype', 'table', 'long_name', 'max_length']

    def __init__(self, name=None, datatype=None, value=None, parent=None,
                 reference=None, version=None, validation_level=None,
                 traversal_parent=None):

        self._parent = None  # we need to initialize it
        self._datatype = None  # we need to initialize it
        if not name and datatype is None:
            raise OperationNotAllowed("Cannot instantiate a SubComponent with name and datatype both empty")

        CanBeVaries.__init__(self, name, datatype, parent, reference,
                             version, validation_level, traversal_parent)

        if _valid_child_name(name, 'VARIES') and self.datatype is None:
            self.datatype = 'ST'

        self.value = value

    def add(self, obj):
        raise OperationNotAllowed("Cannot add children to a SubComponent")

    def to_er7(self, encoding_chars=None, trailing_children=False):
        """
        Return the ER7-encoded string

        :type encoding_chars: ``dict``
        :param encoding_chars: a dictionary containing the encoding chars or None to use the default
            (see :func:`get_default_encoding_chars <hl7apy.get_default_encoding_chars>`)

        :type trailing_children: ``bool``
        :param trailing_children: if ``True``, trailing children will be added even if their value is None

        :return: the ER7-encoded string

        >>> s = SubComponent("CE_1")
        >>> s.value = "IDENTIFIER"
        >>> print(s.to_er7())
        IDENTIFIER
        """

        if encoding_chars is None:
            encoding_chars = self.encoding_chars
        try:
            return self.value.to_er7(encoding_chars)
        except AttributeError:
            return ''

    def _set_value(self, value):
        if value is None:
            self._value = None
        else:
            if value and isinstance(value, basestring):
                self._value = datatype_factory(self.datatype, value, self.version,
                                               self.validation_level)
            elif not value or isinstance(value, BaseDataType):
                self._value = value
            else:
                raise ValueError('Cannot assign {0}'.format(value.classname))
            self.set_parent_to_traversal()

    def _get_value(self):
        return self._value

    value = property(_get_value, _set_value)

    def _get_datatype(self):
        return self._datatype

    def _set_datatype(self, datatype):

        if datatype and not is_base_datatype(datatype, self.version):  # only base datatypes allowed
            raise OperationNotAllowed("Cannot set a complex datatype to a SubComponent")

        if Validator.is_strict(self.validation_level) and \
                self.datatype is not None and datatype != self.datatype:
            raise OperationNotAllowed("Cannot change datatype using STRICT validation")
        # if the SC is already valued it is impossible to change the datatype
        if hasattr(self, 'value') and self.value:
            raise OperationNotAllowed("Cannot change datatype: the Subcomponent is valued")
        else:
            # change also the parent datatype if it is a base datatype
            if self.parent is not None and is_base_datatype(self.parent.datatype, self.parent.version) and \
                    self.parent.datatype != datatype:  # avoid infinite recursion
                self.parent.datatype = datatype
            self._datatype = datatype

    datatype = property(_get_datatype, _set_datatype)

    def __repr__(self):
        return '<SubComponent {0}>'.format(self.name or self.datatype) def parse_message(message, validation_level=None, find_groups=True, message_profile=None, report_file=None,
                  force_validation=False):
    """
    Parse the given ER7-encoded message and return an instance of :class:`Message <hl7apy.core.Message>`.

    :type message: ``str``
    :param message: the ER7-encoded message to be parsed

    :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 find_groups: ``bool``
    :param find_groups: if ``True``, automatically assign the segments found to the appropriate
        :class:`Groups <hl7apy.core.Group>` instances. If ``False``, the segments found are assigned as
        children of the :class:`Message <hl7apy.core.Message>` instance
        
    :type force_validation: ``bool``
    :type force_validation: if ``True``, automatically forces the message validation after the end of the parsing

    :return: an instance of :class:`Message <hl7apy.core.Message>`

    >>> message = "MSH|^~\&|GHH_ADT||||20080115153000||OML^O33^OML_O33|0123456789|P|2.5||||AL\\rPID|1||" \
    "566-554-3423^^^GHH^MR||EVERYMAN^ADAM^A|||M|||2222 HOME STREET^^ANN ARBOR^MI^^USA||555-555-2004|||M\\r"
    >>> m = parse_message(message)
    >>> print(m)
    <Message OML_O33>
    >>> print(m.msh.sending_application.to_er7())
    GHH_ADT
    >>> print(m.children)
    [<Segment MSH>, <Group OML_O33_PATIENT>]
    """
    message = message.lstrip()
    encoding_chars, message_structure, version = get_message_info(message)
    validation_level = _get_validation_level(validation_level)

    try:
        reference = message_profile[message_structure] if message_profile else None
    except KeyError:
        raise MessageProfileNotFound()

    try:
        m = Message(name=message_structure, reference=reference, version=version,
                    validation_level=validation_level, encoding_chars=encoding_chars)
    except InvalidName:
        m = Message(version=version, validation_level=validation_level,
                    encoding_chars=encoding_chars)

    try:
        children = parse_segments(message, m.version, encoding_chars, validation_level, m.reference, find_groups)
    except AttributeError:  # m.reference can raise i
        children = parse_segments(message, m.version, encoding_chars, validation_level, find_groups=False)

    m.children = children

    if force_validation:
        if message_profile is None:
            Validator.validate(m, report_file=report_file)
        else:
            Validator.validate(m, message_profile[message_structure], report_file=report_file)

    return m def parse_segments(text, version=None, encoding_chars=None, validation_level=None, references=None, find_groups=False):
    """
    Parse the given ER7-encoded segments and return a list of :class:`hl7apy.core.Segment` instances.

    :type text: ``str``
    :param text: the ER7-encoded string containing the segments to be parsed

    :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:`validation_level <hl7apy.set_default_validation_level>`)

    :type references: ``list``
    :param references: A list of the references of the :class:`Segment <hl7apy.core.Segment>`'s children

    :type find_groups: ``bool``
    :param find_groups: if ``True``, automatically assign the segments found to the appropriate
        :class:`Groups <hl7apy.core.Group>` instances. If ``False``, the segments found are assigned as
        children of the :class:`Message <hl7apy.core.Message>` instance

    :return: a list of :class:`Segment <hl7apy.core.Segment>` instances

    >>> segments = "EVN||20080115153000||||20080114003000\\rPID|1||566-554-3423^^^GHH^MR||EVERYMAN^ADAM^A|||M|||" \
    "2222 HOME STREET^^ANN ARBOR^MI^^USA||555-555-2004|||M\\r"
    >>> print(parse_segments(segments))
    [<Segment EVN>, <Segment PID>]
    """
    version = _get_version(version)
    encoding_chars = _get_encoding_chars(encoding_chars, version)
    validation_level = _get_validation_level(validation_level)

    segment_sep = encoding_chars['SEGMENT']
    segments = []

    parents_refs = [(None, references)]
    current_parent = None
    for s in text.split(segment_sep):
        if len(s) > 0:
            segment_name = s[:3]
            for x in xrange(len(parents_refs)):
                if not find_groups:
                    segment = parse_segment(s.strip(), version, encoding_chars, validation_level)
                    segments.append(segment)
                else:
                    ref, parents_refs = _get_segment_reference(segment_name, parents_refs)
                    if ref is None:
                        # group not found at the current level, go back to the previous level
                        if current_parent is not None:
                            parents_refs.pop()
                            current_parent = current_parent.parent
                    else:
                        if current_parent is None and parents_refs[-1][0] is not None or \
                                current_parent is not None and parents_refs[-1][0] != current_parent.name:
                            # create the parents group of the segment
                            if current_parent is not None:
                                cur_idx = parents_refs.index((current_parent.name, current_parent.reference))
                            else:
                                cur_idx = parents_refs.index((None, references))
                            for p_ref in parents_refs[cur_idx + 1:]:
                                group = Group(p_ref[0], version=version, reference=p_ref[1],
                                              validation_level=validation_level)
                                if current_parent is None:
                                    segments.append(group)
                                else:
                                    current_parent.add(group)
                                current_parent = group
                        elif current_parent is not None and segment_name in [c.name for c in current_parent.children] \
                                and current_parent.repetitions[segment_name][1] == 1:
                            # The number of instances allowed is reached so we create another instance of the same
                            group = Group(current_parent.name, version=version, reference=current_parent.reference,
                                          validation_level=validation_level)

                            if current_parent.parent is None:
                                segments.append(group)
                            else:
                                current_parent.parent.add(group)
                            current_parent = group

                        segment = parse_segment(s.strip(), version, encoding_chars, validation_level, ref)
                        if current_parent is None:
                            segments.append(segment)
                        else:
                            current_parent.add(segment)
                        break
    return segments def parse_segment(text, version=None, encoding_chars=None, validation_level=None, reference=None):
    """
    Parse the given ER7-encoded segment and return an instance of :class:`Segment <hl7apy.core.Segment>`.

    :type text: ``str``
    :param text: the ER7-encoded string containing the segment to be parsed

    :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>`, :func:`find_reference <hl7apy.find_reference>` or
        belonging to a message profile

    :return: an instance of :class:`Segment <hl7apy.core.Segment>`

    >>> segment = "EVN||20080115153000||||20080114003000"
    >>> s =  parse_segment(segment)
    >>> print(s)
    <Segment EVN>
    >>> print(s.to_er7())
    EVN||20080115153000||||20080114003000
    """
    version = _get_version(version)
    encoding_chars = _get_encoding_chars(encoding_chars, version)
    validation_level = _get_validation_level(validation_level)

    segment_name = text[:3]
    text = text[4:] if segment_name != 'MSH' else text[3:]
    segment = Segment(segment_name, version=version, validation_level=validation_level,
                      reference=reference)
    segment.children = parse_fields(text, segment_name, version, encoding_chars, validation_level,
                                    segment.structure_by_name, segment.allow_infinite_children)
    return segment 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 class Validator(object):
    def __init__(self, level):
    def validate(element, reference=None, report_file=None):
        def _check_z_element(el, errs, warns):
        def _check_repetitions(el, children, cardinality, child_name, errs):
        def _check_table_compliance(el, ref, warns):
        def _check_length(el, ref, warns):
        def _check_datatype(el, ref, errs):
        def _get_valid_children_info(ref):
        def _get_child_reference_info(ref):
        def _check_known_element(el, ref, errs, warns):
        def _is_valid(el, ref, errs, warns):
    def is_strict(level):
    def is_tolerant(level):
    def is_quiet(level): class ValidationError(HL7apyException):
    """
    Error during validation

    >>> from hl7apy.parser import parse_message
    >>> from hl7apy.validation import VALIDATION_LEVEL
    >>> msh = 'MSH|^~\&|SENDING APP|SENDING FAC|REC APP|REC FAC|20080115153000||ADT^A01^ADT_A01|' \
    '0123456789|P|2.5||||AL\\r'
    >>> evn = 'EVN||20080115153000||AAA|AAA|20080114003000\\r'
    >>> pid = 'PID|1||123-456-789^^^HOSPITAL^MR||SURNAME^NAME^A|||M|||1111 SOMEWHERE STREET^^SOMEWHERE^^^USA||' \
    '555-555-2004~444-333-222|||M\\r'
    >>> message = msh + evn + pid
    >>> parse_message(message, validation_level=VALIDATION_LEVEL.STRICT, force_validation=True)  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    ValidationError: Missing required child ADT_A01.PV1
    """
import os
import unittest
import hl7apy
from hl7apy.core import Group, Field, Component, SubComponent
from hl7apy.parser import parse_message, parse_segments, parse_segment, parse_field
from hl7apy.validation import VALIDATION_LEVEL
from hl7apy.exceptions import ValidationError
            regex = 'Error:.*'
        elif error_type == 'WARNING':
            regex = 'Warning:.*'
        self.assertRegexpMatches(s, regex)
        os.remove(self.report_file)

    def test_well_structured_message(self):
        """
        Tests that a valid message is validated
        """
        for msg_str in (self.adt_a01, self.oml_o33):
            msg = self._create_message(msg_str)
            self.assertTrue(msg.validate())

    # def test_oml_o33_2_message(self):
    #     # This test is failing because the oml_o33_2 parsing creates a
    #     # OML_O33_ORDER_PRIOR which expect a OBR segment, but the parser's
    #     # implementation puts the OBR segment in the group OML_O33_OBSERVATION_REQUEST
    #     # This behaviour HL7 fault
    #     msg = self._create_message(self.oml_o33_2)
    #     self.assertTrue(msg.validate())

    def test_unknown_message(self):
        msg = self._create_message(self.adt_a01)
        msg.name = None
        self.assertRaises(ValidationError, msg.validate, report_file=self.report_file)
        self._test_report_file('ERROR')

    def test_missing_required_group(self):
        """
        Test that if a required group is not present the messase in not validated
        The message misses the oml_o33_specimen group
        """
        msg = self._create_message(self.oml_o33)
        del msg.oml_o33_specimen
        self.assertRaises(ValidationError, msg.validate, report_file=self.report_file)
        self._test_report_file('ERROR')

    def test_missing_required_segment(self):
        """
        Tests that when a required segment is missing the message is not validated
        The message used misses the EVN segment
        """
        msg = self._create_message(self.adt_a01)
        del msg.evn
        self.assertRaises(ValidationError, msg.validate, report_file=self.report_file)
        self._test_report_file('ERROR')

    def test_missing_required_field(self):
        """
        Tests that when a required field is missing the message is not validated
        The message used misses the msh_9 segment
        """
        msg = self._create_message(self.adt_a01)
        del msg.msh.msh_9
        self.assertRaises(ValidationError, msg.validate, report_file=self.report_file)
        self._test_report_file('ERROR')

    def test_missing_required_component(self):
        """
        Tests that when a required component is missing the message is not validated
        The message used misses the msg_1 component in the msh_9 field
        """
        msg = self._create_message(self.adt_a01)
        del msg.msh.msh_9.msg_1
        self.assertRaises(ValidationError, msg.validate, report_file=self.report_file)
        self._test_report_file('ERROR')

    def test_missing_required_subcomponent(self):
        """
        Tests that when a required subcomponent is missing the message is not validated
        The message used misses the cwe_1 subcomponent in the cx_10 component
        """
        msg = self._create_message(self.oml_o33)
        msg.pid.pid_3.cx_10 = 'xxx&yyy&zzz&&&&&&&'
        del msg.pid.pid_3.cx_10.cwe_1
        self.assertRaises(ValidationError, msg.validate, report_file=self.report_file)
        self._test_report_file('ERROR')

    def test_having_more_groups(self):
        """
        Tests that when a group occurs more than the allowed times the message is not validated
        The message used has 2 occurrence of oml_o33_group
        """
        msg = self._create_message(self.oml_o33)
        oml_o33_patient = Group('OML_O33_PATIENT')
        segments = parse_segments('PID|||1010110909194822^^^GATEWAY_IL&1.3.6.1.4.1.21367.2011.2.5.17&ISO^PK||PIPPO^PLUTO^^^^^L||19790515|M|||VIA DI TOPOLINO^CAGLIARI^CAGLIARI^^09100^100^H^^092009~^^^^^^L|||||||PPPPPP79E15B354I^^^CF|||||CAGLIARI|||100\rPV1||O|||||||||||||||||1107080001^^^LIS')
        oml_o33_patient.children = segments
        msg.add(oml_o33_patient)
        self.assertRaises(ValidationError, msg.validate, report_file=self.report_file)
        self._test_report_file('ERROR')

    def test_having_more_segments(self):
        """
        Tests that when a segment occurs more than the allowed times the message is not validated
        The message used has 2 occurrence of the segment evn
        """
        msg = self._create_message(self.adt_a01)
        evn = parse_segment('EVN||20080115153000||AAA|AAA|20080114003000', version='2.6')
        msg.add(evn)
        self.assertRaises(ValidationError, msg.validate, report_file=self.report_file)
        self._test_report_file('ERROR')

    def test_having_more_fields(self):
        """
        Tests that when a field occurs more than the allowed times the message is not validated
        The message used has 2 occurrence of msh_9
        """
        msg = self._create_message(self.adt_a01)
        msh_9 = parse_field('RSP^SLI^RSP_K11', 'MSH_9', version="2.6")
        msg.msh.add(msh_9)
        self.assertRaises(ValidationError, msg.validate, report_file=self.report_file)
        self._test_report_file('ERROR')

    def test_wrong_datatype_field(self):
        """
        Tests that if a field is not of the correct datatype the message is not validate
        The message used has the MSH_9 of type ST
        """
        msg = self._create_message(self.adt_a01)

        msh_9 = Field('MSH_9', datatype='ST', version='2.6')