def first(el):
    if len(el) > 0:
        return el[0]
    else:
        return None class Compound(BaseModel):
    names = ListType(StringType())
    labels = ListType(StringType())
    roles = ListType(StringType())
    nmr_spectra = ListType(ModelType(NmrSpectrum))
    ir_spectra = ListType(ModelType(IrSpectrum))
    uvvis_spectra = ListType(ModelType(UvvisSpectrum))
    melting_points = ListType(ModelType(MeltingPoint))
    glass_transitions = ListType(ModelType(GlassTransition))
    quantum_yields = ListType(ModelType(QuantumYield))
    fluorescence_lifetimes = ListType(ModelType(FluorescenceLifetime))
    electrochemical_potentials = ListType(ModelType(ElectrochemicalPotential))

    def merge(self, other):
        """Merge data from another Compound into this Compound."""
        log.debug('Merging: %s and %s' % (self.serialize(), other.serialize()))
        for k in self.keys():
            for new_item in other[k]:
                if new_item not in self[k]:
                    self[k].append(new_item)
        log.debug('Result: %s' % self.serialize())
        return self

    def merge_contextual(self, other):
        """Merge in contextual info from a template Compound."""
        # TODO: This is currently dependent on our data model? Make more robust to schema changes
        # Currently we assume all lists at Compound level, with 1 further potential nested level of lists
        for k in self.keys():
            # print('key: %s' % k)
            for item in self[k]:
                # print('item: %s' % item)
                for other_item in other.get(k, []):
                    # Skip text properties (don't merge names, labels, roles)
                    if isinstance(other_item, six.text_type):
                        continue
                    for otherk in other_item.keys():
                        if isinstance(other_item[otherk], list):
                            if len(other_item[otherk]) > 0 and len(item[otherk]) > 0:
                                other_nested_item = other_item[otherk][0]
                                for othernestedk in other_nested_item.keys():
                                    for nested_item in item[otherk]:
                                        if not nested_item[othernestedk]:
                                            nested_item[othernestedk] = other_nested_item[othernestedk]
                        elif not item[otherk]:
                            item[otherk] = other_item[otherk]
        log.debug('Result: %s' % self.serialize())
        return self

    @property
    def is_unidentified(self):
        if not self.names and not self.labels:
            return True
        return False

    # @property
    # def is_contextual(self):
    #     for k in self:
    #         # Not contextual if we have any names or labels
    #         if k in {'names', 'labels'}:
    #             return False
    #     return super(Compound, self).is_contextual


    @property
    def is_id_only(self):
        """Return True if identifier information only."""
        for key, value in self.items():
            if key not in {'names', 'labels', 'roles'} and value:
                return False
        if self.names or self.labels:
            return True
        return False class UvvisSpectrum(BaseModel):
    solvent = StringType(contextual=True)
    temperature = StringType(contextual=True)
    temperature_units = StringType(contextual=True)
    concentration = StringType(contextual=True)
    concentration_units = StringType(contextual=True)
    apparatus = StringType(contextual=True)
    peaks = ListType(ModelType(UvvisPeak)) class UvvisPeak(BaseModel):
    #: Peak value, i.e. wavelength
    value = StringType()
    #: Peak value units
    units = StringType(contextual=True)
    # Extinction value
    extinction = StringType()
    # Extinction units
    extinction_units = StringType(contextual=True)
    # Peak shape information (e.g. shoulder, broad)
    shape = StringType() class QuantumYield(BaseModel):
    """A quantum yield measurement."""
    value = StringType()
    units = StringType(contextual=True)
    solvent = StringType(contextual=True)
    type = StringType(contextual=True)
    standard = StringType(contextual=True)
    standard_value = StringType(contextual=True)
    standard_solvent = StringType(contextual=True)
    concentration = StringType(contextual=True)
    concentration_units = StringType(contextual=True)
    temperature = StringType(contextual=True)
    temperature_units = StringType(contextual=True)
    apparatus = StringType(contextual=True) class FluorescenceLifetime(BaseModel):
    """A fluorescence lifetime measurement."""
    value = StringType()
    units = StringType(contextual=True)
    solvent = StringType(contextual=True)
    concentration = StringType(contextual=True)
    concentration_units = StringType(contextual=True)
    temperature = StringType(contextual=True)
    temperature_units = StringType(contextual=True)
    apparatus = StringType(contextual=True) class MeltingPoint(BaseModel):
    """A melting point measurement."""
    value = StringType()
    units = StringType(contextual=True)
    solvent = StringType(contextual=True)
    concentration = StringType(contextual=True)
    concentration_units = StringType(contextual=True)
    apparatus = StringType(contextual=True) class GlassTransition(BaseModel):
    """A glass transition temperature."""
    value = StringType()
    units = StringType(contextual=True)
    method = StringType(contextual=True)
    concentration = StringType(contextual=True)
    concentration_units = StringType(contextual=True) class ElectrochemicalPotential(BaseModel):
    """An oxidation or reduction potential, from cyclic voltammetry."""
    value = StringType()
    units = StringType(contextual=True)
    type = StringType(contextual=True)
    solvent = StringType(contextual=True)
    concentration = StringType(contextual=True)
    concentration_units = StringType(contextual=True)
    temperature = StringType(contextual=True)
    temperature_units = StringType(contextual=True)
    apparatus = StringType(contextual=True) class IrSpectrum(BaseModel):
    solvent = StringType(contextual=True)
    temperature = StringType(contextual=True)
    temperature_units = StringType(contextual=True)
    concentration = StringType(contextual=True)
    concentration_units = StringType(contextual=True)
    apparatus = StringType(contextual=True)
    peaks = ListType(ModelType(IrPeak)) class IrPeak(BaseModel):
    value = StringType()
    units = StringType(contextual=True)
    strength = StringType()
    bond = StringType() def join(tokens, start, result):
    """Join tokens into a single string with spaces between."""
    texts = []
    if len(result) > 0:
        for e in result:
            for child in e.iter():
                if child.text is not None:
                    texts.append(child.text)
        return [E(result[0].tag, ' '.join(texts))] def merge(tokens, start, result):
    """Join tokens into a single string with no spaces."""
    texts = []
    if len(result) > 0:
        for e in result:
            for child in e.iter():
                if child.text is not None:
                    texts.append(child.text)
        return [E(result[0].tag, ''.join(texts))] def fix_whitespace(tokens, start, result):
    """Fix whitespace around hyphens and commas. Can be used to remove whitespace tokenization artefacts."""
    for e in result:
        for child in e.iter():
            child.text = child.text.replace(' , ', ', ')
            for hyphen in HYPHENS:
                child.text = child.text.replace(' %s ' % hyphen, '%s' % hyphen)
            child.text = re.sub(r'- (.) -', r'-\1-', child.text)
    return result class BaseParser(object):
    """"""

    @abstractproperty
    def root(self):
        pass

    @abstractmethod
    def interpret(self, result, start, end):
        pass

    def parse(self, tokens):
        for result in self.root.scan(tokens):
            for model in self.interpret(*result):
                yield model R = Regex I = IWord W = Word class Optional(ParseElementEnhance):

    def __init__(self, expr):
        super(Optional, self).__init__(expr)

    def _parse_tokens(self, tokens, i, actions=True):
        results = []
        try:
            results, i = self.expr.parse(tokens, i, actions)
        except (ParseException, IndexError):
            pass
        return results, i class ZeroOrMore(ParseElementEnhance):
    """Optional repetition of zero or more of the given expression."""

    def _parse_tokens(self, tokens, i, actions=True):
        results = []
        try:
            results, i = self.expr.parse(tokens, i, actions)
            while 1:
                start_i = i
                tmpresults, i = self.expr.parse(tokens, start_i, actions)
                if tmpresults:
                    results.extend(tmpresults)
        except (ParseException, IndexError):
            pass
        return ([E(self.name, *results)] if self.name else results), i class Any(BaseParserElement):
    """Always match a single token."""

    def _parse_tokens(self, tokens, i, actions=True):
        return [E(self.name or safe_name(tokens[i][1]), tokens[i][0])], i + 1 class OneOrMore(ParseElementEnhance):
    """Repetition of one or more of the given expression."""

    def _parse_tokens(self, tokens, i, actions=True):
        # must be at least one
        results, i = self.expr.parse(tokens, i, actions)
        try:
            while 1:
                start_i = i
                tmpresults, i = self.expr.parse(tokens, start_i, actions)
                if tmpresults:
                    results.extend(tmpresults)
        except (ParseException, IndexError):
            pass
        return ([E(self.name, *results)] if self.name else results), i class Start(BaseParserElement):
    """Match at start of tokens."""

    def __init__(self):
        super(Start, self).__init__()

    def _parse_tokens(self, tokens, i, actions=True):
        if i != 0:
            raise ParseException(tokens, i, 'Expected start of tokens', self)
        return [], i class End(BaseParserElement):
    """Match at end of tokens."""

    def __init__(self):
        super(End, self).__init__()

    def _parse_tokens(self, tokens, i, actions=True):
        if i < len(tokens):
            raise ParseException(tokens, i, 'Expected end of tokens', self)
        return [], i class Group(ParseElementEnhance):
    """"""

    def _parse_tokens(self, tokens, i, actions=True):
        results, i = self.expr.parse(tokens, i, actions)
        return ([E(self.name, *results)] if self.name else results), i class Not(ParseElementEnhance):
    """Check ahead to disallow a match with the given parse expression."""

    def _parse_tokens(self, tokens, i, actions=True):
        try:
            self.expr.try_parse(tokens, i)
        except (ParseException, IndexError):
            pass
        else:
            raise ParseException(tokens, i, 'Encountered disallowed token', self)
        return [], i
