def _parse_table(self, node, state):
        """Parse a table node.

        :param node: The lxml table node to parse
        :param state: The global state necessary to place the node in context
            of the document as a whole.
        """
        if not self.tabular:
            logger.error("Called _parse_table without tabular activated.")
            return state

        if node.tag == "table":
            table_idx = state["table"]["idx"]
            stable_id = f"{state['document'].name}::{'table'}:{state['table']['idx']}"

            # Set name for Table
            name = node.attrib["name"] if "name" in node.attrib else None

            # Create the Table in the DB
            parts = {}
            parts["document"] = state["document"]
            parts["stable_id"] = stable_id
            parts["name"] = name
            parts["position"] = table_idx
            parent = state["parent"][node]
            if isinstance(parent, Cell):
                parts["section"] = parent.table.section
            elif isinstance(parent, Section):
                parts["section"] = parent
            else:
                raise NotImplementedError("Table is not within a Section or Cell")

            state["context"][node] = Table(**parts)

            # Local state for each table. This is required to support nested
            # tables
            state["table"][table_idx] = {
                "grid": defaultdict(int),
                "cell_pos": 0,
                "row_idx": -1,
                "col_idx": 0,
            }

            # Increment table counter
            state["table"]["idx"] += 1

        elif node.tag == "tr":
            if not isinstance(state["parent"][node], Table):
                raise NotImplementedError("Table row parent must be a Table.")

            state["table"][state["parent"][node].position]["col_idx"] = 0
            state["table"][state["parent"][node].position]["row_idx"] += 1

        elif node.tag in ["td", "th"]:
            if not isinstance(state["parent"][node], Table):
                raise NotImplementedError("Cell parent must be a Table.")

            if not state["table"][state["parent"][node].position]["row_idx"] >= 0:
                raise NotImplementedError("Table cell encountered before a table row.")

            # calculate row_start/col_start
            while state["table"][state["parent"][node].position]["grid"][
                (
                    state["table"][state["parent"][node].position]["row_idx"],
                    state["table"][state["parent"][node].position]["col_idx"],
                )
            ]:  # while a cell on the grid is occupied, keep moving
                state["table"][state["parent"][node].position]["col_idx"] += 1
            col_start = state["table"][state["parent"][node].position]["col_idx"]
            row_start = state["table"][state["parent"][node].position]["row_idx"]

            # calculate row_end/col_end
            row_end = row_start
            if "rowspan" in node.attrib:
                try:
                    row_end += int(node.get("rowspan")) - 1
                except ValueError:
                    logger.error(f"Rowspan has invalid value: '{node.get('rowspan')}'")

            col_end = col_start
            if "colspan" in node.attrib:
                try:
                    col_end += int(node.get("colspan")) - 1
                except ValueError:
                    logger.error(f"Colspan has invalid value: '{node.get('colspan')}'")

            # update grid with occupied cells
            for r, c in itertools.product(
                list(range(row_start, row_end + 1)), list(range(col_start, col_end + 1))
            ):
                state["table"][state["parent"][node].position]["grid"][(r, c)] = 1

            # Set name for Cell
            name = node.attrib["name"] if "name" in node.attrib else None

            # construct cell
            parts = defaultdict(list)
            parts["document"] = state["document"]
            parts["name"] = name
            parts["table"] = state["parent"][node]
            parts["row_start"] = row_start
            parts["row_end"] = row_end
            parts["col_start"] = col_start
            parts["col_end"] = col_end
            parts["position"] = state["table"][state["parent"][node].position][
                "cell_pos"
            ]
            stable_id = (
                f"{parts['document'].name}"
                f"::"
                f"{'cell'}"
                f":"
                f"{parts['table'].position}"
                f":"
                f"{row_start}"
                f":"
                f"{col_start}"
            )
            parts["stable_id"] = stable_id
            # Create the Cell in the DB
            state["context"][node] = Cell(**parts)

            # Update position
            state["table"][state["parent"][node].position]["col_idx"] += 1
            state["table"][state["parent"][node].position]["cell_pos"] += 1

        return state
