"""Parse micromeritics xls output files.

@author Chris Murdock
@modified Paul Iacomi
"""

import logging
import re
from itertools import product

import xlrd

_NUMBER_REGEX = re.compile(r'^(-)?\d+(.|,)?\d+')

_FIELDS = {
    'sample:': {
        'text': ['sample:', 'echantillon:'],
        'name': 'material',
        'row': 0,
        'column': 1,
        'type': 'string'
    },
    'adsorbate': {
        'text': ['analysis ads'],
        'name': 'adsorbate',
        'row': 0,
        'column': 1,
        'type': 'string'
    },
    'temperature': {
        'text': ['analysis bath'],
        'name': 'temperature',
        'row': 0,
        'column': 1,
        'type': 'number'
    },
    'user': {
        'text': ['operator', 'analyste'],
        'name': 'user',
        'row': 0,
        'column': 1,
        'type': 'string'
    },
    'date': {
        'text': ['started'],
        'name': 'date',
        'row': 0,
        'column': 1,
        'type': 'string'
    },
    'sample mass': {
        'text': ['sample mass'],
        'name': 'mass',
        'row': 0,
        'column': 1,
        'type': 'number'
    },
    'comment': {
        'text': ['comments'],
        'name': 'comment',
        'row': 0,
        'column': 0,
        'type': 'string'
    },
    'isotherm tabular': {
        'text': ['isotherm tabular'],
        'type': 'isotherm report',
        'labels': {
            'Relative': 'relative',
            'Absolute': 'absolute',
            'Quantity': 'loading',
            'Elapsed': 'time',
            'Saturation': 'saturation'
        }
    },
    'primary data': {
        'text': ['primary data'],
        'type': 'error',
        'row': 1,
        'column': 0,
        'name': 'errors'
    },
    'cell_value': {
        'text': [],
        'header': {
            'row': 2
        },
        'datapoints': {
            'row': 3
        }
    }
}


def read_mic_report(path):
    """
    Parse an xls file generated by micromeritics software.

    Parameters
    ----------
    path: str
        the location of an xls file generated by a micromeritics instrument.

    Returns
    -------
    dict
        A dictionary containing report information.

    """
    workbook = xlrd.open_workbook(path, encoding_override='latin-1')
    sheet = workbook.sheet_by_index(0)
    data = {}
    errors = []
    for row, col in product(range(sheet.nrows), range(sheet.ncols)):
        cell_value = str(sheet.cell(row, col).value).lower()
        try:
            field = next(v for k, v in _FIELDS.items() if
                         any([cell_value.startswith(n) for
                              n in v.get('text')]))
        except StopIteration:
            continue
        if field['type'] == 'number':
            val = sheet.cell(row + field['row'], col + field['column']).value
            data[field['name']] = _handle_numbers(field, val)
        elif field['type'] == 'string':
            val = sheet.cell(row + field['row'], col + field['column']).value
            data[field['name']] = _handle_string(val)
        elif field['type'] == 'isotherm report':
            data['pressure'] = {}
            for i, item in enumerate(_get_data_labels(sheet, row, col)):
                points = _get_datapoints(sheet, row, col + i)
                _assign_data(item, field, data, points)
        elif field['type'] == 'error':
            errors += _get_errors(sheet, row, col)
    if errors:
        data['errors'] = errors
    _check(data, path)
    return data


def _handle_numbers(field, val):
    """
    Removes any extra information (such as units) to return only the number as a float.

    Input is a cell of type 'number'.
    """
    if val:
        ret = float(_NUMBER_REGEX.search(val.replace(',', '')).group())
        if field['name'] == 'temperature':
            if '°C' in val:
                ret = ret + 273.15
        return ret
    else:
        return None


def _handle_string(val):
    """
    Replaces Comments: and any newline found.

    Input is a cell of type 'string'.
    """
    return val.replace('Comments: ', '').replace('\r\n', ' ')


