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_fields(text, name_prefix=None, version=None, encoding_chars=None, validation_level=None,
                 references=None, force_varies=False):
    """
    Parse the given ER7-encoded fields and return a list of :class:`hl7apy.core.Field`.

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

    :type name_prefix: ``str``
    :param name_prefix: the field prefix (e.g. MSH)

    :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:`Field <hl7apy.core.Field>`'s children

    :type force_varies: ``bool``
    :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: a list of :class:`Field <hl7apy.core.Field>` instances

    >>> fields = "1|NUCLEAR^NELDA^W|SPO|2222 HOME STREET^^ANN ARBOR^MI^^USA"
    >>> nk1_fields = parse_fields(fields, name_prefix="NK1")
    >>> print(nk1_fields)
    [<Field NK1_1 (SET_ID_NK1) of type SI>, <Field NK1_2 (NAME) of type XPN>, <Field NK1_3 (RELATIONSHIP) of type CE>, \
<Field NK1_4 (ADDRESS) of type XAD>]
    >>> s = Segment("NK1")
    >>> s.children = nk1_fields
    >>> print(s.to_er7())
    NK1|1|NUCLEAR^NELDA^W|SPO|2222 HOME STREET^^ANN ARBOR^MI^^USA
    >>> unknown_fields = parse_fields(fields)
    >>> s.children = unknown_fields
    >>> print(s.to_er7())
    NK1||||||||||||||||||||||||||||||||||||||||1|NUCLEAR^NELDA^W|SPO|2222 HOME STREET^^ANN ARBOR^MI^^USA
    """
    version = _get_version(version)
    encoding_chars = _get_encoding_chars(encoding_chars, version)
    validation_level = _get_validation_level(validation_level)

    text = text.strip("\r")
    field_sep = encoding_chars['FIELD']
    repetition_sep = encoding_chars['REPETITION']
    splitted_fields = text.split(field_sep)
    fields = []
    for index, field in enumerate(splitted_fields):
        name = "{0}_{1}".format(name_prefix, index + 1) if name_prefix is not None else None
        try:
            reference = references[name]['ref'] if references is not None else None
        except KeyError:
            reference = None

        if field.strip() or name is None:
            if name == 'MSH_2':
                fields.append(parse_field(field, name, version, encoding_chars, validation_level,
                                          reference))
            else:
                for rep in field.split(repetition_sep):
                    fields.append(parse_field(rep, name, version, encoding_chars, validation_level,
                                              reference, force_varies))
        elif name == "MSH_1":
            fields.append(parse_field(field_sep, name, version, encoding_chars, validation_level,
                                      reference))
    return fields 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 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, version)
    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_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, version)
    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,
                                   component.structure_by_name)
    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_subcomponents(text, component_datatype='ST', version=None, encoding_chars=None,
                        validation_level=None, references=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>`)

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

    :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

        try:
            reference = references[subcomponent_name]['ref'] \
                if None not in (references, subcomponent_name) else None
        except KeyError:
            reference = None
            subcomponent_name = None
            subcomponent_datatype = 'ST'

        if subcomponent.strip() or subcomponent_name is None:
            subcomponents.append(parse_subcomponent(subcomponent, subcomponent_name, subcomponent_datatype,
                                                    version, validation_level, reference))
    return subcomponents def get_message_type(content):
    fields, enc_chars = _split_msh(content)

    try:
        msh_9 = fields[8].strip()
    except IndexError:
        msh_9 = None

    return msh_9 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 ParserError(HL7apyException):
    """
    Error during parsing

    >>> from hl7apy.parser import parse_message
    >>> m = parse_message('NOTHL7')  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    ParserError: Invalid message
    """ class OperationNotAllowed(HL7apyException):
    """
    Generic exception raised when something is not allowed

    >>> from hl7apy.core import Segment
    >>> s = Segment()  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    OperationNotAllowed: Cannot instantiate an unknown Segment
    """ class InvalidEncodingChars(HL7apyException):
    """
    Raised when the encoding chars specified is not a correct set of HL7 encoding chars

    >>> from hl7apy.core import Message
    >>> encoding_chars = {'GROUP': '\\r', 'SEGMENT': '\\r', 'COMPONENT': '^', \
                          'SUBCOMPONENT': '&', 'REPETITION': '~', 'ESCAPE': '\\\\'}
    >>> m = Message('ADT_A01', encoding_chars=encoding_chars)  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    InvalidEncodingChars: Missing required encoding chars
    """
    def __init__(self, message=None):
        self.message = message

    def __str__(self):
        if self.message:
            return 'Invalid encoding chars: {}'.format(self.message)
        else:
            return 'Invalid encoding chars' class InvalidName(HL7apyException):
    """
    Raised if the reference for the given class/name has not been found

    >>> from hl7apy.core import Message
    >>> Message('Unknown')  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    InvalidName: Invalid name for Message: UNKNOWN
    """
    def __init__(self, cls, name):
        self.cls = cls
        self.name = name

    def __str__(self):
        return 'Invalid name for {0}: {1}'.format(self.cls, self.name) 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
    """ class MaxChildLimitReached(HL7apyException):
    """
    Raised when a child cannot be added to an instance of :class:`Element <hl7apy.core.Element>`
    since the :class:`Element <hl7apy.core.Element>` has already reached the maximum number
    of children allowed for the given child type (e.g. a :class:`Message <hl7apy.core.Message>` should have
    at most 1 MSH segment)

    >>> from hl7apy.core import Message, Segment
    >>> m = Message("OML_O33", validation_level=1)
    >>> m.add(Segment('MSH'))  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    MaxChildLimitReached: Cannot add <Segment MSH>: max limit (1) reached for <Message OML_O33>
    """
    def __init__(self, parent, child, limit):
        self.parent = parent
        self.child = child
        self.limit = limit

    def __str__(self):
        return 'Cannot add {0}: max limit ({1}) reached for {2}'.format(self.child, self.limit, self.parent)
import importlib
import os
import unittest
import hl7apy
from hl7apy.parser import parse_message, parse_segments, parse_segment, parse_fields, parse_field, \
    parse_components, parse_component, parse_subcomponents, get_message_type
from hl7apy.validation import VALIDATION_LEVEL
from hl7apy.exceptions import ParserError, OperationNotAllowed, InvalidEncodingChars, InvalidName, \
    ValidationError, MaxChildLimitReached
        segment = 'OBX|1|ST|1^YYY||ABCDEFG|HJKLMN|||||O'
        s = parse_segment(segment)
        self.assertEqual(s.obx_5.datatype, 'varies')
        self.assertNotEqual(s.obx_6.datatype, 'varies')

    def test_parse_fields(self):
        # note: this method seems not to work without segment definition, and also for MSH segment
        field = 'PID|xxx|yyy^zz'
        fields = parse_fields(field)
        for field in fields:
            self.assertEqual(field.classname, 'Field')
        self.assertEqual(fields[0].to_er7(), 'PID')
        self.assertEqual(fields[1].to_er7(), 'xxx')

    def test_parse_fields_multiple_instances(self):
        field = '||@PID.3.1^aaaa^~@PID.8^F'
        f = parse_fields(field, name_prefix='PID')
        self.assertEqual(len(f), 2)  # only the valued field is recognized
        self.assertEqual(f[0].cx_1.to_er7(), '@PID.3.1')

    def test_parse_null_fields(self):
        fields = 'PID|||'
        f = parse_fields(fields)
        self.assertEqual(len(f), 4)

    def test_parse_fields_invalid_encoding_chars(self):
        fields = 'PID|xxx|yyy^zz'
        self.assertRaises(InvalidEncodingChars, parse_fields, fields, encoding_chars=self._get_invalid_encoding_chars())
        self.assertRaises(InvalidEncodingChars, parse_fields, fields, encoding_chars=self._get_invalid_encoding_chars(),
                          validation_level=VALIDATION_LEVEL.STRICT)

    def test_parse_fields_custom_encoding_chars(self):
        fields = 'xxx@yyy$zz'
        f = parse_fields(fields, encoding_chars=self._get_custom_encoding_chars())
        self.assertEqual(f[0].to_er7(encoding_chars=self._get_custom_encoding_chars()), 'xxx')
        self.assertEqual(f[1].children[0].to_er7(), 'yyy')

    def test_parse_field(self):
        field = 'xxx^yyy^zzz'
        f = parse_field(field)  # exception thrown here

    def test_parse_field_invalid_encoding_chars(self):
        field = 'xxx^yyy^zzz'
        self.assertRaises(InvalidEncodingChars, parse_field, field, encoding_chars=self._get_invalid_encoding_chars())
        self.assertRaises(InvalidEncodingChars, parse_field, field, encoding_chars=self._get_invalid_encoding_chars(),
                          validation_level=VALIDATION_LEVEL.STRICT)

    def test_parse_field_custom_encoding_chars(self):
        field = 'aaa$bbb$ccc'
        f = parse_field(field, encoding_chars=self._get_custom_encoding_chars())
        self.assertEqual(len(f.children), 3)
        self.assertEqual(f.children[0].to_er7(), 'aaa')
        self.assertEqual(f.children[1].to_er7(), 'bbb')
        self.assertEqual(f.children[2].to_er7(), 'ccc')

        field = 'aaa^bbb^ccc'
        to_string_field = 'aaa\\S\\bbb\\S\\ccc'
        f = parse_field(field, encoding_chars=self._get_custom_encoding_chars())
        self.assertEqual(len(f.children), 1)
        self.assertEqual(f.children[0].to_er7(), to_string_field)
        self.assertEqual(f.children[0].to_er7(encoding_chars=self._get_custom_encoding_chars()), field)

    def test_parse_invalid_name_field(self):
        f = parse_field('xxx', name='yyy')
        self.assertIsNone(f.name)

    def test_parse_components(self):
        components = 'aaa^bbb'
        c = parse_components(components)
        self.assertEqual(c[0].to_er7(), 'aaa')
        self.assertEqual(c[1].to_er7(), 'bbb')
        components = '^comp^'
        c = parse_components(components)
        self.assertEqual(len(c), 3)
        self.assertEqual(c[0].to_er7(), '')
        self.assertEqual(c[1].to_er7(), 'comp')
        self.assertEqual(c[2].to_er7(), '')

    def test_parse_components_invalid_encoding_chars(self):
        components = 'aaa^bbb'
        self.assertRaises(InvalidEncodingChars, parse_components, components,
                          encoding_chars=self._get_invalid_encoding_chars())
        self.assertRaises(InvalidEncodingChars, parse_components, components,
                          encoding_chars=self._get_invalid_encoding_chars(),
                          validation_level=VALIDATION_LEVEL.STRICT)

    def test_parse_component(self):
        component = 'comp'
        c = parse_component(component)
        self.assertEqual(c.to_er7(), 'comp')
        component = '^comp^'
        c = parse_component(component)
        self.assertEqual(c.to_er7(), '\\S\\comp\\S\\')
        component = 'comp1&comp2'
        c = parse_component(component)
        self.assertEqual(c.to_er7(), 'comp1&comp2')
        self.assertEqual(c.children[0].to_er7(), 'comp1')
        self.assertEqual(c.children[1].to_er7(), 'comp2')

    def test_parse_component_with_datatype(self):
        c = parse_component('555-55-5555&PRIMARY&PATRICIA&6&&MD', datatype='CE', version='2.5.1')
        self.assertEqual(c.value, '555-55-5555&PRIMARY&PATRICIA&6&&MD')

    def test_parse_component_with_datatype_and_unknown_subcomponent(self):
        c = parse_component('555-55-5555&PRIMARY&PATRICIA P&6&&MD&UNKN', datatype='CE', version='2.5.1')
        self.assertEqual(c.value, '555-55-5555&PRIMARY&PATRICIA P&6&&MD&UNKN')

    def test_parse_component_invalid_encoding_chars(self):
        component = 'comp'
        self.assertRaises(InvalidEncodingChars, parse_component, component,
                          encoding_chars=self._get_invalid_encoding_chars())
        self.assertRaises(InvalidEncodingChars, parse_component, component,
                          encoding_chars=self._get_invalid_encoding_chars(),
                          validation_level=VALIDATION_LEVEL.STRICT)

    def test_parse_invalid_name_component_strict(self):
        self.assertRaises(InvalidName, parse_component, 'xxx', 'XXX', validation_level=VALIDATION_LEVEL.STRICT)

    def test_parse_subcomponents(self):
        subcomponent = 'subcomp'

        s = parse_subcomponents(subcomponent)