2020-08-06 22:19:40 -04:00
|
|
|
#!/usr/bin/env python
|
2021-12-09 22:27:51 -05:00
|
|
|
import logging
|
|
|
|
|
import os
|
2020-08-06 22:19:40 -04:00
|
|
|
import unittest
|
2021-12-09 22:27:51 -05:00
|
|
|
|
2020-08-06 22:19:40 -04:00
|
|
|
import pytest
|
2021-12-09 22:27:51 -05:00
|
|
|
|
2020-08-06 22:19:40 -04:00
|
|
|
import pdfplumber
|
|
|
|
|
from pdfplumber import table
|
2020-12-16 22:19:17 -05:00
|
|
|
|
2020-08-06 22:19:40 -04:00
|
|
|
logging.disable(logging.ERROR)
|
|
|
|
|
|
|
|
|
|
HERE = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
|
|
|
|
|
|
|
2020-12-16 22:19:17 -05:00
|
|
|
class Test(unittest.TestCase):
|
2020-08-06 22:19:40 -04:00
|
|
|
@classmethod
|
|
|
|
|
def setup_class(self):
|
|
|
|
|
path = os.path.join(HERE, "pdfs/pdffill-demo.pdf")
|
|
|
|
|
self.pdf = pdfplumber.open(path)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def teardown_class(self):
|
|
|
|
|
self.pdf.close()
|
|
|
|
|
|
|
|
|
|
def test_orientation_errors(self):
|
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
|
table.join_edge_group([], "x")
|
|
|
|
|
|
|
|
|
|
def test_table_settings_errors(self):
|
|
|
|
|
with pytest.raises(ValueError):
|
2022-05-06 09:43:50 -04:00
|
|
|
tf = table.TableFinder(self.pdf.pages[0], tuple())
|
|
|
|
|
|
|
|
|
|
with pytest.raises(TypeError):
|
2020-12-16 22:19:17 -05:00
|
|
|
tf = table.TableFinder(self.pdf.pages[0], {"strategy": "x"})
|
2020-12-16 22:29:51 -05:00
|
|
|
tf.get_edges()
|
2020-08-06 22:19:40 -04:00
|
|
|
|
|
|
|
|
with pytest.raises(ValueError):
|
2020-12-16 22:19:17 -05:00
|
|
|
tf = table.TableFinder(self.pdf.pages[0], {"vertical_strategy": "x"})
|
2020-08-06 22:19:40 -04:00
|
|
|
|
|
|
|
|
with pytest.raises(ValueError):
|
2020-12-16 22:19:17 -05:00
|
|
|
tf = table.TableFinder(
|
|
|
|
|
self.pdf.pages[0],
|
|
|
|
|
{
|
|
|
|
|
"vertical_strategy": "explicit",
|
|
|
|
|
"explicit_vertical_lines": [],
|
|
|
|
|
},
|
|
|
|
|
)
|
2020-08-06 22:19:40 -04:00
|
|
|
|
2021-12-03 17:46:59 -05:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
|
tf = table.TableFinder(self.pdf.pages[0], {"join_tolerance": -1})
|
|
|
|
|
tf.get_edges()
|
|
|
|
|
|
2020-08-06 22:19:40 -04:00
|
|
|
def test_edges_strict(self):
|
|
|
|
|
path = os.path.join(HERE, "pdfs/issue-140-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
2020-12-16 22:19:17 -05:00
|
|
|
t = pdf.pages[0].extract_table(
|
|
|
|
|
{
|
|
|
|
|
"vertical_strategy": "lines_strict",
|
|
|
|
|
"horizontal_strategy": "lines_strict",
|
|
|
|
|
}
|
|
|
|
|
)
|
2020-08-06 22:19:40 -04:00
|
|
|
|
|
|
|
|
assert t[-1] == [
|
|
|
|
|
"",
|
|
|
|
|
"0085648100300",
|
|
|
|
|
"CENTRAL KMA",
|
|
|
|
|
"LILYS 55% DARK CHOC BAR",
|
|
|
|
|
"415",
|
|
|
|
|
"$ 0.61",
|
|
|
|
|
"$ 253.15",
|
|
|
|
|
"0.0000",
|
2020-12-16 22:19:17 -05:00
|
|
|
"",
|
2020-08-06 22:19:40 -04:00
|
|
|
]
|
|
|
|
|
|
2024-08-02 19:00:23 -04:00
|
|
|
def test_rows_and_columns(self):
|
|
|
|
|
path = os.path.join(HERE, "pdfs/issue-140-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
page = pdf.pages[0]
|
|
|
|
|
table = page.find_table()
|
|
|
|
|
row = [page.crop(bbox).extract_text() for bbox in table.rows[0].cells]
|
|
|
|
|
assert row == [
|
|
|
|
|
"Line no",
|
|
|
|
|
"UPC code",
|
|
|
|
|
"Location",
|
|
|
|
|
"Item Description",
|
|
|
|
|
"Item Quantity",
|
|
|
|
|
"Bill Amount",
|
|
|
|
|
"Accrued Amount",
|
|
|
|
|
"Handling Rate",
|
|
|
|
|
"PO number",
|
|
|
|
|
]
|
|
|
|
|
col = [page.crop(bbox).extract_text() for bbox in table.columns[1].cells]
|
|
|
|
|
assert col == [
|
|
|
|
|
"UPC code",
|
|
|
|
|
"0085648100305",
|
|
|
|
|
"0085648100380",
|
|
|
|
|
"0085648100303",
|
|
|
|
|
"0085648100300",
|
|
|
|
|
]
|
|
|
|
|
|
2020-10-20 09:32:06 -04:00
|
|
|
def test_explicit_desc_decimalization(self):
|
|
|
|
|
"""
|
|
|
|
|
See issue #290
|
|
|
|
|
"""
|
2020-12-16 22:19:17 -05:00
|
|
|
tf = table.TableFinder(
|
|
|
|
|
self.pdf.pages[0],
|
|
|
|
|
{
|
|
|
|
|
"vertical_strategy": "explicit",
|
|
|
|
|
"explicit_vertical_lines": [100, 200, 300],
|
|
|
|
|
"horizontal_strategy": "explicit",
|
|
|
|
|
"explicit_horizontal_lines": [100, 200, 300],
|
|
|
|
|
},
|
|
|
|
|
)
|
2020-10-20 09:32:06 -04:00
|
|
|
assert tf.tables[0].extract()
|
|
|
|
|
|
2021-05-08 17:42:12 -04:00
|
|
|
def test_text_tolerance(self):
|
|
|
|
|
path = os.path.join(HERE, "pdfs/senate-expenditures.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
bbox = (70.332, 130.986, 420, 509.106)
|
|
|
|
|
cropped = pdf.pages[0].crop(bbox)
|
|
|
|
|
t = cropped.extract_table(
|
|
|
|
|
{
|
|
|
|
|
"horizontal_strategy": "text",
|
|
|
|
|
"vertical_strategy": "text",
|
|
|
|
|
"min_words_vertical": 20,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
t_tol = cropped.extract_table(
|
|
|
|
|
{
|
|
|
|
|
"horizontal_strategy": "text",
|
|
|
|
|
"vertical_strategy": "text",
|
|
|
|
|
"min_words_vertical": 20,
|
|
|
|
|
"text_x_tolerance": 1,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
t_tol_from_tables = cropped.extract_tables(
|
|
|
|
|
{
|
|
|
|
|
"horizontal_strategy": "text",
|
|
|
|
|
"vertical_strategy": "text",
|
|
|
|
|
"min_words_vertical": 20,
|
|
|
|
|
"text_x_tolerance": 1,
|
|
|
|
|
}
|
|
|
|
|
)[0]
|
|
|
|
|
|
|
|
|
|
assert t[-1] == [
|
|
|
|
|
"DHAW20190070",
|
|
|
|
|
"09/09/2019",
|
|
|
|
|
"CITIBANK-TRAVELCBACARD",
|
|
|
|
|
"08/12/2019",
|
|
|
|
|
"08/14/2019",
|
|
|
|
|
]
|
|
|
|
|
assert t_tol[-1] == [
|
|
|
|
|
"DHAW20190070",
|
|
|
|
|
"09/09/2019",
|
|
|
|
|
"CITIBANK - TRAVEL CBA CARD",
|
|
|
|
|
"08/12/2019",
|
|
|
|
|
"08/14/2019",
|
|
|
|
|
]
|
|
|
|
|
assert t_tol[-1] == t_tol_from_tables[-1]
|
|
|
|
|
|
2023-02-13 17:41:47 -05:00
|
|
|
def test_text_layout(self):
|
|
|
|
|
path = os.path.join(HERE, "pdfs/issue-53-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
table = pdf.pages[0].extract_table(
|
|
|
|
|
{
|
|
|
|
|
"text_layout": True,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
assert table[3][0] == " FY2013 \n FY2014 "
|
|
|
|
|
|
2020-08-06 22:19:40 -04:00
|
|
|
def test_text_without_words(self):
|
|
|
|
|
assert table.words_to_edges_h([]) == []
|
|
|
|
|
assert table.words_to_edges_v([]) == []
|
2021-01-19 15:30:47 +05:30
|
|
|
|
|
|
|
|
def test_order(self):
|
|
|
|
|
"""
|
|
|
|
|
See issue #336
|
|
|
|
|
"""
|
|
|
|
|
path = os.path.join(HERE, "pdfs/issue-336-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
tables = pdf.pages[0].extract_tables()
|
|
|
|
|
assert len(tables) == 3
|
|
|
|
|
assert len(tables[0]) == 8
|
|
|
|
|
assert len(tables[1]) == 11
|
|
|
|
|
assert len(tables[2]) == 2
|
2021-07-11 23:59:27 +02:00
|
|
|
|
|
|
|
|
def test_issue_466_mixed_strategy(self):
|
|
|
|
|
"""
|
|
|
|
|
See issue #466
|
|
|
|
|
"""
|
|
|
|
|
path = os.path.join(HERE, "pdfs/issue-466-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
tables = pdf.pages[0].extract_tables(
|
|
|
|
|
{
|
|
|
|
|
"vertical_strategy": "lines",
|
|
|
|
|
"horizontal_strategy": "text",
|
|
|
|
|
"snap_tolerance": 8,
|
|
|
|
|
"intersection_tolerance": 4,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# The engine only extracts the tables which have drawn horizontal
|
|
|
|
|
# lines.
|
|
|
|
|
# For the 3 extracted tables, some common properties are expected:
|
|
|
|
|
# - 4 rows
|
|
|
|
|
# - 3 columns
|
|
|
|
|
# - Data in last row contains the string 'last'
|
|
|
|
|
for t in tables:
|
|
|
|
|
assert len(t) == 4
|
|
|
|
|
assert len(t[0]) == 3
|
|
|
|
|
|
|
|
|
|
# Verify that all cell contain real data
|
|
|
|
|
for cell in t[3]:
|
|
|
|
|
assert "last" in cell
|
2021-11-22 19:48:14 +05:30
|
|
|
|
|
|
|
|
def test_discussion_539_null_value(self):
|
|
|
|
|
"""
|
|
|
|
|
See discussion #539
|
|
|
|
|
"""
|
|
|
|
|
path = os.path.join(HERE, "pdfs/nics-background-checks-2015-11.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
page = pdf.pages[0]
|
|
|
|
|
table_settings = {
|
|
|
|
|
"vertical_strategy": "lines",
|
|
|
|
|
"horizontal_strategy": "lines",
|
|
|
|
|
"explicit_vertical_lines": [],
|
|
|
|
|
"explicit_horizontal_lines": [],
|
|
|
|
|
"snap_tolerance": 3,
|
|
|
|
|
"join_tolerance": 3,
|
|
|
|
|
"edge_min_length": 3,
|
|
|
|
|
"min_words_vertical": 3,
|
|
|
|
|
"min_words_horizontal": 1,
|
2023-02-13 17:41:47 -05:00
|
|
|
"text_keep_blank_chars": False,
|
2021-11-22 19:48:14 +05:30
|
|
|
"text_tolerance": 3,
|
|
|
|
|
"intersection_tolerance": 3,
|
|
|
|
|
}
|
|
|
|
|
assert page.extract_table(table_settings)
|
|
|
|
|
assert page.extract_tables(table_settings)
|
2023-04-12 18:40:59 -04:00
|
|
|
|
|
|
|
|
def test_table_curves(self):
|
|
|
|
|
# See https://github.com/jsvine/pdfplumber/discussions/808
|
|
|
|
|
path = os.path.join(HERE, "pdfs/table-curves-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
page = pdf.pages[0]
|
|
|
|
|
assert len(page.curves)
|
|
|
|
|
tables = page.extract_tables()
|
|
|
|
|
assert len(tables) == 1
|
|
|
|
|
t = tables[0]
|
|
|
|
|
assert t[-2][-2] == "Uncommon"
|
|
|
|
|
|
|
|
|
|
assert len(page.extract_tables({"vertical_strategy": "lines_strict"})) == 0
|