def _convert_time(points):
    """Convert time points from HH:MM format to minutes."""
    minutes = []
    for point in points:
        hours, mins = str(point).split(':')
        minutes.append(int(hours) * 60 + int(mins))
    return minutes


def _get_data_labels(sheet, row, col):
    """Locate all column labels for data collected during the experiment."""
    final_column = col
    header_row = _FIELDS['cell_value']['header']['row']
    # Abstract this sort of thing
    header = sheet.cell(row + header_row, final_column).value
    while any(header.startswith(label) for label
              in _FIELDS['isotherm tabular']['labels']):
        final_column += 1
        header = sheet.cell(row + header_row, final_column).value
    return [sheet.cell(row + header_row, i).value for i in
            range(col, final_column)]


def _get_datapoints(sheet, row, col):
    """Return all collected data points for a given column."""
    rowc = _FIELDS['cell_value']['datapoints']['row']
    # Data can start on two different rows. Try first option and then next row.
    if sheet.cell(row + rowc, col).value:
        start_row = row + rowc
        final_row = row + rowc
    else:
        start_row = row + (rowc + 1)
        final_row = row + (rowc + 1)
    point = sheet.cell(final_row, col).value
    while point:
        final_row += 1
        point = sheet.cell(final_row, col).value
    return [sheet.cell(i, col).value for i in range(start_row, final_row)]


def _assign_data(item, field, data, points):
    """
    Add data to the data dictionary.

    For each column of data collected
    in a form depending on the label of the column.
    """
    name = next(f for f in field['labels'] if item.startswith(f))
    if field['labels'][name] == 'time':
        data['time'] = _convert_time(points)
    elif field['labels'][name] == 'loading':
        data['loading'] = points
        for (u, c) in (('(mmol/', 'mmol'), ('(mol/', 'mol'), ('(cm³/', 'cm3(STP)')):
            if u in item:
                data['loading_unit'] = c
        for (u, c) in (('/g', 'g'), ('/kg', 'kg')):
            if u in item:
                data['adsorbent_unit'] = c
    elif field['labels'][name] in ['relative', 'absolute', 'saturation']:
        data['pressure'][field['labels'][name]] = points
    else:
        raise ValueError("Label name '{}' not recognized."
                         .format(field['labels'][name]))


def _get_errors(sheet, row, col):
    """
    Look for all cells that contain errors.

    (are below a cell labelled primary data).
    """
    field = _FIELDS['primary data']
    val = sheet.cell(row + field['row'], col + field['column']).value
    if not val:
        return []
    final_row = row + field['row']
    error = sheet.cell(final_row, col + field['column']).value
    while error:
        final_row += 1
        error = sheet.cell(final_row, col + field['column']).value
    return [sheet.cell(i, col + field['column']).value
            for i in range(row + field['row'], final_row)]


def _check(data, path):
    """
    Check keys in data and logs a warning if a key is empty.

    Also logs a warning for errors found in file.
    """
    if 'loading' in data:
        empties = (k for k, v in data.items() if not v)
        for empty in empties:
            logging.info('No data collected for %s in file %s.', empty, path)
    if 'errors' in data:
        logging.warning('\n'.join(data['errors']))

1decd203005e8435d80bec291b168964a3cad35b
<mask token>


def _handle_numbers(field, val):
    """
    Removes any extra information (such as units) to return only the number as a float.

    Input is a cell of type 'number'.
    """
    if val:
        ret = float(_NUMBER_REGEX.search(val.replace(',', '')).group())
        if field['name'] == 'temperature':
            if '°C' in val:
                ret = ret + 273.15
        return ret
    else:
        return None


<mask token>


def _convert_time(points):
    """Convert time points from HH:MM format to minutes."""
    minutes = []
    for point in points:
        hours, mins = str(point).split(':')
        minutes.append(int(hours) * 60 + int(mins))
    return minutes


<mask token>


