def get_extension(file_path):
    if typepy.is_null_string(file_path):
        raise InvalidFilePathError("file path is empty")

    return os.path.splitext(file_path)[1].lstrip(".") MODULE_NAME = "pytablereader"
def set_logger(is_enable, propagation_depth=2):
def set_log_level(log_level):
def typehints_to_str(type_hints):
    def logging_load(self):  # pragma: no cover
    def __init__(self, loader):
    def logging_load(self):
    def logging_table(self, table_data):
    def _get_load_message(self):
    def logging_load(self):
    def logging_table(self, table_data):
    def _get_load_message(self):
    def _get_load_message(self):
    def _get_load_message(self):
class LoggerInterface(metaclass=abc.ABCMeta):
class BaseLogger(LoggerInterface):
class NullSourceLogger(BaseLogger):
class FileSourceLogger(BaseLogger):
class TextSourceLogger(BaseLogger): class CsvTableFileLoader(CsvTableLoader):
    """
    A file loader class to extract tabular data from CSV files.

    :param str file_path: Path to the loading CSV file.

    .. py:attribute:: table_name

        Table name string. Defaults to ``%(filename)s``.

    :Examples:
        :ref:`example-csv-table-loader`
    """

    def __init__(self, file_path, quoting_flags=None, type_hints=None, type_hint_rules=None):
        super().__init__(file_path, quoting_flags, type_hints, type_hint_rules)

        self._validator = FileValidator(file_path)
        self._logger = FileSourceLogger(self)

    def load(self):
        """
        Extract tabular data as |TableData| instances from a CSV file.
        |load_source_desc_file|

        :return:
            Loaded table data.
            |load_table_name_desc|

            ===================  ========================================
            Format specifier     Value after the replacement
            ===================  ========================================
            ``%(filename)s``     |filename_desc|
            ``%(format_name)s``  ``"csv"``
            ``%(format_id)s``    |format_id_desc|
            ``%(global_id)s``    |global_id|
            ===================  ========================================
        :rtype: |TableData| iterator
        :raises pytablereader.DataError:
            If the CSV data is invalid.

        .. seealso::
            :py:func:`csv.reader`
        """

        self._validate()
        self._logger.logging_load()
        self.encoding = get_file_encoding(self.source, self.encoding)

        self._csv_reader = csv.reader(
            open(self.source, encoding=self.encoding),
            delimiter=self.delimiter,
            quotechar=self.quotechar,
            strict=True,
            skipinitialspace=True,
        )

        formatter = CsvTableFormatter(self._to_data_matrix())
        formatter.accept(self)

        return formatter.to_table_data()

    def _get_default_table_name_template(self):
        return tnt.FILENAME class HtmlTableFileLoader(HtmlTableLoader):
    """
    A file loader class to extract tabular data from HTML files.

    :param str file_path: Path to the loading HTML file.

    .. py:attribute:: table_name

        Table name string. Defaults to ``%(title)s_%(key)s``.

    .. py:attribute:: encoding

        HTML file encoding. Defaults to ``"utf-8"``.
    """

    def __init__(self, file_path=None, quoting_flags=None, type_hints=None, type_hint_rules=None):
        super().__init__(file_path, quoting_flags, type_hints, type_hint_rules)

        self.encoding = None

        self._validator = FileValidator(file_path)
        self._logger = FileSourceLogger(self)

    def load(self):
        """
        Extract tabular data as |TableData| instances from HTML table tags in
        a HTML file.
        |load_source_desc_file|

        :return:
            Loaded table data iterator.
            |load_table_name_desc|

            ===================  ==============================================
            Format specifier     Value after the replacement
            ===================  ==============================================
            ``%(filename)s``     |filename_desc|
            ``%(title)s``        ``<title>`` tag value of the HTML.
            ``%(key)s``          | This replaced to:
                                 | **(1)** ``id`` attribute of the table tag
                                 | **(2)** ``%(format_name)s%(format_id)s``
                                 | if ``id`` attribute not present in the
                                 | table tag.
            ``%(format_name)s``  ``"html"``
            ``%(format_id)s``    |format_id_desc|
            ``%(global_id)s``    |global_id|
            ===================  ==============================================
        :rtype: |TableData| iterator
        :raises pytablereader.DataError:
            If the HTML data is invalid or empty.

        .. note::

            Table tag attributes ignored with loaded |TableData|.
        """

        self._validate()
        self._logger.logging_load()
        self.encoding = get_file_encoding(self.source, self.encoding)

        with open(self.source, encoding=self.encoding) as fp:
            formatter = HtmlTableFormatter(fp.read(), self._logger)
        formatter.accept(self)

        return formatter.to_table_data() class JsonTableFileLoader(JsonTableLoader):
    """
    A file loader class to extract tabular data from JSON files.

    :param str file_path: Path to the loading JSON file.

    .. py:attribute:: table_name

        Table name string. Defaults to ``%(filename)s_%(key)s``.
    """

    def __init__(self, file_path=None, quoting_flags=None, type_hints=None, type_hint_rules=None):
        super().__init__(file_path, quoting_flags, type_hints, type_hint_rules)

        self.encoding = None

        self._validator = FileValidator(file_path)
        self._logger = FileSourceLogger(self)

    def load(self):
        """
        Extract tabular data as |TableData| instances from a JSON file.
        |load_source_desc_file|

        This method can be loading four types of JSON formats:

        **(1)** Single table data in a file:

            .. code-block:: json
                :caption: Acceptable JSON Schema (1): single table

                {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "additionalProperties": {
                            "anyOf": [
                                {"type": "string"},
                                {"type": "number"},
                                {"type": "boolean"},
                                {"type": "null"}
                            ]
                        }
                    }
                }

            .. code-block:: json
                :caption: Acceptable JSON example for the JSON schema (1)

                [
                    {"attr_b": 4, "attr_c": "a", "attr_a": 1},
                    {"attr_b": 2.1, "attr_c": "bb", "attr_a": 2},
                    {"attr_b": 120.9, "attr_c": "ccc", "attr_a": 3}
                ]

            The example data will be loaded as the following tabular data:

                .. table::

                    +------+------+------+
                    |attr_a|attr_b|attr_c|
                    +======+======+======+
                    |     1|   4.0|a     |
                    +------+------+------+
                    |     2|   2.1|bb    |
                    +------+------+------+
                    |     3| 120.9|ccc   |
                    +------+------+------+

        **(2)** Single table data in a file:

            .. code-block:: json
                :caption: Acceptable JSON Schema (2): single table

                {
                    "type": "object",
                    "additionalProperties": {
                        "type": "array",
                        "items": {
                            "anyOf": [
                                {"type": "string"},
                                {"type": "number"},
                                {"type": "boolean"},
                                {"type": "null"}
                            ]
                        }
                    }
                }

            .. code-block:: json
                :caption: Acceptable JSON example for the JSON schema (2)

                {
                    "attr_a": [1, 2, 3],
                    "attr_b": [4, 2.1, 120.9],
                    "attr_c": ["a", "bb", "ccc"]
                }

            The example data will be loaded as the following tabular data:

                .. table::

                    +------+------+------+
                    |attr_a|attr_b|attr_c|
                    +======+======+======+
                    |     1|   4.0|a     |
                    +------+------+------+
                    |     2|   2.1|bb    |
                    +------+------+------+
                    |     3| 120.9|ccc   |
                    +------+------+------+

        **(3)** Single table data in a file:

            .. code-block:: json

                :caption: Acceptable JSON Schema (3): single table

                {
                    "type": "object",
                    "additionalProperties": {
                        "anyOf": [
                            {"type": "string"},
                            {"type": "number"},
                            {"type": "boolean"},
                            {"type": "null"}
                        ]
                    }
                }

            .. code-block:: json
                :caption: Acceptable JSON example for the JSON schema (3)

                {
                    "num_ratings": 27,
                    "support_threads": 1,
                    "downloaded": 925716,
                    "last_updated":"2017-12-01 6:22am GMT",
                    "added":"2010-01-20",
                    "num": 1.1,
                    "hoge": null
                }

            The example data will be loaded as the following tabular data:

                .. table::

                    +---------------+---------------------+
                    |      key      |        value        |
                    +===============+=====================+
                    |num_ratings    |                   27|
                    +---------------+---------------------+
                    |support_threads|                    1|
                    +---------------+---------------------+
                    |downloaded     |               925716|
                    +---------------+---------------------+
                    |last_updated   |2017-12-01 6:22am GMT|
                    +---------------+---------------------+
                    |added          |2010-01-20           |
                    +---------------+---------------------+
                    |num            |                  1.1|
                    +---------------+---------------------+
                    |hoge           |None                 |
                    +---------------+---------------------+

        **(4)** Multiple table data in a file:

            .. code-block:: json
                :caption: Acceptable JSON Schema (4): multiple tables

                {
                    "type": "object",
                    "additionalProperties": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "additionalProperties": {
                                "anyOf": [
                                    {"type": "string"},
                                    {"type": "number"},
                                    {"type": "boolean"},
                                    {"type": "null"}
                                ]
                            }
                        }
                    }
                }

            .. code-block:: json
                :caption: Acceptable JSON example for the JSON schema (4)

                {
                    "table_a" : [
                        {"attr_b": 4, "attr_c": "a", "attr_a": 1},
                        {"attr_b": 2.1, "attr_c": "bb", "attr_a": 2},
                        {"attr_b": 120.9, "attr_c": "ccc", "attr_a": 3}
                    ],
                    "table_b" : [
                        {"a": 1, "b": 4},
                        {"a": 2 },
                        {"a": 3, "b": 120.9}
                    ]
                }

            The example data will be loaded as the following tabular data:

                .. table:: table_a

                    +------+------+------+
                    |attr_a|attr_b|attr_c|
                    +======+======+======+
                    |     1|   4.0|a     |
                    +------+------+------+
                    |     2|   2.1|bb    |
                    +------+------+------+
                    |     3| 120.9|ccc   |
                    +------+------+------+

                .. table:: table_b

                    +-+-----+
                    |a|  b  |
                    +=+=====+
                    |1|  4.0|
                    +-+-----+
                    |2| None|
                    +-+-----+
                    |3|120.9|
                    +-+-----+

        **(5)** Multiple table data in a file:

            .. code-block:: json
                :caption: Acceptable JSON Schema (5): multiple tables

                {
                    "type": "object",
                    "additionalProperties": {
                        "type": "object",
                        "additionalProperties": {
                            "type": "array",
                            "items": {
                                "anyOf": [
                                    {"type": "string"},
                                    {"type": "number"},
                                    {"type": "boolean"},
                                    {"type": "null"}
                                ]
                            }
                        }
                    }
                }

            .. code-block:: json
                :caption: Acceptable JSON example for the JSON schema (5)

                {
                    "table_a" : {
                        "attr_a": [1, 2, 3],
                        "attr_b": [4, 2.1, 120.9],
                        "attr_c": ["a", "bb", "ccc"]
                    },
                    "table_b" : {
                        "a": [1, 3],
                        "b": [4, 120.9]
                    }
                }

            The example data will be loaded as the following tabular data:

                .. table:: table_a

                    +------+------+------+
                    |attr_a|attr_b|attr_c|
                    +======+======+======+
                    |     1|   4.0|a     |
                    +------+------+------+
                    |     2|   2.1|bb    |
                    +------+------+------+
                    |     3| 120.9|ccc   |
                    +------+------+------+

                .. table:: table_b

                    +-+-----+
                    |a|  b  |
                    +=+=====+
                    |1|  4.0|
                    +-+-----+
                    |3|120.9|
                    +-+-----+

        **(6)** Multiple table data in a file:

            .. code-block:: json
                :caption: Acceptable JSON Schema (6): multiple tables

                {
                    "type": "object",
                    "additionalProperties": {
                        "type": "object",
                        "additionalProperties": {
                            "anyOf": [
                                {"type": "string"},
                                {"type": "number"},
                                {"type": "boolean"},
                                {"type": "null"}
                            ]
                        }
                    }
                }

            .. code-block:: json
                :caption: Acceptable JSON example for the JSON schema (6)

                {
                    "table_a": {
                        "num_ratings": 27,
                        "support_threads": 1,
                        "downloaded": 925716,
                        "last_updated":"2017-12-01 6:22am GMT",
                        "added":"2010-01-20",
                        "num": 1.1,
                        "hoge": null
                    },
                    "table_b": {
                        "a": 4,
                        "b": 120.9
                    }
                }

            The example data will be loaded as the following tabular data:

                .. table:: table_a

                    +---------------+---------------------+
                    |      key      |        value        |
                    +===============+=====================+
                    |num_ratings    |                   27|
                    +---------------+---------------------+
                    |support_threads|                    1|
                    +---------------+---------------------+
                    |downloaded     |               925716|
                    +---------------+---------------------+
                    |last_updated   |2017-12-01 6:22am GMT|
                    +---------------+---------------------+
                    |added          |2010-01-20           |
                    +---------------+---------------------+
                    |num            |                  1.1|
                    +---------------+---------------------+
                    |hoge           |None                 |
                    +---------------+---------------------+


                .. table:: table_b

                    +---+-----+
                    |key|value|
                    +===+=====+
                    |a  |  4.0|
                    +---+-----+
                    |b  |120.9|
                    +---+-----+

        :return:
            Loaded table data iterator.
            |load_table_name_desc|

            ===================  ==============================================
            Format specifier     Value after the replacement
            ===================  ==============================================
            ``%(filename)s``     |filename_desc|
            ``%(key)s``          | This replaced the different value
                                 | for each single/multiple JSON tables:
                                 | [single JSON table]
                                 | ``%(format_name)s%(format_id)s``
                                 | [multiple JSON table] Table data key.
            ``%(format_name)s``  ``"json"``
            ``%(format_id)s``    |format_id_desc|
            ``%(global_id)s``    |global_id|
            ===================  ==============================================
        :rtype: |TableData| iterator
        :raises pytablereader.DataError:
            If the data is invalid JSON.
        :raises pytablereader.error.ValidationError:
            If the data is not acceptable JSON format.
        """

        formatter = JsonTableFormatter(self.load_dict())
        formatter.accept(self)

        return formatter.to_table_data()

    def load_dict(self):
        self._validate()
        self._logger.logging_load()
        self.encoding = get_file_encoding(self.source, self.encoding)

        with open(self.source, encoding=self.encoding) as fp:
            try:
                return json.load(fp, object_pairs_hook=OrderedDict)
            except ValueError as e:
                raise ValidationError(e)

    def _get_default_table_name_template(self):
        return f"{tnt.FILENAME:s}_{tnt.KEY:s}" class JsonLinesTableFileLoader(JsonLinesTableLoader):
    """
    A file loader class to extract tabular data from Line-delimited JSON files.

    :param str file_path: Path to the loading Line-delimited JSON file.

    .. py:attribute:: table_name

        Table name string. Defaults to ``%(filename)s_%(key)s``.
    """

    def __init__(self, file_path=None, quoting_flags=None, type_hints=None, type_hint_rules=None):
        super().__init__(file_path, quoting_flags, type_hints, type_hint_rules)

        self.encoding = None

        self._validator = FileValidator(file_path)
        self._logger = FileSourceLogger(self)

    def load(self):
        """
        Extract tabular data as |TableData| instances from a Line-delimited JSON file.
        |load_source_desc_file|

        :return:
            Loaded table data iterator.
            |load_table_name_desc|

        :rtype: |TableData| iterator
        :raises pytablereader.DataError:
            If the data is invalid Line-delimited JSON.
        :raises pytablereader.error.ValidationError:
            If the data is not acceptable Line-delimited JSON format.
        """

        formatter = JsonLinesTableFormatter(self.load_dict())
        formatter.accept(self)

        return formatter.to_table_data()

    def load_dict(self):
        self._validate()
        self._logger.logging_load()
        self.encoding = get_file_encoding(self.source, self.encoding)

        buffer = []
        with open(self.source, encoding=self.encoding) as fp:
            for line_idx, line in enumerate(fp):
                line = line.strip()
                if not line:
                    continue

                try:
                    buffer.append(json.loads(line, object_pairs_hook=OrderedDict))
                except json.JSONDecodeError as e:
                    raise ValidationError(
                        "line {line_idx}: {msg}: {value}".format(
                            line_idx=line_idx + 1, msg=e, value=line
                        )
                    )

        return buffer

    def _get_default_table_name_template(self):
        return f"{tnt.FILENAME:s}_{tnt.KEY:s}" class LtsvTableFileLoader(LtsvTableLoader):
    """
    `Labeled Tab-separated Values (LTSV) <http://ltsv.org/>`__
    format file loader class.

    :param str file_path: Path to the loading LTSV file.

    .. py:attribute:: table_name

        Table name string. Defaults to ``%(filename)s``.
    """

    def __init__(self, file_path, quoting_flags=None, type_hints=None, type_hint_rules=None):
        super().__init__(file_path, quoting_flags, type_hints, type_hint_rules)

        self.encoding = None

        self._validator = FileValidator(file_path)
        self._logger = FileSourceLogger(self)

        self.__file = None

    def load(self):
        """
        Extract tabular data as |TableData| instances from a LTSV file.
        |load_source_desc_file|

        :return:
            Loaded table data.
            |load_table_name_desc|

            ===================  ========================================
            Format specifier     Value after the replacement
            ===================  ========================================
            ``%(filename)s``     |filename_desc|
            ``%(format_name)s``  ``"ltsv"``
            ``%(format_id)s``    |format_id_desc|
            ``%(global_id)s``    |global_id|
            ===================  ========================================
        :rtype: |TableData| iterator
        :raises pytablereader.InvalidHeaderNameError:
            If an invalid label name is included in the LTSV file.
        :raises pytablereader.DataError:
            If the LTSV data is invalid.
        """

        self._validate()
        self._logger.logging_load()
        self.encoding = get_file_encoding(self.source, self.encoding)

        self._ltsv_input_stream = open(self.source, encoding=self.encoding)

        for data_matrix in self._to_data_matrix():
            formatter = SingleJsonTableConverterA(data_matrix)
            formatter.accept(self)

            return formatter.to_table_data()

    def _get_default_table_name_template(self):
        return tnt.FILENAME class MarkdownTableFileLoader(MarkdownTableLoader):
    """
    A file loader class to extract tabular data from Markdown files.

    :param str file_path: Path to the loading Markdown file.

    .. py:attribute:: table_name

        Table name string. Defaults to ``%(filename)s_%(key)s``.
    """

    def __init__(self, file_path=None, quoting_flags=None, type_hints=None, type_hint_rules=None):
        super().__init__(file_path, quoting_flags, type_hints, type_hint_rules)

        self.encoding = None

        self._validator = FileValidator(file_path)
        self._logger = FileSourceLogger(self)

    def load(self):
        """
        Extract tabular data as |TableData| instances from a Markdown file.
        |load_source_desc_file|

        :return:
            Loaded table data iterator.
            |load_table_name_desc|

            ===================  ==============================================
            Format specifier     Value after the replacement
            ===================  ==============================================
            ``%(filename)s``     |filename_desc|
            ``%(key)s``          ``%(format_name)s%(format_id)s``
            ``%(format_name)s``  ``"markdown"``
            ``%(format_id)s``    |format_id_desc|
            ``%(global_id)s``    |global_id|
            ===================  ==============================================
        :rtype: |TableData| iterator
        :raises pytablereader.DataError:
            If the Markdown data is invalid or empty.
        """

        self._validate()
        self._logger.logging_load()
        self.encoding = get_file_encoding(self.source, self.encoding)

        with open(self.source, encoding=self.encoding) as fp:
            formatter = MarkdownTableFormatter(fp.read(), self._logger)
        formatter.accept(self)

        return formatter.to_table_data()

    def _get_default_table_name_template(self):
        return f"{tnt.FILENAME:s}_{tnt.KEY:s}" class MediaWikiTableFileLoader(MediaWikiTableLoader):
    """
    A file loader class to extract tabular data from MediaWiki files.

    :param str file_path: Path to the loading file.

    .. py:attribute:: table_name

        Table name string. Defaults to ``%(filename)s_%(key)s``.
    """

    def __init__(self, file_path=None, quoting_flags=None, type_hints=None, type_hint_rules=None):
        super().__init__(file_path, quoting_flags, type_hints, type_hint_rules)

        self.encoding = None

        self._validator = FileValidator(file_path)
        self._logger = FileSourceLogger(self)

    def load(self):
        """
        Extract tabular data as |TableData| instances from a MediaWiki file.
        |load_source_desc_file|

        :return:
            Loaded table data iterator.
            |load_table_name_desc|

            ===================  ==============================================
            Format specifier     Value after the replacement
            ===================  ==============================================
            ``%(filename)s``     |filename_desc|
            ``%(key)s``          | This replaced to:
                                 | **(1)** ``caption`` mark of the table
                                 | **(2)** ``%(format_name)s%(format_id)s``
                                 | if ``caption`` mark not included
                                 | in the table.
            ``%(format_name)s``  ``"mediawiki"``
            ``%(format_id)s``    |format_id_desc|
            ``%(global_id)s``    |global_id|
            ===================  ==============================================
        :rtype: |TableData| iterator
        :raises pytablereader.DataError:
            If the MediaWiki data is invalid or empty.
        """

        self._validate()
        self._logger.logging_load()
        self.encoding = get_file_encoding(self.source, self.encoding)

        with open(self.source, encoding=self.encoding) as fp:
            formatter = MediaWikiTableFormatter(fp.read())
        formatter.accept(self)

        return formatter.to_table_data()

    def _get_default_table_name_template(self):
        return f"{tnt.FILENAME:s}_{tnt.KEY:s}" class ExcelTableFileLoader(SpreadSheetLoader):
    """
    A file loader class to extract tabular data from Microsoft Excel |TM|
    files.

    :param str file_path: Path to the loading Excel workbook file.

    .. py:attribute:: table_name

        Table name string. Defaults to ``%(sheet)s``.

    .. py:attribute:: start_row

        The first row to search header row.
    """

    @property
    def format_name(self):
        return "excel"

    @property
    def _sheet_name(self):
        return self._worksheet.name

    @property
    def _row_count(self):
        return self._worksheet.nrows

    @property
    def _col_count(self):
        return self._worksheet.ncols

    def __init__(self, file_path=None, quoting_flags=None, type_hints=None, type_hint_rules=None):
        super().__init__(file_path, quoting_flags, type_hints, type_hint_rules)

        self._validator = FileValidator(file_path)
        self._logger = FileSourceLogger(self)

    def load(self):
        """
        Extract tabular data as |TableData| instances from an Excel file.
        |spreadsheet_load_desc|

        :return:
            Loaded |TableData| iterator.
            |TableData| created for each sheet in the workbook.
            |load_table_name_desc|

            ===================  ====================================
            Format specifier     Value after the replacement
            ===================  ====================================
            ``%(filename)s``     Filename of the workbook
            ``%(sheet)s``        Name of the sheet
            ``%(format_name)s``  ``"spreadsheet"``
            ``%(format_id)s``    |format_id_desc|
            ``%(global_id)s``    |global_id|
            ===================  ====================================
        :rtype: |TableData| iterator
        :raises pytablereader.DataError:
            If the header row is not found.
        :raises pytablereader.error.OpenError:
            If failed to open the source file.
        """

        try:
            import excelrd as xlrd
        except ImportError:
            import xlrd

        self._validate()
        self._logger.logging_load()

        try:
            workbook = xlrd.open_workbook(self.source)
        except xlrd.biffh.XLRDError as e:
            raise OpenError(e)

        for worksheet in workbook.sheets():
            self._worksheet = worksheet

            if self._is_empty_sheet():
                continue

            self.__extract_not_empty_col_idx()

            try:
                start_row_idx = self._get_start_row_idx()
            except DataError:
                continue

            rows = [
                self.__get_row_values(row_idx)
                for row_idx in range(start_row_idx + 1, self._row_count)
            ]

            self.inc_table_count()
            headers = self.__get_row_values(start_row_idx)

            yield TableData(
                self._make_table_name(),
                headers,
                rows,
                dp_extractor=self.dp_extractor,
                type_hints=self._extract_type_hints(headers),
            )

    def _is_empty_sheet(self):
        return any(
            [
                self._col_count == 0,
                self._row_count <= 1,
                # nrows == 1 means exists header row only
            ]
        )

    def _get_start_row_idx(self):
        for row_idx in range(self.start_row, self._row_count):
            if self.__is_header_row(row_idx):
                break
        else:
            raise DataError("header row not found")

        return row_idx

    def __is_header_row(self, row_idx):
        try:
            from excelrd import XL_CELL_EMPTY
        except ImportError:
            from xlrd import XL_CELL_EMPTY

        return XL_CELL_EMPTY not in self._worksheet.row_types(
            row_idx, self._start_col_idx, self._end_col_idx + 1
        )

    @staticmethod
    def __is_empty_cell_types(cell_types):
        try:
            from excelrd import XL_CELL_EMPTY
        except ImportError:
            from xlrd import XL_CELL_EMPTY

        return all([cell_type == XL_CELL_EMPTY for cell_type in cell_types])

    def __extract_not_empty_col_idx(self):
        col_idx_list = [
            col_idx
            for col_idx in range(self._col_count)
            if not self.__is_empty_cell_types(self._worksheet.col_types(col_idx))
        ]

        self._start_col_idx = min(col_idx_list)
        self._end_col_idx = max(col_idx_list)

    def __get_row_values(self, row_idx):
        return self._worksheet.row_values(row_idx, self._start_col_idx, self._end_col_idx + 1) class SqliteFileLoader(AbstractTableReader):
    """
    A file loader class to extract tabular data from SQLite database files.

    :param str file_path: Path to the loading SQLite database file.

    .. py:attribute:: table_name

        Table name string. Defaults to ``%(filename)s_%(key)s``.

    :Dependency Packages:
        - `SimpleSQLite <https://github.com/thombashi/SimpleSQLite>`__
    """

    @property
    def format_name(self):
        return "sqlite"

    def __init__(self, file_path=None, quoting_flags=None, type_hints=None, type_hint_rules=None):
        super().__init__(file_path, quoting_flags, type_hints, type_hint_rules)

        self._validator = FileValidator(file_path)

    def load(self):
        """
        Extract tabular data as |TableData| instances from a SQLite database
        file. |load_source_desc_file|

        :return:
            Loaded table data iterator.
            |load_table_name_desc|

            ===================  ==============================================
            Format specifier     Value after the replacement
            ===================  ==============================================
            ``%(filename)s``     |filename_desc|
            ``%(key)s``          ``%(format_name)s%(format_id)s``
            ``%(format_name)s``  ``"sqlite"``
            ``%(format_id)s``    |format_id_desc|
            ``%(global_id)s``    |global_id|
            ===================  ==============================================
        :rtype: |TableData| iterator
        :raises pytablereader.DataError:
            If the SQLite database file data is invalid or empty.
        """

        self._validate()

        formatter = SqliteTableFormatter(self.source)
        formatter.accept(self)

        return formatter.to_table_data()

    def _get_default_table_name_template(self):
        return f"{tnt.FORMAT_NAME:s}{tnt.FORMAT_ID:s}" class TsvTableFileLoader(CsvTableFileLoader):
    """
    Tab separated values (TSV) format file loader class.

    :param str file_path: Path to the loading TSV file.

    .. py:attribute:: table_name

        Table name string. Defaults to ``%(filename)s``.
    """

    @property
    def format_name(self):
        return "tsv"

    def __init__(self, file_path, quoting_flags=None, type_hints=None, type_hint_rules=None):
        super().__init__(file_path, quoting_flags, type_hints, type_hint_rules)

        self.delimiter = "\t"

        self._validator = FileValidator(file_path) class BaseTableLoaderFactory(metaclass=abc.ABCMeta):
    @property
    def source(self):
        """
        :return: Data source to load.
        :rtype: str
        """

        return self._source

    def __init__(self, source, encoding=None):
        if not encoding:
            self._encoding = Default.ENCODING
        else:
            self._encoding = encoding

        self._source = MultiByteStrDecoder(source, [encoding]).unicode_str

    @abc.abstractmethod
    def create_from_path(self):  # pragma: no cover
        pass

    @abc.abstractmethod
    def create_from_format_name(self, format_name):  # pragma: no cover
        pass

    @abc.abstractmethod
    def _get_extension_loader_mapping(self):  # pragma: no cover
        pass

    @abc.abstractmethod
    def _get_format_name_loader_mapping(self):  # pragma: no cover
        pass

    def get_format_names(self):
        """
        :return: Available format names.
        :rtype: list
        """

        return sorted(self._get_format_name_loader_mapping())

    def get_format_name_list(self):
        warnings.warn("'get_format_name_list' has moved to 'get_format_names'", DeprecationWarning)
        return self.get_format_names()

    def get_extensions(self):
        """
        :return: Available format file extensions.
        :rtype: list
        """

        return sorted(self._get_extension_loader_mapping())

    def get_extension_list(self):
        warnings.warn("'get_extension_list' has moved to 'get_extensions'", DeprecationWarning)
        return self.get_extensions()

    def _get_loader_class(self, loader_mapping, format_name):
        try:
            format_name = format_name.casefold()
        except AttributeError:
            raise TypeError("format name must be a string")

        try:
            return loader_mapping[format_name]
        except KeyError:
            raise LoaderNotFoundError(
                ", ".join(
                    [
                        f"loader not found: format='{format_name}'",
                        f"source='{self.source}'",
                    ]
                )
            )

    def _create_from_extension(self, extension):
        try:
            loader = self._get_loader_class(self._get_extension_loader_mapping(), extension)(
                self.source
            )

            return self._post_create(loader, extension=extension)
        except LoaderNotFoundError as e:
            raise LoaderNotFoundError(
                "\n".join(
                    [
                        f"{e.args[0]:s} (unknown extension).",
                        "",
                        "acceptable extensions are: {}.".format(", ".join(self.get_extensions())),
                        f"actual: '{extension}'",
                    ]
                )
            )

    def _create_from_format_name(self, format_name):
        try:
            loader = self._get_loader_class(self._get_format_name_loader_mapping(), format_name)(
                self.source
            )

            return self._post_create(loader, format_name=format_name)
        except LoaderNotFoundError as e:
            raise LoaderNotFoundError(
                "\n".join(
                    [
                        f"{e.args[0]:s} (unknown format name).",
                        "acceptable format names are: {}.".format(
                            ", ".join(self.get_format_names())
                        ),
                    ]
                )
            )

    def _post_create(self, loader, **kwargs):
        loader.encoding = self._encoding

        if loader.format_name == "csv" and kwargs.get("format_name") == "ssv":
            loader.delimiter = " "

        return loader
