def get_datapoints(root):
    """Parse datapoints with ignition delay from file.

    Args:
        root (`~xml.etree.ElementTree.Element`): Root of ReSpecTh XML file

    Returns:
        properties (`dict`): Dictionary with ignition delay data
    """
    # Shock tube experiment will have one data group, while RCM may have one
    # or two (one for ignition delay, one for volume-history)
    dataGroups = root.findall('dataGroup')
    if not dataGroups:
        raise MissingElementError('dataGroup')

    # all situations will have main experimental data in first dataGroup
    dataGroup = dataGroups[0]
    property_id = {}
    unit_id = {}
    species_id = {}
    # get properties of dataGroup
    for prop in dataGroup.findall('property'):
        unit_id[prop.attrib['id']] = prop.attrib['units']
        temp_prop = prop.attrib['name']
        if temp_prop not in datagroup_properties + ['composition']:
            raise KeyError(temp_prop + ' not valid dataPoint property')
        property_id[prop.attrib['id']] = temp_prop

        if temp_prop == 'composition':
            spec = {'species-name': prop.find('speciesLink').attrib['preferredKey']}
            # use InChI for unique species identifier (if present)
            try:
                spec['InChI'] = prop.find('speciesLink').attrib['InChI']
            except KeyError:
                # TODO: add InChI validator/search
                warn('Missing InChI for species ' + spec['species-name'])
                pass
            species_id[prop.attrib['id']] = spec

    if not property_id:
        raise MissingElementError('property')

    # now get data points
    datapoints = []
    for dp in dataGroup.findall('dataPoint'):
        datapoint = {}
        if 'composition' in property_id.values():
            datapoint['composition'] = {'species': [], 'kind': None}

        for val in dp:
            # handle "regular" properties differently than composition
            if property_id.get(val.tag) in datagroup_properties:
                units = unit_id[val.tag]
                if units == 'Torr':
                    units = 'torr'
                datapoint[property_id[val.tag].replace(' ', '-')] = [val.text + ' ' + units]
            elif property_id.get(val.tag) == 'composition':
                spec = {}
                spec['species-name'] = species_id[val.tag]['species-name']
                spec['InChI'] = species_id[val.tag].get('InChI')

                units = unit_id[val.tag]
                # If mole or mass fraction, just set value
                if units in ['mole fraction', 'mass fraction', 'mole percent']:
                    spec['amount'] = [float(val.text)]
                elif units == 'percent':
                    # assume this means mole percent
                    warn('Assuming percent in composition means mole percent')
                    spec['amount'] = [float(val.text)]
                    units = 'mole percent'
                elif units == 'ppm':
                    # assume molar ppm, convert to mole fraction
                    warn('Assuming molar ppm in composition and converting to mole fraction')
                    spec['amount'] = [float(val.text) * 1.e-6]
                    units = 'mole fraction'
                elif units == 'ppb':
                    # assume molar ppb, convert to mole fraction
                    warn('Assuming molar ppb in composition and converting to mole fraction')
                    spec['amount'] = [float(val.text) * 1.e-9]
                    units = 'mole fraction'
                else:
                    raise KeywordError('composition units need to be one of: mole fraction, '
                                       'mass fraction, mole percent, percent, ppm, or ppb.'
                                       )

                # check consistency of composition type
                if datapoint['composition']['kind'] is None:
                    datapoint['composition']['kind'] = units
                elif datapoint['composition']['kind'] != units:
                    raise KeywordError(
                        'composition units ' + units +
                        ' not consistent with ' + datapoint['composition']['kind']
                        )

                datapoint['composition']['species'].append(spec)
            else:
                raise KeywordError('value missing from properties: ' + val.tag)

        datapoints.append(datapoint)

    if len(datapoints) == 0:
        raise MissingElementError('dataPoint')

    # ReSpecTh files can have other dataGroups with pressure, volume, or temperature histories
    if len(dataGroups) > 1:
        datapoints[0]['time-histories'] = []
        for dataGroup in dataGroups[1:]:
            time_tag = None
            quant_tags = []
            quant_dicts = []
            quant_types = []
            for prop in dataGroup.findall('property'):
                if prop.attrib['name'] == 'time':
                    time_dict = {'units': prop.attrib['units'], 'column': 0}
                    time_tag = prop.attrib['id']
                elif prop.attrib['name'] in ['volume', 'temperature', 'pressure']:
                    quant_types.append(prop.attrib['name'])
                    quant_dicts.append({'units': prop.attrib['units'], 'column': 1})
                    quant_tags.append(prop.attrib['id'])
                else:
                    raise KeywordError('Only volume, temperature, pressure, and time are allowed '
                                       'in a time-history dataGroup.')

            if time_tag is None or len(quant_tags) == 0:
                raise KeywordError('Both time and quantity properties required for time-history.')

            time_histories = [
                {'time': time_dict, 'quantity': q, 'type': t, 'values': []}
                for (q, t) in zip(quant_dicts, quant_types)
            ]
            # collect volume-time history
            for dp in dataGroup.findall('dataPoint'):
                time = None
                quants = {}
                for val in dp:
                    if val.tag == time_tag:
                        time = float(val.text)
                    elif val.tag in quant_tags:
                        quant = float(val.text)
                        tag_idx = quant_tags.index(val.tag)
                        quant_type = quant_types[tag_idx]
                        quants[quant_type] = quant
                    else:
                        raise KeywordError('Value tag {} not found in dataGroup tags: '
                                           '{}'.format(val.tag, quant_tags))
                if time is None or len(quants) == 0:
                    raise KeywordError('Both time and quantity values required in each '
                                       'time-history dataPoint.')
                for t in time_histories:
                    t['values'].append([time, quants[t['type']]])

            datapoints[0]['time-histories'].extend(time_histories)

    return datapoints