Skip to content

Commit

Permalink
ENH: Support qualified names in update_page_form_field_values (#1695)
Browse files Browse the repository at this point in the history
PDF forms often use names like "A.1", "A.2", "B.1", "B.2", … for the fields. However, the `.` has a special meaning, so this creates a hierarchy instead.

It was impossible to fill those individual fields with `update_page_form_field_values()`:

```python
update_page_form_field_values({"A": "foo"})  # fills all "A.*" fields
update_page_form_field_values({"1": "foo"})  # fills all "*.1" fields
update_page_form_field_values({"A.1": "foo"})  # fills none of the fields
```

This change makes `update_page_form_field_values()` to also check for qualified field names.

See also: #545
  • Loading branch information
xi authored Mar 14, 2023
1 parent 0afac1d commit 9878034
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 3 deletions.
29 changes: 26 additions & 3 deletions pypdf/_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,23 @@ def appendPagesFromReader(
)
self.append_pages_from_reader(reader, after_page_append)

def _get_qualified_field_name(self, parent: DictionaryObject) -> Optional[str]:
if "/TM" in parent:
return cast(str, parent["/TM"])
elif "/T" not in parent:
return None
elif "/Parent" in parent:
qualified_parent = self._get_qualified_field_name(
cast(DictionaryObject, parent["/Parent"])
)
if qualified_parent is not None:
return (
qualified_parent
+ "."
+ cast(str, parent["/T"])
)
return cast(str, parent["/T"])

def update_page_form_field_values(
self,
page: PageObject,
Expand Down Expand Up @@ -795,11 +812,14 @@ def update_page_form_field_values(
for j in range(len(page[PG.ANNOTS])): # type: ignore
writer_annot = page[PG.ANNOTS][j].get_object() # type: ignore
# retrieve parent field values, if present
writer_parent_annot = {} # fallback if it's not there
writer_parent_annot = DictionaryObject() # fallback if it's not there
if PG.PARENT in writer_annot:
writer_parent_annot = writer_annot[PG.PARENT]
for field in fields:
if writer_annot.get(FieldDictionaryAttributes.T) == field:
if (
writer_annot.get(FieldDictionaryAttributes.T) == field
or self._get_qualified_field_name(writer_annot) == field
):
if writer_annot.get(FieldDictionaryAttributes.FT) == "/Btn":
writer_annot.update(
{
Expand All @@ -823,7 +843,10 @@ def update_page_form_field_values(
)
}
)
elif writer_parent_annot.get(FieldDictionaryAttributes.T) == field:
elif (
writer_parent_annot.get(FieldDictionaryAttributes.T) == field
or self._get_qualified_field_name(writer_parent_annot) == field
):
writer_parent_annot.update(
{
NameObject(FieldDictionaryAttributes.V): TextStringObject(
Expand Down
18 changes: 18 additions & 0 deletions tests/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,24 @@ def test_fill_form():
Path(tmp_filename).unlink() # cleanup


def test_fill_form_with_qualified():
reader = PdfReader(RESOURCE_ROOT / "form.pdf")
reader.add_form_topname("top")

writer = PdfWriter()
writer.clone_document_from_reader(reader)
writer.add_page(reader.pages[0])
writer.update_page_form_field_values(
writer.pages[0], {"top.foo": "filling"}, flags=1
)
b = BytesIO()
writer.write(b)

reader2 = PdfReader(b)
fields = reader2.get_fields()
assert fields["top.foo"]["/V"] == "filling"


@pytest.mark.parametrize(
("use_128bit", "user_password", "owner_password"),
[(True, "userpwd", "ownerpwd"), (False, "userpwd", "ownerpwd")],
Expand Down

0 comments on commit 9878034

Please sign in to comment.