Bibliography Extraction and Parsing#
Docling extracts bibliographic references from three source types — JATS/XML, LaTeX, and PDF — each with a distinct backend path and its own quality considerations.
JATS/XML#
The JatsDocumentBackend handles JATS-tagged scientific articles from PubMed Central, bioRxiv, medRxiv, and Springer Nature. Reference lists live in the <back> subtree and are walked by _walk_linear().
How references are parsed:
<ref-list>— creates aReferencesheading (or uses the<title>child if present) followed by aGroupLabel.LISTgroup .<element-citation>— parsed field-by-field by_parse_element_citation(), extractingauthor_names,title,source,year,volume,page,pub_id(DOI, PMID, etc.),publisher_name, andpublisher_loc, then serialized to a flat string.<mixed-citation>— extracted as raw text via_get_text()without structured field parsing .
Author names are assembled from <name>, <string-name>, <collab>, name-alternatives, and <anonymous> elements via _parse_contrib_name(). An <etal> node appends "et al." . Citations are added as DocItemLabel.TEXT items or list items depending on the parent context .
LaTeX#
The LaTeX backend handles bibliography via the thebibliography environment in environments.py. When a \thebibliography environment is encountered, a "References" heading is emitted and _process_bibliography() is called.
_process_bibliography() iterates the environment's node list, splitting on \bibitem macros. Each \bibitem key becomes a [key] prefix emitted as a DocItemLabel.LIST_ITEM, and the remaining nodes for that entry are processed through _process_nodes() as additional LIST_ITEM content within a GroupLabel.LIST group .
PDF#
PDF bibliography extraction is handled by the docling_parse C++ backend and the page assembly pipeline. Unlike structured formats, references are extracted as text flow without structural citation awareness.
Known Quality Issues#
1. TableFormer V2 duplicates multi-page reference tables
When a bibliography is typeset as a multi-page table (common in some journal templates), TableFormer V2 (TableStructureV2Options) produces dozens of overlapping, sliding-window duplicate rows — expanding a ~7k-word document to ~42k words in one reported case. The root cause is V2's autoregressive decoder drifting after roughly 190 cells on long, low-entropy table content . V1 (TableStructureOptions) handles these tables cleanly.
Workaround: Use TableStructureOptions (V1) for documents with multi-page bibliography tables.
2. URL character-by-character spacing
Some PDFs store URLs in reference sections using per-glyph Tj operators (one character per operator), causing docling-parse to emit them as space-separated characters: h t t p s : / / d o i . o r g / .... The Python-side sanitize_text() has no URL-aware collapse logic; the fix must happen at the docling-parse level .
3. Metadata field leakage into references
PDF metadata field names (WGROUP, STRING, PUBLICATION, PAGE, etc.) can appear as spaced-character text sequences inside extracted reference sections. The backend (docling_parse_backend.py) consumes all textline_cells from docling-parse without filtering /Artifact marked-content, allowing structural metadata to bleed into the output .
4. Character corruption in author names
docling_parse ignores the PDF /ActualText marked-content property and falls back to raw byte-decoding for glyphs with no ToUnicode mapping. Composite accent glyphs (e.g., a zero-advance caron with code 0x4D) decode as M instead of the correct accented character, corrupting author names silently — e.g., Orešič → OresMi . A proposed fix (docling-parse PR #300) implements /ActualText marked-content replacement per PDF 32000-1 §14.9.4.