Parse mmcif file into a dictionary.

    Notes
    -----
    Full list of keys/value types, and further information on them can be viewed here:
            http://mmcif.wwpdb.org/docs/pdb_to_pdbx_correspondences.html
    All values in the returned dict are str or list(str).
    This means that some of the data values are string representations of integers
    - parse these outside of this function if desired.
    An alternative approach to this can be found in Biopython (via the function Bio.PDB.MMCIF2Dict.MMCIF2Dict).
    mmcif files are subject to the usual "here be dragons" problems of the PDB and difficult file formats.
    As such, this function is likely to be in a permanent state of flux as more dragons are found.

    Parameters
    ----------
    mmcif : str
        mmcif string or a path to an mmcif file.
    path : bool
        True if mmcif is a path.

    Returns
    -------
    cif_data : dict
        Keys are cif data names, e.g. '_struct_keywords.text'.
        Values are str or list(str).

def dict_from_mmcif(mmcif, path=True):
    """Parse mmcif file into a dictionary.

    Notes
    -----
    Full list of keys/value types, and further information on them can be viewed here:
            http://mmcif.wwpdb.org/docs/pdb_to_pdbx_correspondences.html
    All values in the returned dict are str or list(str).
    This means that some of the data values are string representations of integers
    - parse these outside of this function if desired.
    An alternative approach to this can be found in Biopython (via the function Bio.PDB.MMCIF2Dict.MMCIF2Dict).
    mmcif files are subject to the usual "here be dragons" problems of the PDB and difficult file formats.
    As such, this function is likely to be in a permanent state of flux as more dragons are found.

    Parameters
    ----------
    mmcif : str
        mmcif string or a path to an mmcif file.
    path : bool
        True if mmcif is a path.

    Returns
    -------
    cif_data : dict
        Keys are cif data names, e.g. '_struct_keywords.text'.
        Values are str or list(str).

    """
    if path:
        with open(mmcif, 'r') as foo:
            lines = foo.readlines()
    else:
        lines = mmcif.splitlines()
    lines = [' '.join(x.strip().split()) for x in lines]

    # Some of the data in a .cif files are stored between 'loop_' to initiate a loop, and '#' to terminate it.
    # The variable 'loop' is a flag to keep track of this behaviour.
    loop = False

    # Set up the dictionary to populate as the lines of the .cif file are iterated over.
    cif_data = {}

    for i, line in enumerate(lines):

        if not line:
            continue

        # hash signifies end of a loop. Ensure loop flag is set to False.
        if line == '#':
            loop = False
            continue

        if not loop:
            # This line initiates a loop section, in which keys are listed first,
            # followed by lines of data in which the values are listed in the same order as the above keys.
            # The values in the loop section will be stored as lists - there are multiple values for one key.
            # An example of this type of data is the 'REVDAT' section, which stores details on the (potentially
            # numerous) various revisions made to the PDB file during its history.

            if line[:5] == 'loop_':
                loop = True
                key_list = []
                continue

            # Lines beginning with '_' start with data names, i.e. keys in the cif_data dictionary.
            elif line[0] == '_':
                # If line consists only of a key, then subsequent lines may contain the associated value.
                if len(line.split()) == 1:
                    current_key = line
                    count = 1
                    while True:
                        # Look forward until a key is found, keeping count of the number of lines in between.
                        try:
                            if lines[i + count][0] != '_':
                                count += 1

                            # prevent infinite loop.
                            elif i + count > len(lines):
                                break

                            else:
                                if count > 1:
                                    try:
                                        cif_data[current_key] = ' '.join(lines[i + 1: i + count])
                                    except IndexError:
                                        cif_data[current_key] = None
                                else:
                                    cif_data[current_key] = None
                                break
                        except IndexError:
                            break
                    continue

                # Simplest case. Line is a key-value pair, with the key identified by its first character, '_'.
                elif len(line.split()) > 1:
                    line = line.split()
                    try:
                        cif_data[line[0]] = ' '.join(line[1:])
                    except IndexError:
                        cif_data[line[0]] = None
                    continue

            # Line is one of multiple lines that are combined into a value in the while True: loop above.
            else:
                continue

        else:
            # Within a loop section, keys are identified by their first character '_'.
            # Add them to the list of keys in the loop.
            if line[0] == '_':
                if len(line.split()) == 1:
                    key_list.append(line)
                    if line not in cif_data.keys():
                        cif_data[line] = []

            # Within a loop section, the values are listed within a single space-separated line in the same order
            # that the keys were listed at the start of the loop.
            else:
                # Cannot do a simple split if any of the values themselves are strings containing at least one space.
                if '\"' in line and line.count('\"') % 2 == 0:
                    line_parts = [x.strip() for x in line.split('\"') if x]
                    line = []
                    for part in line_parts:
                        if line_parts.index(part) % 2 == 0:
                            for x in part.split():
                                line.append(x)
                        else:
                            line.append(part)

                elif '\'' in line and line.count('\'') % 2 == 0:
                    line = [x.strip() for x in line.split('\'') if x]

                elif len(key_list) == len(line.split()):
                    line = line.split()

                if len(key_list) == len(line):
                    for j, v in enumerate(line):
                        cif_data[key_list[j]].append(line[j])
                else:
                    # CURRENTLY THERE IS A PROBLEM WITH REALLY LONG LOOPS eg _pdbx_refine_tls*, _pdbx_struct_oper_list*
                    # The values span multiple lines, and therefore do not satisfy
                    # the condition of the above 'if' statement.
                    # A correction for this needs to keep track of the value count on subsequent lines,
                    # until the 'if' condition is met.
                    continue

    return cif_data