def _get_errors(sheet, row, col):
    """
    Look for all cells that contain errors.

    (are below a cell labelled primary data).
    """
    field = _FIELDS['primary data']
    val = sheet.cell(row + field['row'], col + field['column']).value
    if not val:
        return []
    final_row = row + field['row']
    error = sheet.cell(final_row, col + field['column']).value
    while error:
        final_row += 1
        error = sheet.cell(final_row, col + field['column']).value
    return [sheet.cell(i, col + field['column']).value for i in range(row +
        field['row'], final_row)]


def _check(data, path):
    """
    Check keys in data and logs a warning if a key is empty.

    Also logs a warning for errors found in file.
    """
    if 'loading' in data:
        empties = (k for k, v in data.items() if not v)
        for empty in empties:
            logging.info('No data collected for %s in file %s.', empty, path)
    if 'errors' in data:
        logging.warning('\n'.join(data['errors']))

<mask token>


def _handle_numbers(field, val):
    """
    Removes any extra information (such as units) to return only the number as a float.

    Input is a cell of type 'number'.
    """
    if val:
        ret = float(_NUMBER_REGEX.search(val.replace(',', '')).group())
        if field['name'] == 'temperature':
            if '°C' in val:
                ret = ret + 273.15
        return ret
    else:
        return None


def _handle_string(val):
    """
    Replaces Comments: and any newline found.

    Input is a cell of type 'string'.
    """
    return val.replace('Comments: ', '').replace('\r\n', ' ')


def _convert_time(points):
    """Convert time points from HH:MM format to minutes."""
    minutes = []
    for point in points:
        hours, mins = str(point).split(':')
        minutes.append(int(hours) * 60 + int(mins))
    return minutes


def _get_data_labels(sheet, row, col):
    """Locate all column labels for data collected during the experiment."""
    final_column = col
    header_row = _FIELDS['cell_value']['header']['row']
    header = sheet.cell(row + header_row, final_column).value
    while any(header.startswith(label) for label in _FIELDS[
        'isotherm tabular']['labels']):
        final_column += 1
        header = sheet.cell(row + header_row, final_column).value
    return [sheet.cell(row + header_row, i).value for i in range(col,
        final_column)]


def _get_datapoints(sheet, row, col):
    """Return all collected data points for a given column."""
    rowc = _FIELDS['cell_value']['datapoints']['row']
    if sheet.cell(row + rowc, col).value:
        start_row = row + rowc
        final_row = row + rowc
    else:
        start_row = row + (rowc + 1)
        final_row = row + (rowc + 1)
    point = sheet.cell(final_row, col).value
    while point:
        final_row += 1
        point = sheet.cell(final_row, col).value
    return [sheet.cell(i, col).value for i in range(start_row, final_row)]


<mask token>


def _get_errors(sheet, row, col):
    """
    Look for all cells that contain errors.

    (are below a cell labelled primary data).
    """
    field = _FIELDS['primary data']
    val = sheet.cell(row + field['row'], col + field['column']).value
    if not val:
        return []
    final_row = row + field['row']
    error = sheet.cell(final_row, col + field['column']).value
    while error:
        final_row += 1
        error = sheet.cell(final_row, col + field['column']).value
    return [sheet.cell(i, col + field['column']).value for i in range(row +
        field['row'], final_row)]


def _check(data, path):
    """
    Check keys in data and logs a warning if a key is empty.

    Also logs a warning for errors found in file.
    """
    if 'loading' in data:
        empties = (k for k, v in data.items() if not v)
        for empty in empties:
            logging.info('No data collected for %s in file %s.', empty, path)
    if 'errors' in data:
        logging.warning('\n'.join(data['errors']))

<mask token>