from mbstrdecoder import detect_file_encoding
from .._common import get_extension
from .._logger import logger
from ..csv.core import CsvTableFileLoader
from ..html.core import HtmlTableFileLoader
from ..json.core import JsonTableFileLoader
from ..jsonlines.core import JsonLinesTableFileLoader
from ..ltsv.core import LtsvTableFileLoader
from ..markdown.core import MarkdownTableFileLoader
from ..mediawiki.core import MediaWikiTableFileLoader
from ..spreadsheet.excelloader import ExcelTableFileLoader
from ..sqlite.core import SqliteFileLoader
from ..tsv.core import TsvTableFileLoader
from ._base import BaseTableLoaderFactory
        If the ``file_path`` is an empty path.
    """

    @property
    def file_extension(self):
        """
        :return: File extension of the :py:attr:`.source` (without period).
        :rtype: str
        """

        return get_extension(self.source)

    def __init__(self, source, encoding=None):
        if not encoding and source:
            encoding = detect_file_encoding(source)
            logger.debug(f"detect encoding: file={source}, encoding={encoding}")

        super().__init__(source, encoding)

    def create_from_path(self):
        """
        Create a file loader from the file extension to loading file.
        Supported file extensions are as follows:

            ==========================  =======================================
            Extension                   Loader
            ==========================  =======================================
            ``"csv"``                   :py:class:`~.CsvTableFileLoader`
            ``"xls"``/``"xlsx"``        :py:class:`~.ExcelTableFileLoader`
            ``"htm"``/``"html"``        :py:class:`~.HtmlTableFileLoader`
            ``"json"``                  :py:class:`~.JsonTableFileLoader`
            ``"jsonl"``                 :py:class:`~.JsonLinesTableFileLoader`
            ``"ldjson"``                :py:class:`~.JsonLinesTableFileLoader`
            ``"ltsv"``                  :py:class:`~.LtsvTableFileLoader`
            ``"md"``                    :py:class:`~.MarkdownTableFileLoader`
            ``"ndjson"``                :py:class:`~.JsonLinesTableFileLoader`
            ``"sqlite"``/``"sqlite3"``  :py:class:`~.SqliteFileLoader`
            ``"tsv"``                   :py:class:`~.TsvTableFileLoader`
            ==========================  =======================================

        :return:
            Loader that coincides with the file extension of the
            :py:attr:`.file_extension`.
        :raises pytablereader.LoaderNotFoundError:
            |LoaderNotFoundError_desc| loading the file.
        """

        loader = self._create_from_extension(self.file_extension)

        logger.debug(
            "TableFileLoaderFactory.create_from_path: extension={}, loader={}".format(
                self.file_extension, loader.format_name
            )
        )

        return loader

    def create_from_format_name(self, format_name):
        """
        Create a file loader from a format name.
        Supported file formats are as follows:

            ================  ======================================
            Format name               Loader
            ================  ======================================
            ``"csv"``         :py:class:`~.CsvTableFileLoader`
            ``"excel"``       :py:class:`~.ExcelTableFileLoader`
            ``"html"``        :py:class:`~.HtmlTableFileLoader`
            ``"json"``        :py:class:`~.JsonTableFileLoader`
            ``"json"``        :py:class:`~.JsonTableFileLoader`
            ``"json_lines"``  :py:class:`~.JsonTableFileLoader`
            ``"jsonl"``       :py:class:`~.JsonLinesTableFileLoader`
            ``"ltsv"``        :py:class:`~.LtsvTableFileLoader`
            ``"markdown"``    :py:class:`~.MarkdownTableFileLoader`
            ``"mediawiki"``   :py:class:`~.MediaWikiTableFileLoader`
            ``"ndjson"``      :py:class:`~.JsonLinesTableFileLoader`
            ``"sqlite"``      :py:class:`~.SqliteFileLoader`
            ``"ssv"``         :py:class:`~.CsvTableFileLoader`
            ``"tsv"``         :py:class:`~.TsvTableFileLoader`
            ================  ======================================

        :param str format_name: Format name string (case insensitive).
        :return: Loader that coincides with the ``format_name``:
        :raises pytablereader.LoaderNotFoundError:
            |LoaderNotFoundError_desc| the format.
        """

        loader = self._create_from_format_name(format_name)

        logger.debug(
            "TableFileLoaderFactory.create_from_format_name: name={}, loader={}".format(
                format_name, loader.format_name
            )
        )

        return loader

    @staticmethod
    def _get_common_loader_mapping():
        return {
            "csv": CsvTableFileLoader,
            "html": HtmlTableFileLoader,
            "json": JsonTableFileLoader,
            "jsonl": JsonLinesTableFileLoader,
            "ldjson": JsonLinesTableFileLoader,
            "ltsv": LtsvTableFileLoader,
            "ndjson": JsonLinesTableFileLoader,
            "sqlite": SqliteFileLoader,
            "tsv": TsvTableFileLoader,
        }

    def _get_extension_loader_mapping(self):
        """
        :return: Mappings of format extension and loader class.
        :rtype: dict
        """

        loader_table = self._get_common_loader_mapping()
        loader_table.update(
            {

                "htm": HtmlTableFileLoader,