Source code for klea_utils.biblio.docling

#!/usr/bin/env python3
"""
Docling-based bibliographic metadata extraction

File: klea_utils/biblio/docling.py

Copyright 2026 Ankur Sinha
Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
"""

import logging
from pathlib import Path

logger = logging.getLogger(__name__)


[docs] def extract_docling_structured(dl_doc, file_path: str) -> dict: """Extract the free, reliably-structured bibliographic signals from a Docling document. These come straight from Docling's layout model and document origin, so they do not depend on regex heuristics: - ``title`` -- the first ``TitleItem``'s text, falling back to the source file's stem - ``source_type`` -- the document's ``origin.mimetype`` - ``source_url`` -- the ``origin.uri`` when it is an ``http(s)`` URL (web-sourced inputs only) - ``urls`` -- deduplicated ``http(s)`` hyperlink URLs found on any text item (from markdown/HTML links) :param dl_doc: A Docling ``DoclingDocument`` :param file_path: Path of the source file (for the title fallback) :returns: Dict with any of ``title``, ``source_type``, ``source_url``, ``urls`` """ result: dict = {} result["title"] = _first_title_text(dl_doc) or Path(file_path).stem origin = getattr(dl_doc, "origin", None) if origin is not None: mimetype = getattr(origin, "mimetype", None) if mimetype: result["source_type"] = mimetype uri = getattr(origin, "uri", None) if uri is not None: uri_str = str(uri) if uri_str.startswith(("http://", "https://")): result["source_url"] = uri_str urls = _hyperlink_urls(dl_doc) if urls: result["urls"] = urls logger.debug(f"docling structured signals: {result}") return result
[docs] def extract_layout_region(dl_doc, page: int = 1, frac: float = 0.35) -> str | None: """Return the text of the top *frac* of *page* as a single string. Filters the document's text items to those whose bounding box starts within the top *frac* of *page* (TOPLEFT origin) and joins their text. The first-page header is where authors, keywords and the DOI live, so this gives the regex tier a focused region to scan rather than the whole document. :param dl_doc: A Docling ``DoclingDocument`` :param page: Page number to inspect :param frac: Fraction of the page height that counts as the header :returns: Joined region text, or ``None`` when there is nothing in the region """ page_item = dl_doc.pages.get(page) if page_item is None: return None height = page_item.size.height region_texts = [] for item in dl_doc.texts: if not item.prov: continue prov = item.prov[0] if prov.page_no != page: continue if prov.bbox.t <= frac * height: region_texts.append(item.text) if not region_texts: return None region_text = "\n".join(region_texts) logger.debug( f"layout region (page {page}, top {frac:.0%}): " f"{len(region_text)} characters from {len(region_texts)} items" ) return region_text
def _first_title_text(dl_doc) -> str | None: """Return the first title item's text, or ``None`` if there is none.""" # Lazy: importing docling_core pulls in the pydantic document models. # Only needed when a Docling document is being inspected. from docling_core.types.doc.document import DocItemLabel for item in dl_doc.texts: if item.label == DocItemLabel.TITLE and item.text.strip(): return item.text.strip() return None def _hyperlink_urls(dl_doc) -> list[str]: """Return deduplicated ``http(s)`` hyperlink URLs from text items.""" urls: list[str] = [] seen: set[str] = set() for item in dl_doc.texts: hyperlink = getattr(item, "hyperlink", None) if hyperlink is None: continue value = str(hyperlink) if not value.startswith(("http://", "https://")): continue if value not in seen: seen.add(value) urls.append(value) return urls