def read_mic_report(path):
    """
    Parse an xls file generated by micromeritics software.

    Parameters
    ----------
    path: str
        the location of an xls file generated by a micromeritics instrument.

    Returns
    -------
    dict
        A dictionary containing report information.

    """
    workbook = xlrd.open_workbook(path, encoding_override='latin-1')
    sheet = workbook.sheet_by_index(0)
    data = {}
    errors = []
    for row, col in product(range(sheet.nrows), range(sheet.ncols)):
        cell_value = str(sheet.cell(row, col).value).lower()
        try:
            field = next(v for k, v in _FIELDS.items() if any([cell_value.
                startswith(n) for n in v.get('text')]))
        except StopIteration:
            continue
        if field['type'] == 'number':
            val = sheet.cell(row + field['row'], col + field['column']).value
            data[field['name']] = _handle_numbers(field, val)
        elif field['type'] == 'string':
            val = sheet.cell(row + field['row'], col + field['column']).value
            data[field['name']] = _handle_string(val)
        elif field['type'] == 'isotherm report':
            data['pressure'] = {}
            for i, item in enumerate(_get_data_labels(sheet, row, col)):
                points = _get_datapoints(sheet, row, col + i)
                _assign_data(item, field, data, points)
        elif field['type'] == 'error':
            errors += _get_errors(sheet, row, col)
    if errors:
        data['errors'] = errors
    _check(data, path)
    return data


def _handle_numbers(field, val):
    """
    Removes any extra information (such as units) to return only the number as a float.

    Input is a cell of type 'number'.
    """
    if val:
        ret = float(_NUMBER_REGEX.search(val.replace(',', '')).group())
        if field['name'] == 'temperature':
            if '°C' in val:
                ret = ret + 273.15
        return ret
    else:
        return None


def _handle_string(val):
    """
    Replaces Comments: and any newline found.

    Input is a cell of type 'string'.
    """
    return val.replace('Comments: ', '').replace('\r\n', ' ')


def _convert_time(points):
    """Convert time points from HH:MM format to minutes."""
    minutes = []
    for point in points:
        hours, mins = str(point).split(':')
        minutes.append(int(hours) * 60 + int(mins))
    return minutes


def _get_data_labels(sheet, row, col):
    """Locate all column labels for data collected during the experiment."""
    final_column = col
    header_row = _FIELDS['cell_value']['header']['row']
    header = sheet.cell(row + header_row, final_column).value
    while any(header.startswith(label) for label in _FIELDS[
        'isotherm tabular']['labels']):
        final_column += 1
        header = sheet.cell(row + header_row, final_column).value
    return [sheet.cell(row + header_row, i).value for i in range(col,
        final_column)]


def _get_datapoints(sheet, row, col):
    """Return all collected data points for a given column."""
    rowc = _FIELDS['cell_value']['datapoints']['row']
    if sheet.cell(row + rowc, col).value:
        start_row = row + rowc
        final_row = row + rowc
    else:
        start_row = row + (rowc + 1)
        final_row = row + (rowc + 1)
    point = sheet.cell(final_row, col).value
    while point:
        final_row += 1
        point = sheet.cell(final_row, col).value
    return [sheet.cell(i, col).value for i in range(start_row, final_row)]


<mask token>


def _get_errors(sheet, row, col):
    """
    Look for all cells that contain errors.

    (are below a cell labelled primary data).
    """
    field = _FIELDS['primary data']
    val = sheet.cell(row + field['row'], col + field['column']).value
    if not val:
        return []
    final_row = row + field['row']
    error = sheet.cell(final_row, col + field['column']).value
    while error:
        final_row += 1
        error = sheet.cell(final_row, col + field['column']).value
    return [sheet.cell(i, col + field['column']).value for i in range(row +
        field['row'], final_row)]


def _check(data, path):
    """
    Check keys in data and logs a warning if a key is empty.

    Also logs a warning for errors found in file.
    """
    if 'loading' in data:
        empties = (k for k, v in data.items() if not v)
        for empty in empties:
            logging.info('No data collected for %s in file %s.', empty, path)
    if 'errors' in data:
        logging.warning('\n'.join(data['errors']))

<mask token>