import logging
import re
from lxml.builder import E
from .common import delim
from ..utils import first
from ..model import Compound, UvvisSpectrum, UvvisPeak, QuantumYield, FluorescenceLifetime, MeltingPoint, GlassTransition
from ..model import ElectrochemicalPotential, IrSpectrum, IrPeak
from .actions import join, merge, fix_whitespace
from .base import BaseParser
from .cem import chemical_label, label_before_name, chemical_name, chemical_label_phrase, solvent_name, lenient_chemical_label
from .elements import R, I, W, Optional, ZeroOrMore, Any, OneOrMore, Start, End, Group, Not
# -*- coding: utf-8 -*-
"""
chemdataextractor.parse.table
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals


log = logging.getLogger(__name__)


delims = ZeroOrMore(delim)
minus = R('^[\-–−‒]$')


name_blacklist = R('^([\d\.]+)$')

#: Compound identifier column heading
compound_heading = R('(^|\b)(comp((oun)?d)?|molecule|ligand|oligomer|complex|dye|porphyrin|substance|sample|material|catalyst|acronym|isomer|(co)?polymer|chromophore|species|quinone|ether|diene|adduct|acid|radical|monomer|amine|analyte|product|system|(photo)?sensitiser|phthalocyanine|MPc)(e?s)?($|\b)', re.I)
solvent_heading = R('(^|\b)(solvent)s?($|\b)', re.I)
solvent_in_heading = Group(solvent_name)('cem')
solvent_cell = Group(solvent_name | chemical_name)('cem')
compound_cell = Group(
    (Start() + chemical_label + End())('cem') |
    (Start() + lenient_chemical_label + End())('cem') |
    chemical_label_phrase('cem') |

    (Not(Start() + OneOrMore(name_blacklist) + End()) + OneOrMore(Any())('name').add_action(join).add_action(fix_whitespace) + Optional(W('(').hide() + chemical_label + W(')').hide()))('cem') |