def records(self):
        """Chemical records that have been parsed from the table."""
        caption_records = self.caption.records
        # Parse headers to extract contextual data and determine value parser for the column
        value_parsers = {}
        header_compounds = defaultdict(list)
        table_records = ModelList()
        seen_compound_col = False
        log.debug('Parsing table headers')

        for i, col_headings in enumerate(zip(*self.headings)):
            # log.info('Considering column %s' % i)
            for parsers in self.parsers:
                log.debug(parsers)
                heading_parser = parsers[0]
                value_parser = parsers[1] if len(parsers) > 1 else None
                disallowed_parser = parsers[2] if len(parsers) > 2 else None
                allowed = False
                disallowed = False
                for cell in col_headings:
                    log.debug(cell.tagged_tokens)
                    results = list(heading_parser.parse(cell.tagged_tokens))
                    if results:
                        allowed = True
                        log.debug('Heading column %s: Match %s: %s' % (i, heading_parser.__class__.__name__, [c.serialize() for c in results]))
                    # Results from every parser are stored as header compounds
                    header_compounds[i].extend(results)
                    # Referenced footnote records are also stored
                    for footnote in self.footnotes:
                        # print('%s - %s - %s' % (footnote.id, cell.references, footnote.id in cell.references))
                        if footnote.id in cell.references:
                            log.debug('Adding footnote %s to column %s: %s' % (footnote.id, i, [c.serialize() for c in footnote.records]))
                            # print('Footnote records: %s' % [c.to_primitive() for c in footnote.records])
                            header_compounds[i].extend(footnote.records)
                    # Check if the disallowed parser matches this cell
                    if disallowed_parser and list(disallowed_parser.parse(cell.tagged_tokens)):
                        log.debug('Column %s: Disallowed %s' % (i, heading_parser.__class__.__name__))
                        disallowed = True
                # If heading parser matches and disallowed parser doesn't, store the value parser
                if allowed and not disallowed and value_parser and i not in value_parsers:
                    if isinstance(value_parser, CompoundCellParser):
                        # Only take the first compound col
                        if seen_compound_col:
                            continue
                        seen_compound_col = True
                    log.debug('Column %s: Value parser: %s' % (i, value_parser.__class__.__name__))
                    value_parsers[i] = value_parser
                    # Stop after value parser is assigned?

        # for hrow in self.headings:
        #     for i, cell in enumerate(hrow):
        #         log.debug(cell.tagged_tokens)
        #         for heading_parser, value_parser in self.parsers:
        #             results = list(heading_parser.parse(cell.tagged_tokens))
        #             if results:
        #                 log.debug('Heading column %s: Match %s: %s' % (i, heading_parser.__class__.__name__, [c.to_primitive() for c in results]))
        #             # Results from every parser are stored as header compounds
        #             header_compounds[i].extend(results)
        #             if results and value_parser and i not in value_parsers:
        #                 if isinstance(value_parser, CompoundCellParser):
        #                     # Only take the first compound col
        #                     if seen_compound_col:
        #                         continue
        #                     seen_compound_col = True
        #                 value_parsers[i] = value_parser
        #                 break  # Stop after first heading parser matches
        #         # Referenced footnote records are also stored
        #         for footnote in self.footnotes:
        #             # print('%s - %s - %s' % (footnote.id, cell.references, footnote.id in cell.references))
        #             if footnote.id in cell.references:
        #                 log.debug('Adding footnote %s to column %s: %s' % (footnote.id, i, [c.to_primitive() for c in footnote.records]))
        #                 # print('Footnote records: %s' % [c.to_primitive() for c in footnote.records])
        #                 header_compounds[i].extend(footnote.records)

        # If no parsers, skip processing table
        if value_parsers:

            # If no CompoundCellParser() in value_parsers and value_parsers[0] == [] then set CompoundCellParser()
            if not seen_compound_col and 0 not in value_parsers:
                log.debug('No compound column found in table, assuming first column')
                value_parsers[0] = CompoundCellParser()

            for row in self.rows:
                row_compound = Compound()
                # Keep cell records that are contextual to merge at the end
                contextual_cell_compounds = []
                for i, cell in enumerate(row):
                    log.debug(cell.tagged_tokens)
                    if i in value_parsers:
                        results = list(value_parsers[i].parse(cell.tagged_tokens))
                        if results:
                            log.debug('Cell column %s: Match %s: %s' % (i, value_parsers[i].__class__.__name__, [c.serialize() for c in results]))
                        # For each result, merge in values from elsewhere
                        for result in results:
                            # Merge each header_compounds[i]
                            for header_compound in header_compounds[i]:
                                if header_compound.is_contextual:
                                    result.merge_contextual(header_compound)
                            # Merge footnote compounds
                            for footnote in self.footnotes:
                                if footnote.id in cell.references:
                                    for footnote_compound in footnote.records:
                                        result.merge_contextual(footnote_compound)
                            if result.is_contextual:
                                # Don't merge cell as a value compound if there are no values
                                contextual_cell_compounds.append(result)
                            else:
                                row_compound.merge(result)
                # Merge contextual information from cells
                for contextual_cell_compound in contextual_cell_compounds:
                    row_compound.merge_contextual(contextual_cell_compound)
                # If no compound name/label, try take from previous row
                if not row_compound.names and not row_compound.labels and table_records:
                    prev = table_records[-1]
                    row_compound.names = prev.names
                    row_compound.labels = prev.labels
                # Merge contextual information from caption into the full row
                for caption_compound in caption_records:
                    if caption_compound.is_contextual:
                        row_compound.merge_contextual(caption_compound)
                # And also merge from any footnotes that are referenced from the caption
                for footnote in self.footnotes:
                    if footnote.id in self.caption.references:
                        # print('Footnote records: %s' % [c.to_primitive() for c in footnote.records])
                        for fn_compound in footnote.records:
                            row_compound.merge_contextual(fn_compound)

                log.debug(row_compound.serialize())
                if row_compound.serialize():
                    table_records.append(row_compound)

        # TODO: If no rows have name or label, see if one is in the caption

        # Include non-contextual caption records in the final output
        caption_records = [c for c in caption_records if not c.is_contextual]
        table_records += caption_records
        return table_records
Chemical records that have been parsed from the table.