def _parse_table ( self , node , state ) : if not self . tabular : logger . error ( "Called _parse_table without tabular activated." ) return state if node . tag == "table" : table_idx = state [ "table" ] [ "idx" ] stable_id = f"{state['document'].name}::{'table'}:{state['table']['idx']}" name = node . attrib [ "name" ] if "name" in node . attrib else None parts = { } parts [ "document" ] = state [ "document" ] parts [ "stable_id" ] = stable_id parts [ "name" ] = name parts [ "position" ] = table_idx parent = state [ "parent" ] [ node ] if isinstance ( parent , Cell ) : parts [ "section" ] = parent . table . section elif isinstance ( parent , Section ) : parts [ "section" ] = parent else : raise NotImplementedError ( "Table is not within a Section or Cell" ) state [ "context" ] [ node ] = Table ( ** parts ) state [ "table" ] [ table_idx ] = { "grid" : defaultdict ( int ) , "cell_pos" : 0 , "row_idx" : - 1 , "col_idx" : 0 , } state [ "table" ] [ "idx" ] += 1 elif node . tag == "tr" : if not isinstance ( state [ "parent" ] [ node ] , Table ) : raise NotImplementedError ( "Table row parent must be a Table." ) state [ "table" ] [ state [ "parent" ] [ node ] . position ] [ "col_idx" ] = 0 state [ "table" ] [ state [ "parent" ] [ node ] . position ] [ "row_idx" ] += 1 elif node . tag in [ "td" , "th" ] : if not isinstance ( state [ "parent" ] [ node ] , Table ) : raise NotImplementedError ( "Cell parent must be a Table." ) if not state [ "table" ] [ state [ "parent" ] [ node ] . position ] [ "row_idx" ] >= 0 : raise NotImplementedError ( "Table cell encountered before a table row." ) while state [ "table" ] [ state [ "parent" ] [ node ] . position ] [ "grid" ] [ ( state [ "table" ] [ state [ "parent" ] [ node ] . position ] [ "row_idx" ] , state [ "table" ] [ state [ "parent" ] [ node ] . position ] [ "col_idx" ] , ) ] : state [ "table" ] [ state [ "parent" ] [ node ] . position ] [ "col_idx" ] += 1 col_start = state [ "table" ] [ state [ "parent" ] [ node ] . position ] [ "col_idx" ] row_start = state [ "table" ] [ state [ "parent" ] [ node ] . position ] [ "row_idx" ] row_end = row_start if "rowspan" in node . attrib : try : row_end += int ( node . get ( "rowspan" ) ) - 1 except ValueError : logger . error ( f"Rowspan has invalid value: '{node.get('rowspan')}'" ) col_end = col_start if "colspan" in node . attrib : try : col_end += int ( node . get ( "colspan" ) ) - 1 except ValueError : logger . error ( f"Colspan has invalid value: '{node.get('colspan')}'" ) for r , c in itertools . product ( list ( range ( row_start , row_end + 1 ) ) , list ( range ( col_start , col_end + 1 ) ) ) : state [ "table" ] [ state [ "parent" ] [ node ] . position ] [ "grid" ] [ ( r , c ) ] = 1 name = node . attrib [ "name" ] if "name" in node . attrib else None parts = defaultdict ( list ) parts [ "document" ] = state [ "document" ] parts [ "name" ] = name parts [ "table" ] = state [ "parent" ] [ node ] parts [ "row_start" ] = row_start parts [ "row_end" ] = row_end parts [ "col_start" ] = col_start parts [ "col_end" ] = col_end parts [ "position" ] = state [ "table" ] [ state [ "parent" ] [ node ] . position ] [ "cell_pos" ] stable_id = ( f"{parts['document'].name}" f"::" f"{'cell'}" f":" f"{parts['table'].position}" f":" f"{row_start}" f":" f"{col_start}" ) parts [ "stable_id" ] = stable_id state [ "context" ] [ node ] = Cell ( ** parts ) state [ "table" ] [ state [ "parent" ] [ node ] . position ] [ "col_idx" ] += 1 state [ "table" ] [ state [ "parent" ] [ node ] . position ] [ "cell_pos" ] += 1 return state
Parse a table node.

        :param node: The lxml table node to parse
        :param state: The global state necessary to place the node in context
            of the document as a whole.