Repository navigation
Conversation
|
✅ DCO Check Passed Thanks @wittjeff, all your commits are properly signed off. 🎉 |
Merge Protections🔴 1 of 2 protections blocking · waiting on 👀 reviews
🔴 Require two reviewer for test updatesWaiting for
This rule is failing.When test data is updated, we require two reviewers
Show 1 satisfied protection🟢 Enforce conventional commitMake sure that we follow https://www.conventionalcommits.org/en/v1.0.0/
|
AI triage (advisory)Checked commit The PR adds opt-in PDF document metadata extraction ( CI safety: no concern found. A maintainer must still approve CI. 2 related pull request(s) in the same code
The |
There was a problem hiding this comment.
AI first review (advisory): no blocker found
This review covers the full diff at 32245ecccc. It read the code and did not run it.
The change adds opt-in PDF catalog language and document property extraction for both PDFium and docling-parse backends. Check that the custom property names on DoclingDocument.body.meta match downstream serializer expectations.
A maintainer makes the merge decision. This review never approves the pull request.
|
The docling-core side that gates the default is now open: docling-project/docling-core#869 (Markdown and plain text stop rendering |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
…e document A PDF's catalog `/Lang` entry and its document properties (the `/Info` dictionary and the XMP packet) never reached the `DoclingDocument`: docling-parse exposes them in its document annotations, but the pipeline read those only for the outline. Add `PdfDocumentBackend.get_document_metadata()` with an empty default and implement it for both PDF backends: pypdfium2 reads `/Lang` and `/Info`, docling-parse reads `/Lang` and the XMP packet. `/Info` keys are mapped to the XMP names ISO 32000-2 pairs them with, so both backends report the same vocabulary, and PDF date strings are converted to ISO 8601. The new `PdfPipelineOptions.attach_document_metadata` option (CLI: `--document-metadata`) records the result on `DoclingDocument.body.meta`, the document-level meta slot (docling-core#544): the language as `BaseMeta.language` with the primary subtag and `created_by="pdf:/Lang"`, the full BCP 47 tag as the `pdf__lang` custom field, and each property as a namespaced custom field such as `dc__title` or `pdf__producer`. The option is off by default because docling-core's Markdown and HTML serializers still render body meta fields as content (docling-core#745). Closes docling-project#4741 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Jeff Witt <1848307+wittjeff@users.noreply.github.com>
… stubs _build_document now reads the option to decide whether to fetch the document metadata, so the SimpleNamespace stubs must define it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Jeff Witt <1848307+wittjeff@users.noreply.github.com>
32245ec to
13cd676
Compare
There was a problem hiding this comment.
AI first review (advisory): blocker found
This review covers the full diff at 13cd6768a8. It read the code and did not run it.
An XMP packet that declares an entity or DOCTYPE makes the whole conversion fail when attach_document_metadata is on. The new extractor catches only ParseError. Check this error path first.
A maintainer makes the merge decision. This review never approves the pull request.
| without a text value, are skipped. An unparsable packet yields no properties. | ||
| """ | ||
| try: | ||
| root = _defused_fromstring(xml) |
There was a problem hiding this comment.
blocker: Hostile XMP packet fails the whole conversion
Trigger: the option is on, the backend is docling-parse, and the PDF has an XMP packet with an <!ENTITY ...> declaration, as in a billion-laughs payload. defusedxml forbids entities by default (forbid_entities=True). It raises EntitiesForbidden. DTDForbidden and ExternalReferenceForbidden exist too. All three derive from DefusedXmlException, which is a ValueError (defusedxml/common.py:18). They are not ElementTree.ParseError. The except ElementTree.ParseError block does not catch them.
Result: the error leaves get_document_metadata() and _build_document(). docling/pipeline/base_pipeline.py:110 catches it and sets ConversionStatus.FAILURE. The PDF's text and layout are lost because of a metadata property. The docstring says an unparsable packet yields no properties, and this case breaks that rule. Metadata is optional, and the input is untrusted PDF content.
Fix: also catch DefusedXmlException (from defusedxml.common) in parse_xmp_properties, log it, and return {}. Add a test with an XMP packet that has an entity declaration. The existing malformed-packet test covers only ParseError.
… the conversion parse_xmp_properties caught only ElementTree.ParseError. defusedxml raises EntitiesForbidden, DTDForbidden and ExternalReferenceForbidden (all DefusedXmlException, a ValueError) for a packet that declares entities or external references, so a hostile XMP packet escaped get_document_metadata() and the pipeline marked the whole conversion FAILURE when attach_document_metadata was on. Catch DefusedXmlException too, log a warning and return no properties, as the docstring already promised for an unusable packet. Adds a test with an entity-declaring packet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Jeff Witt <1848307+wittjeff@users.noreply.github.com>
Closes #4741. Addresses the reading side of #1826 (XMP metadata): the XMP packet is read on the docling-parse backend and
/Infoon the pypdfium2 backend, standard schemas only; custom namespaces, nestedxmpMMstructures, and metadata editing stay out of scope.Problem
A PDF's catalog
/Langentry and its document properties (/Infodictionary, XMP packet) never reach theDoclingDocument. docling-parse exposes them in its document annotations (language,meta_xml), but the pipeline reads those only for the outline, andPdfDocumentBackendhas no hook for document-level metadata.Change
PdfDocumentBackend.get_document_metadata()with an empty default, implemented for both PDF backends. pypdfium2 reads/Lang(FPDFCatalog_GetLanguage) and/Info; docling-parse reads/Langand the XMP packet./Infokeys are mapped to the XMP names ISO 32000-2 Table 349 pairs them with (Title->dc:title,Producer->pdf:Producer, ...), and PDF date strings become ISO 8601, so both backends report one vocabulary.PdfPipelineOptions.attach_document_metadata(CLI--document-metadata). When on, the pipeline fetches the metadata while the backend is open and records it onDoclingDocument.body.metaat assembly, the document-level meta slot per docling-core#544:body.meta.language = LanguageMetaField(code=<primary subtag>, created_by="pdf:/Lang"), so a declared language is distinguishable from a detected one;pdf__langcustom field with the full BCP 47 tag (en-US), becauseHumanLanguageLabelaccepts primary subtags only (docling-core#868);dc__title,dc__creator,pdf__producer,xmp__create_date,pdfuaid__part.docling/utils/pdf_metadata.pyfollows thepdf_outline.pypattern: lazy pypdfium2 import so theservice_clientchain stays importable on docling-slim.Why opt-in
docling-core's Markdown and HTML serializers still render body meta fields as content (docling-core#745): with the field set,
export_to_markdown()appendsconfidence=None created_by='pdf:/Lang' code=<HumanLanguageLabel.EN: 'en'>as a paragraph. The option stays off by default until that fix is released and docling bumps its docling-core floor; the JSON export is already correct.Tests
tests/test_pdf_document_metadata.py(core lane): XMP flattening (rdf:Altx-default,rdf:Seqjoin, attribute properties, unknown namespaces skipped, malformed packet), PDF date conversion, both backends on the new fixturetests/data/pdf/document_metadata.pdf(built with pypdf:/Lang,/Info, XMP) and on a plain PDF, and the attach semantics (existing meta kept, unknown primary subtag keeps onlypdf__lang, empty metadata leavesbody.metaunset).tests/test_pdf_document_metadata_pipeline.py(ml_pdf_model): end-to-end with the option on and off.make validatepasses;tests/test_heading_hierarchy_bookmarks.py,tests/test_cli.py,tests/test_options.py,tests/test_backend_optional_dependencies.pypass locally.Docs
docs/usage/advanced_options.mdsection "PDF document metadata"; the SDK and CLI references in the packaged usage skill.🤖 Generated with Claude Code