# -*- coding: utf-8 -*- import logging import click try: import matplotlib.pyplot as plt except ImportError: _HAS_MPL = False else: _HAS_MPL = True from . import __version__, read_pdf, plot logger = logging.getLogger('camelot') logger.setLevel(logging.INFO) class Config(object): def __init__(self): self.config = {} def set_config(self, key, value): self.config[key] = value pass_config = click.make_pass_decorator(Config) @click.group() @click.version_option(version=__version__) @click.option('-p', '--pages', default='1', help='Comma-separated page numbers.' ' Example: 1,3,4 or 1,4-end.') @click.option('-pw', '--password', help='Password for decryption.') @click.option('-o', '--output', help='Output file path.') @click.option('-f', '--format', type=click.Choice(['csv', 'json', 'excel', 'html']), help='Output file format.') @click.option('-z', '--zip', is_flag=True, help='Create ZIP archive.') @click.option('-split', '--split_text', is_flag=True, help='Split text that spans across multiple cells.') @click.option('-flag', '--flag_size', is_flag=True, help='Flag text based on' ' font size. Useful to detect super/subscripts.') @click.option('-M', '--margins', nargs=3, default=(1.0, 0.5, 0.1), help='PDFMiner char_margin, line_margin and word_margin.') @click.option('-q', '--quiet', is_flag=True, help='Suppress warnings.') @click.pass_context def cli(ctx, *args, **kwargs): """Camelot: PDF Table Extraction for Humans""" ctx.obj = Config() for key, value in kwargs.items(): ctx.obj.set_config(key, value) @cli.command('lattice') @click.option('-T', '--table_areas', default=[], multiple=True, help='Table areas to process. Example: x1,y1,x2,y2' ' where x1, y1 -> left-top and x2, y2 -> right-bottom.') @click.option('-back', '--process_background', is_flag=True, help='Process background lines.') @click.option('-scale', '--line_size_scaling', default=15, help='Line size scaling factor. The larger the value,' ' the smaller the detected lines.') @click.option('-copy', '--copy_text', default=[], type=click.Choice(['h', 'v']), multiple=True, help='Direction in which text in a spanning cell' ' will be copied over.') @click.option('-shift', '--shift_text', default=['l', 't'], type=click.Choice(['', 'l', 'r', 't', 'b']), multiple=True, help='Direction in which text in a spanning cell will flow.') @click.option('-l', '--line_close_tol', default=2, help='Tolerance parameter used to merge close vertical' ' and horizontal lines.') @click.option('-j', '--joint_close_tol', default=2, help='Tolerance parameter used to decide whether' ' the detected lines and points lie close to each other.') @click.option('-block', '--threshold_blocksize', default=15, help='For adaptive thresholding, size of a pixel' ' neighborhood that is used to calculate a threshold value for' ' the pixel. Example: 3, 5, 7, and so on.') @click.option('-const', '--threshold_constant', default=-2, help='For adaptive thresholding, constant subtracted' ' from the mean or weighted mean. Normally, it is positive but' ' may be zero or negative as well.') @click.option('-I', '--iterations', default=0, help='Number of times for erosion/dilation will be applied.') @click.option('-plot', '--plot_type', type=click.Choice(['text', 'grid', 'contour', 'joint', 'line']), help='Plot elements found on PDF page for visual debugging.') @click.argument('filepath', type=click.Path(exists=True)) @pass_config def lattice(c, *args, **kwargs): """Use lines between text to parse the table.""" conf = c.config pages = conf.pop('pages') output = conf.pop('output') f = conf.pop('format') compress = conf.pop('zip') suppress_warnings = conf.pop('quiet') plot_type = kwargs.pop('plot_type') filepath = kwargs.pop('filepath') kwargs.update(conf) table_areas = list(kwargs['table_areas']) kwargs['table_areas'] = None if not table_areas else table_areas copy_text = list(kwargs['copy_text']) kwargs['copy_text'] = None if not copy_text else copy_text kwargs['shift_text'] = list(kwargs['shift_text']) if plot_type is not None: if not _HAS_MPL: raise ImportError('matplotlib is required for plotting.') else: if output is None: raise click.UsageError('Please specify output file path using --output') if f is None: raise click.UsageError('Please specify output file format using --format') tables = read_pdf(filepath, pages=pages, flavor='lattice', suppress_warnings=suppress_warnings, **kwargs) click.echo('Found {} tables'.format(tables.n)) if plot_type is not None: for table in tables: plot(table, kind=plot_type) plt.show() else: tables.export(output, f=f, compress=compress) @cli.command('stream') @click.option('-T', '--table_areas', default=[], multiple=True, help='Table areas to process. Example: x1,y1,x2,y2' ' where x1, y1 -> left-top and x2, y2 -> right-bottom.') @click.option('-C', '--columns', default=[], multiple=True, help='X coordinates of column separators.') @click.option('-r', '--row_close_tol', default=2, help='Tolerance parameter' ' used to combine text vertically, to generate rows.') @click.option('-c', '--col_close_tol', default=0, help='Tolerance parameter' ' used to combine text horizontally, to generate columns.') @click.option('-plot', '--plot_type', type=click.Choice(['text', 'grid']), help='Plot elements found on PDF page for visual debugging.') @click.argument('filepath', type=click.Path(exists=True)) @pass_config def stream(c, *args, **kwargs): """Use spaces between text to parse the table.""" conf = c.config pages = conf.pop('pages') output = conf.pop('output') f = conf.pop('format') compress = conf.pop('zip') suppress_warnings = conf.pop('quiet') plot_type = kwargs.pop('plot_type') filepath = kwargs.pop('filepath') kwargs.update(conf) table_areas = list(kwargs['table_areas']) kwargs['table_areas'] = None if not table_areas else table_areas columns = list(kwargs['columns']) kwargs['columns'] = None if not columns else columns if plot_type is not None: if not _HAS_MPL: raise ImportError('matplotlib is required for plotting.') else: if output is None: raise click.UsageError('Please specify output file path using --output') if f is None: raise click.UsageError('Please specify output file format using --format') tables = read_pdf(filepath, pages=pages, flavor='stream', suppress_warnings=suppress_warnings, **kwargs) click.echo('Found {} tables'.format(tables.n)) if plot_type is not None: for table in tables: plot(table, kind=plot_type) plt.show() else: tables.export(output, f=f, compress=compress)