def read_mic_report(path):
    """
    Parse an xls file generated by micromeritics software.

    Parameters
    ----------
    path: str
        the location of an xls file generated by a micromeritics instrument.

    Returns
    -------
    dict
        A dictionary containing report information.

    """
    workbook = xlrd.open_workbook(path, encoding_override='latin-1')
    sheet = workbook.sheet_by_index(0)
    data = {}
    errors = []
    for row, col in product(range(sheet.nrows), range(sheet.ncols)):
        cell_value = str(sheet.cell(row, col).value).lower()
        try:
            field = next(v for k, v in _FIELDS.items() if any([cell_value.
                startswith(n) for n in v.get('text')]))
        except StopIteration:
            continue
        if field['type'] == 'number':
            val = sheet.cell(row + field['row'], col + field['column']).value
            data[field['name']] = _handle_numbers(field, val)
        elif field['type'] == 'string':
            val = sheet.cell(row + field['row'], col + field['column']).value
            data[field['name']] = _handle_string(val)
        elif field['type'] == 'isotherm report':
            data['pressure'] = {}
            for i, item in enumerate(_get_data_labels(sheet, row, col)):
                points = _get_datapoints(sheet, row, col + i)
                _assign_data(item, field, data, points)
        elif field['type'] == 'error':
            errors += _get_errors(sheet, row, col)
    if errors:
        data['errors'] = errors
    _check(data, path)
    return data


def _handle_numbers(field, val):
    """
    Removes any extra information (such as units) to return only the number as a float.

    Input is a cell of type 'number'.
    """
    if val:
        ret = float(_NUMBER_REGEX.search(val.replace(',', '')).group())
        if field['name'] == 'temperature':
            if '°C' in val:
                ret = ret + 273.15
        return ret
    else:
        return None


def _handle_string(val):
    """
    Replaces Comments: and any newline found.

    Input is a cell of type 'string'.
    """
    return val.replace('Comments: ', '').replace('\r\n', ' ')


def _convert_time(points):
    """Convert time points from HH:MM format to minutes."""
    minutes = []
    for point in points:
        hours, mins = str(point).split(':')
        minutes.append(int(hours) * 60 + int(mins))
    return minutes


def _get_data_labels(sheet, row, col):
    """Locate all column labels for data collected during the experiment."""
    final_column = col
    header_row = _FIELDS['cell_value']['header']['row']
    header = sheet.cell(row + header_row, final_column).value
    while any(header.startswith(label) for label in _FIELDS[
        'isotherm tabular']['labels']):
        final_column += 1
        header = sheet.cell(row + header_row, final_column).value
    return [sheet.cell(row + header_row, i).value for i in range(col,
        final_column)]


def _get_datapoints(sheet, row, col):
    """Return all collected data points for a given column."""
    rowc = _FIELDS['cell_value']['datapoints']['row']
    if sheet.cell(row + rowc, col).value:
        start_row = row + rowc
        final_row = row + rowc
    else:
        start_row = row + (rowc + 1)
        final_row = row + (rowc + 1)
    point = sheet.cell(final_row, col).value
    while point:
        final_row += 1
        point = sheet.cell(final_row, col).value
    return [sheet.cell(i, col).value for i in range(start_row, final_row)]


def _assign_data(item, field, data, points):
    """
    Add data to the data dictionary.

    For each column of data collected
    in a form depending on the label of the column.
    """
    name = next(f for f in field['labels'] if item.startswith(f))
    if field['labels'][name] == 'time':
        data['time'] = _convert_time(points)
    elif field['labels'][name] == 'loading':
        data['loading'] = points
        for u, c in (('(mmol/', 'mmol'), ('(mol/', 'mol'), ('(cm³/',
            'cm3(STP)')):
            if u in item:
                data['loading_unit'] = c
        for u, c in (('/g', 'g'), ('/kg', 'kg')):
            if u in item:
                data['adsorbent_unit'] = c
    elif field['labels'][name] in ['relative', 'absolute', 'saturation']:
        data['pressure'][field['labels'][name]] = points
    else:
        raise ValueError("Label name '{}' not recognized.".format(field[
            'labels'][name]))


