mirror of
https://github.com/jsvine/pdfplumber.git
synced 2026-08-30 00:50:24 +08:00
Fix bug that crashed table extraction when null value provided for (text|intersection)_(x|y)_tolerance keys
Fixes #539 Thanks to @yoavxyoav for reporting
This commit is contained in:
@@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file. The format
|
||||
### Fixed
|
||||
- Fix slowdown in `.extract_words(...)`/`WordExtractor.iter_chars_to_words(...)` on very long words, caused by repeatedly re-calculating bounding box. ([#483](https://github.com/jsvine/pdfplumber/discussions/483))
|
||||
- Handle `UnicodeDecodeError` when trying to decode utf-16-encoded annotations ([#463](https://github.com/jsvine/pdfplumber/issues/463)) [h/t @tungph]
|
||||
- Fix crash when extracting tables with null values in `(text|intersection)_(x|y)_tolerance` settings. ([#539](https://github.com/jsvine/pdfplumber/discussions/539)) [h/t @yoavxyoav]
|
||||
|
||||
### Development Changes
|
||||
- Add `CONTRIBUTING.md` ([#428](https://github.com/jsvine/pdfplumber/pull/428))
|
||||
|
||||
@@ -219,6 +219,7 @@ class Page(Container):
|
||||
return TableFinder(self, table_settings).tables
|
||||
|
||||
def extract_tables(self, table_settings={}):
|
||||
table_settings = TableFinder.resolve_table_settings(table_settings)
|
||||
tables = self.find_tables(table_settings)
|
||||
|
||||
extract_kwargs = dict(
|
||||
@@ -230,6 +231,7 @@ class Page(Container):
|
||||
return [table.extract(**extract_kwargs) for table in tables]
|
||||
|
||||
def extract_table(self, table_settings={}):
|
||||
table_settings = TableFinder.resolve_table_settings(table_settings)
|
||||
tables = self.find_tables(table_settings)
|
||||
|
||||
if len(tables) == 0:
|
||||
|
||||
+35
-13
@@ -438,20 +438,8 @@ class TableFinder(object):
|
||||
"""
|
||||
|
||||
def __init__(self, page, settings={}):
|
||||
for k in settings.keys():
|
||||
if k not in DEFAULT_TABLE_SETTINGS:
|
||||
raise ValueError(f"Unrecognized table setting: '{k}'")
|
||||
self.page = page
|
||||
self.settings = dict(DEFAULT_TABLE_SETTINGS)
|
||||
self.settings.update(settings)
|
||||
for var, fallback in [
|
||||
("text_x_tolerance", "text_tolerance"),
|
||||
("text_y_tolerance", "text_tolerance"),
|
||||
("intersection_x_tolerance", "intersection_tolerance"),
|
||||
("intersection_y_tolerance", "intersection_tolerance"),
|
||||
]:
|
||||
if self.settings[var] is None:
|
||||
self.settings.update({var: self.settings[fallback]})
|
||||
self.settings = self.resolve_table_settings(settings)
|
||||
self.edges = self.get_edges()
|
||||
self.intersections = edges_to_intersections(
|
||||
self.edges,
|
||||
@@ -461,6 +449,40 @@ class TableFinder(object):
|
||||
self.cells = intersections_to_cells(self.intersections)
|
||||
self.tables = [Table(self.page, t) for t in cells_to_tables(self.cells)]
|
||||
|
||||
@staticmethod
|
||||
def resolve_table_settings(table_settings={}):
|
||||
"""Clean up user-provided table settings.
|
||||
|
||||
Validates that the table settings provided consists of acceptable keys and
|
||||
returns a cleaned up version. The cleaned up version fills out the missing
|
||||
values with the default values in the provided settings.
|
||||
|
||||
TODO: Can be further used to validate that the values are of the correct
|
||||
type. For example, raising a value error when a non-boolean input is
|
||||
provided for the key ``keep_blank_chars``.
|
||||
|
||||
:param table_settings: User-provided table settings.
|
||||
:returns: A cleaned up version of the user-provided table settings.
|
||||
:raises ValueError: When an unrecognised key is provided.
|
||||
"""
|
||||
for k in table_settings.keys():
|
||||
if k not in DEFAULT_TABLE_SETTINGS:
|
||||
raise ValueError(f"Unrecognized table setting: '{k}'")
|
||||
|
||||
resolved_table_settings = dict(DEFAULT_TABLE_SETTINGS)
|
||||
resolved_table_settings.update(table_settings)
|
||||
|
||||
for var, fallback in [
|
||||
("text_x_tolerance", "text_tolerance"),
|
||||
("text_y_tolerance", "text_tolerance"),
|
||||
("intersection_x_tolerance", "intersection_tolerance"),
|
||||
("intersection_y_tolerance", "intersection_tolerance"),
|
||||
]:
|
||||
if resolved_table_settings[var] is None:
|
||||
resolved_table_settings.update({var: resolved_table_settings[fallback]})
|
||||
|
||||
return resolved_table_settings
|
||||
|
||||
def get_edges(self):
|
||||
settings = self.settings
|
||||
for name in ["vertical", "horizontal"]:
|
||||
|
||||
@@ -170,3 +170,31 @@ class Test(unittest.TestCase):
|
||||
# Verify that all cell contain real data
|
||||
for cell in t[3]:
|
||||
assert "last" in cell
|
||||
|
||||
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,
|
||||
"keep_blank_chars": False,
|
||||
"text_tolerance": 3,
|
||||
"text_x_tolerance": None,
|
||||
"text_y_tolerance": None,
|
||||
"intersection_tolerance": 3,
|
||||
"intersection_x_tolerance": None,
|
||||
"intersection_y_tolerance": None,
|
||||
}
|
||||
assert page.extract_table(table_settings)
|
||||
assert page.extract_tables(table_settings)
|
||||
|
||||
Reference in New Issue
Block a user