Repository navigation
Open
Labels
Fixed in next releaseHas a testbugfix developedrelease schedule to be determinedrelease schedule to be determined
Description
Description of the bug
Description of the bug *
Table.to_pandas() exports an extracted table as a pandas DataFrame. To handle duplicate or missing column names, it modifies names:
1888: hdr = self.header
1889: names = self.header.names
1890: hdr_len = len(names)
1891: # ensure uniqueness of column names
1892: for i in range(hdr_len):
1893: name = names[i]
1894: if not name:
1895: names[i] = f"Col{i}"
1896: if hdr_len != len(set(names)):
1897: for i in range(hdr_len):
1898: name = names[i]
1899: if name != f"Col{i}":
1900: names[i] = f"{i}-{name}"In Python, names = self.header.names binds names directly to the TableHeader object's internal list self.header.names.
Mutating names[i] on lines 1895 and 1900 permanently alters the table's internal header state:
- If duplicate headers exist (e.g.
['Total', 'Total']), callingtab.to_pandas()permanently mutatestab.header.namesto['0-Total', '1-Total']. - Subsequent calls to other export methods (such as
tab.to_markdown()) or accessingtab.header.namesoutput the prefixed strings instead of the original extracted text. - An export/read operation should never produce destructive mutations on the instance.
How to reproduce the bug
Highlight in Code
src/table.py:1888-1901:
1888: hdr = self.header
1889: >>>>> names = self.header.names # BUG: Direct reference to internal list!
1890: hdr_len = len(names)
1891: # ensure uniqueness of column names
1892: for i in range(hdr_len):
1893: name = names[i]
1894: if not name:
1895: >>>>> names[i] = f"Col{i}" # BUG: Mutates self.header.names in-place!
1896: if hdr_len != len(set(names)):
1897: for i in range(hdr_len):
1898: name = names[i]
1899: if name != f"Col{i}":
1900: >>>>> names[i] = f"{i}-{name}" # BUG: Mutates self.header.names in-place!Steps & Reproduction Code
import pymupdf
doc = pymupdf.open()
page = doc.new_page()
page.draw_rect([50, 50, 200, 100])
page.draw_line([50, 75], [200, 75])
page.draw_line([125, 50], [125, 100])
page.insert_text([60, 65], "Total")
page.insert_text([135, 65], "Total")
page.insert_text([60, 90], "10")
page.insert_text([135, 90], "20")
tabs = page.find_tables()
tab = tabs[0]
print("Initial header names:", tab.header.names)
# ['Total', 'Total']
tab.to_pandas()
print("Header names after to_pandas():", tab.header.names)
# ['0-Total', '1-Total'] (mutated in-place)
# Downstream export is corrupted:
print("Markdown output:\n", tab.to_markdown())
# Header row shows |0-Total|1-Total| instead of |Total|Total|Expected Behavior
Table.to_pandas() should operate on a shallow copy of self.header.names (e.g. names = list(self.header.names)), leaving self.header.names completely unmodified.
Actual Behavior
self.header.names is mutated in-place and permanently alters table metadata.
Suggested Fix
--- a/src/table.py
+++ b/src/table.py
@@ -1886,7 +1886,7 @@ class Table:
pd_dict = {}
extract = self.extract()
hdr = self.header
- names = self.header.names
+ names = list(self.header.names)
hdr_len = len(names)
# ensure uniqueness of column names
for i in range(hdr_len):PyMuPDF version
2.1
Operating system
Windows
Python version
3.14
Metadata
Metadata
Assignees
Labels
Fixed in next releaseHas a testbugfix developedrelease schedule to be determinedrelease schedule to be determined
Activity
JorjMcKie commentedon Nov 2, 2026
Thanks for your submission. We will include this fix in one of the next versions.
Fix #5241