def _get_errors(sheet, row, col):
    """
    Look for all cells that contain errors.

    (are below a cell labelled primary data).
    """
    field = _FIELDS['primary data']
    val = sheet.cell(row + field['row'], col + field['column']).value
    if not val:
        return []
    final_row = row + field['row']
    error = sheet.cell(final_row, col + field['column']).value
    while error:
        final_row += 1
        error = sheet.cell(final_row, col + field['column']).value
    return [sheet.cell(i, col + field['column']).value for i in range(row +
        field['row'], final_row)]


def _check(data, path):
    """
    Check keys in data and logs a warning if a key is empty.

    Also logs a warning for errors found in file.
    """
    if 'loading' in data:
        empties = (k for k, v in data.items() if not v)
        for empty in empties:
            logging.info('No data collected for %s in file %s.', empty, path)
    if 'errors' in data:
        logging.warning('\n'.join(data['errors']))

"""Parse micromeritics xls output files.

@author Chris Murdock
@modified Paul Iacomi
"""

import logging
import re
from itertools import product

import xlrd

_NUMBER_REGEX = re.compile(r'^(-)?\d+(.|,)?\d+')

_FIELDS = {
    'sample:': {
        'text': ['sample:', 'echantillon:'],
        'name': 'material',
        'row': 0,
        'column': 1,
        'type': 'string'
    },
    'adsorbate': {
        'text': ['analysis ads'],
        'name': 'adsorbate',
        'row': 0,
        'column': 1,
        'type': 'string'
    },
    'temperature': {
        'text': ['analysis bath'],
        'name': 'temperature',
        'row': 0,
        'column': 1,
        'type': 'number'
    },
    'user': {
        'text': ['operator', 'analyste'],
        'name': 'user',
        'row': 0,
        'column': 1,
        'type': 'string'
    },
    'date': {
        'text': ['started'],
        'name': 'date',
        'row': 0,
        'column': 1,
        'type': 'string'
    },
    'sample mass': {
        'text': ['sample mass'],
        'name': 'mass',
        'row': 0,
        'column': 1,
        'type': 'number'
    },
    'comment': {
        'text': ['comments'],
        'name': 'comment',
        'row': 0,
        'column': 0,
        'type': 'string'
    },
    'isotherm tabular': {
        'text': ['isotherm tabular'],
        'type': 'isotherm report',
        'labels': {
            'Relative': 'relative',
            'Absolute': 'absolute',
            'Quantity': 'loading',
            'Elapsed': 'time',
            'Saturation': 'saturation'
        }
    },
    'primary data': {
        'text': ['primary data'],
        'type': 'error',
        'row': 1,
        'column': 0,
        'name': 'errors'
    },
    'cell_value': {
        'text': [],
        'header': {
            'row': 2
        },
        'datapoints': {
            'row': 3
        }
    }
}


def read_mic_report(path):
    """
    Parse an xls file generated by micromeritics software.

    Parameters
    ----------
    path: str
        the location of an xls file generated by a micromeritics instrument.

    Returns
    -------
    dict
        A dictionary containing report information.

    """
    workbook = xlrd.open_workbook(path, encoding_override='latin-1')
    sheet = workbook.sheet_by_index(0)
    data = {}
    errors = []
    for row, col in product(range(sheet.nrows), range(sheet.ncols)):
        cell_value = str(sheet.cell(row, col).value).lower()
        try:
            field = next(v for k, v in _FIELDS.items() if
                         any([cell_value.startswith(n) for
                              n in v.get('text')]))
        except StopIteration:
            continue
        if field['type'] == 'number':
            val = sheet.cell(row + field['row'], col + field['column']).value
            data[field['name']] = _handle_numbers(field, val)
        elif field['type'] == 'string':
            val = sheet.cell(row + field['row'], col + field['column']).value
            data[field['name']] = _handle_string(val)
        elif field['type'] == 'isotherm report':
            data['pressure'] = {}
            for i, item in enumerate(_get_data_labels(sheet, row, col)):
                points = _get_datapoints(sheet, row, col + i)
                _assign_data(item, field, data, points)
        elif field['type'] == 'error':
            errors += _get_errors(sheet, row, col)
    if errors:
        data['errors'] = errors
    _check(data, path)
    return data


def _handle_numbers(field, val):
    """
    Removes any extra information (such as units) to return only the number as a float.

    Input is a cell of type 'number'.
    """
    if val:
        ret = float(_NUMBER_REGEX.search(val.replace(',', '')).group())
        if field['name'] == 'temperature':
            if '°C' in val:
                ret = ret + 273.15
        return ret
    else:
        return None


def _handle_string(val):
    """
    Replaces Comments: and any newline found.

    Input is a cell of type 'string'.
    """
    return val.replace('Comments: ', '').replace('\r\n', ' ')


def _convert_time(points):
    """Convert time points from HH:MM format to minutes."""
    minutes = []
    for point in points:
        hours, mins = str(point).split(':')
        minutes.append(int(hours) * 60 + int(mins))
    return minutes


def _get_data_labels(sheet, row, col):
    """Locate all column labels for data collected during the experiment."""
    final_column = col
    header_row = _FIELDS['cell_value']['header']['row']
    # Abstract this sort of thing
    header = sheet.cell(row + header_row, final_column).value
    while any(header.startswith(label) for label
              in _FIELDS['isotherm tabular']['labels']):
        final_column += 1
        header = sheet.cell(row + header_row, final_column).value
    return [sheet.cell(row + header_row, i).value for i in
            range(col, final_column)]


def _get_datapoints(sheet, row, col):
    """Return all collected data points for a given column."""
    rowc = _FIELDS['cell_value']['datapoints']['row']
    # Data can start on two different rows. Try first option and then next row.
    if sheet.cell(row + rowc, col).value:
        start_row = row + rowc
        final_row = row + rowc
    else:
        start_row = row + (rowc + 1)
        final_row = row + (rowc + 1)
    point = sheet.cell(final_row, col).value
    while point:
        final_row += 1
        point = sheet.cell(final_row, col).value
    return [sheet.cell(i, col).value for i in range(start_row, final_row)]


def _assign_data(item, field, data, points):
    """
    Add data to the data dictionary.

    For each column of data collected
    in a form depending on the label of the column.
    """
    name = next(f for f in field['labels'] if item.startswith(f))
    if field['labels'][name] == 'time':
        data['time'] = _convert_time(points)
    elif field['labels'][name] == 'loading':
        data['loading'] = points
        for (u, c) in (('(mmol/', 'mmol'), ('(mol/', 'mol'), ('(cm³/', 'cm3(STP)')):
            if u in item:
                data['loading_unit'] = c
        for (u, c) in (('/g', 'g'), ('/kg', 'kg')):
            if u in item:
                data['adsorbent_unit'] = c
    elif field['labels'][name] in ['relative', 'absolute', 'saturation']:
        data['pressure'][field['labels'][name]] = points
    else:
        raise ValueError("Label name '{}' not recognized."
                         .format(field['labels'][name]))


def _get_errors(sheet, row, col):
    """
    Look for all cells that contain errors.

    (are below a cell labelled primary data).
    """
    field = _FIELDS['primary data']
    val = sheet.cell(row + field['row'], col + field['column']).value
    if not val:
        return []
    final_row = row + field['row']
    error = sheet.cell(final_row, col + field['column']).value
    while error:
        final_row += 1
        error = sheet.cell(final_row, col + field['column']).value
    return [sheet.cell(i, col + field['column']).value
            for i in range(row + field['row'], final_row)]


def _check(data, path):
    """
    Check keys in data and logs a warning if a key is empty.

    Also logs a warning for errors found in file.
    """
    if 'loading' in data:
        empties = (k for k, v in data.items() if not v)
        for empty in empties:
            logging.info('No data collected for %s in file %s.', empty, path)
    if 'errors' in data:
        logging.warning('\n'.join(data['errors']))
