Skip to content

fix(serializer): keep meta.language out of text exports and emit lang attributes in HTML - #869

Open
wittjeff wants to merge 1 commit into
docling-project:mainfrom
wittjeff:fix/meta-language-serialization
Open

wittjeff wants to merge 1 commit into
docling-project:mainfrom
wittjeff:fix/meta-language-serialization

Conversation

@wittjeff

@wittjeff wittjeff commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Closes #745. Related: #868 (full BCP 47 tag on LanguageMetaField), docling-project/docling#4741 and docling-project/docling#4781 (PDF /Lang attached to body.meta, opt-in until this lands).

Problem

BaseMeta.language is metadata about the text, but once set it is treated as content:

  1. Markdown and plain text print the field's pydantic repr (MarkdownMetaSerializer._serialize_meta_field has no LanguageMetaField branch and falls through to str(field_val)), so the document gains a paragraph such as confidence=0.99 created_by='langdetect' code=<HumanLanguageLabel.DE: 'de'>. EntitiesMetaField leaks the same way.
  2. HTML has no lang attribute on the elements that carry the text.
  3. HTMLParams.html_lang is declared but never emitted; the root is the hard-coded <html>.

Change

Markdown / plain text

  • LanguageMetaField and EntitiesMetaField are skipped unless mark_meta=True, where they render as [Language] de and [Entities] IBM, Zurich.
  • Any other structured field (a BaseModel) without a renderer is skipped instead of printing its repr. Custom scalar fields keep rendering as before.
  • CodeMetaField (PictureMeta.code, e.g. chart-to-code output) renders like a code item: a fenced block with the language as the info string, or the bare text when format_code_blocks=False (plain text). A marked export puts [Code] on the line above the fence. Before, this field printed as a pydantic repr; it is the one standard field the catch-all would otherwise have dropped.

HTML

  • Root element is <html lang="...">. html_lang (params and export_to_html) now defaults to None, resolved by resolve_document_language(): doc.body.meta.language, else the character-weighted majority of the text items' meta.language, else "en". The resolution happens once in HTMLDocSerializer.model_post_init, so every element serializer compares against the same page language.
  • <p>, <h1>..<h6> and <li> get lang="<code>" when the item's meta.language differs from the page language, compared by primary subtag (en-US vs en is the same language). This is WCAG 3.1.1 (page) and 3.1.2 (parts).
  • The language row in the <details class="docling-meta"> block is unchanged: existing tests pin it, and it does no harm.

Ground truth

All .gt.html / .html.gt files are regenerated; the only change in each is <html> -> <html lang="en">. (The regeneration also rewrote the visualization PNGs through local font drift; those are reverted and not part of this PR.)

Tests

tests/test_metadata.py: Markdown and plain text exclude language and entities by default and list them with mark_meta=True; a structured custom field without a renderer is skipped while a scalar one is kept; a picture's code field renders fenced (Markdown), labelled (marked), and bare (plain text); HTML lang attributes with the majority default, an explicit html_lang="en-US", and a body-level language; <html lang="en"> without any language metadata. Full suite and pre-commit (ruff, mypy, pytest, docs) pass.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ DCO Check Passed

Thanks @wittjeff, all your commits are properly signed off. 🎉

@mergify

mergify Bot commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🔴 1 of 2 protections blocking · waiting on 👀 reviews

Protection Waiting on
🔴 Require two reviewer for test updates 👀 reviews
🟢 Enforce conventional commit —

🔴 Require two reviewer for test updates

Waiting for

  • #approved-reviews-by >= 2
This rule is failing.

When test data is updated, we require two reviewers

  • #approved-reviews-by >= 2

Show 1 satisfied protection

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\(.+\))?(!)?:

@github-actions github-actions Bot added ai:ci-safe AI triage found no CI-safety concern (advisory) ai:possible-duplicate AI triage: another PR may fix the same problem bug Something isn't working document-model DoclingDocument core model, metadata, hierarchy, validation serialization Output serializers: Markdown, HTML, LaTeX, JSON, plain-text, outline labels Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

AI triage (advisory)

Checked commit 8837555540. The analysis read the diff only. It did not run code from this pull request.

This PR omits LanguageMetaField and EntitiesMetaField from plain text and Markdown exports unless metadata marking is enabled. It also adds HTML lang attributes to the root <html> element and to child elements that differ from the document language.

CI safety: no concern found. A maintainer must still approve CI.

Possible duplicates

PR State Signal Assessment
#746 open also references issue #745 duplicate: PR #746 fixes the same issue by omitting language metadata from Markdown exports and adding HTML lang attributes.

Reference data changes

Change Files
text 24
  • text tests/data/doc/2206.01062.yaml.html: +1 / -1 lines
  • text tests/data/doc/2408.09869_p1_split.gt.html: +1 / -1 lines
  • text tests/data/doc/2408.09869v3_enriched_p1_include_annotations_false.gt.html: +1 / -1 lines
  • text tests/data/doc/2408.09869v3_enriched_p1_include_annotations_true.gt.html: +1 / -1 lines
  • text tests/data/doc/2408.09869v3_enriched_p2_p3_p5.gt.html: +1 / -1 lines
  • text tests/data/doc/2408.09869v3_enriched_split.gt.html: +1 / -1 lines
  • text tests/data/doc/2408.09869v3_enriched_split_p2.gt.html: +1 / -1 lines
  • text tests/data/doc/activities.gt.html: +1 / -1 lines
  • text tests/data/doc/activities_p1.gt.html: +1 / -1 lines
  • text tests/data/doc/activities_p2.gt.html: +1 / -1 lines
  • text tests/data/doc/barchart.gt.html: +1 / -1 lines
  • text tests/data/doc/concatenated.html: +1 / -1 lines
  • text tests/data/doc/constructed_doc.embedded.html.gt: +1 / -1 lines
  • text tests/data/doc/constructed_doc.placeholder.html.gt: +1 / -1 lines
  • text tests/data/doc/constructed_doc.referenced.html.gt: +1 / -1 lines
  • text tests/data/doc/constructed_document.yaml.html: +1 / -1 lines
  • text tests/data/doc/constructed_orig_false.gt.html: +1 / -1 lines
  • text tests/data/doc/constructed_orig_true.gt.html: +1 / -1 lines
  • text tests/data/doc/dummy_doc.yaml.html: +1 / -1 lines
  • text tests/data/doc/inline_and_formatting.gt.html: +1 / -1 lines
  • text tests/data/doc/polymers.gt.html: +1 / -1 lines
  • text tests/data/doc/rich_table.gt.html: +1 / -1 lines
  • text tests/data/doc/table_footnotes_496.gt.html: +1 / -1 lines
  • text tests/data/docling_document/export/formula_mathml.html: +1 / -1 lines

Assessment: expected. The HTML reference files now contain <html lang="en"> instead of <html>.

The ai:* labels are advisory. They do not approve CI and they do not add tests:full. A new push runs this check again.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI first review (advisory): no blocker found

This review covers the full diff at da71365027. It read the code and did not run it.

The Markdown change is correct for language and entities. The new catch-all BaseModel branch also drops a standard field, CodeMetaField, so that field can no longer reach Markdown or plain-text output. Decide whether that content loss is intended.

A maintainer makes the merge decision. This review never approves the pull request.

@github-actions github-actions Bot added the ai:review-lgtm AI first review found no blocker or major issue (advisory) label Nov 3, 2026
@wittjeff
wittjeff force-pushed the fix/meta-language-serialization branch from da71365 to 0d7e51f Compare November 3, 2026 18:17
@github-actions github-actions Bot added ai:ci-safe AI triage found no CI-safety concern (advisory) ai:possible-duplicate AI triage: another PR may fix the same problem and removed ai:ci-safe AI triage found no CI-safety concern (advisory) ai:possible-duplicate AI triage: another PR may fix the same problem ai:review-lgtm AI first review found no blocker or major issue (advisory) labels Nov 3, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI first review (advisory): no blocker found

This review covers the full diff at 0d7e51f5b8. It read the code and did not run it.

The language and entity skip in Markdown and the HTML lang attributes look correct. The new CodeMetaField branch has a fixed three-backtick fence, which can break the Markdown of the whole document. Check that branch against the existing _code_block helper.

Earlier findings

  • fixed (question): Catch-all BaseModel skip removes PictureMeta.code text from Markdown and plain text

A maintainer makes the merge decision. This review never approves the pull request.

@github-actions github-actions Bot added ai:review-lgtm AI first review found no blocker or major issue (advisory) and removed ai:ci-safe AI triage found no CI-safety concern (advisory) ai:possible-duplicate AI triage: another PR may fix the same problem ai:review-lgtm AI first review found no blocker or major issue (advisory) labels Nov 3, 2026
… attributes in HTML

`BaseMeta.language` is metadata about the text, but the Markdown meta
serializer had no branch for `LanguageMetaField` and fell through to
`str(field_val)`, so a document with language metadata gained paragraphs
such as `confidence=0.99 created_by='langdetect' code=<HumanLanguageLabel.DE: 'de'>`
in Markdown and plain text. `EntitiesMetaField` leaked its repr the same
way, and HTML had no way to express the language at all: `HTMLParams.html_lang`
was never emitted and elements carried no `lang` attribute.

Markdown / plain text: `LanguageMetaField` and `EntitiesMetaField` are
skipped unless `mark_meta=True`, where they render as `[Language] de` and
`[Entities] IBM, Zurich`. Any other structured field without a renderer is
skipped instead of printing its pydantic repr; custom scalar fields keep
rendering as before. `CodeMetaField` (`PictureMeta.code`) gets a renderer
so the skip does not drop it: a fenced block through `_code_block`, which
now takes the language as the info string and sizes the fence past any
backtick run in the code, or the bare text when `format_code_blocks` is
off (plain text). A marked export puts `[Code]` on the line above the
fence, because a fence must start its own line.

HTML: the root element is `<html lang="...">`. `html_lang` now defaults to
None, which resolves to the document language (`doc.body.meta.language`),
else the character-weighted majority of the text items' `meta.language`,
else "en". Paragraphs, headings and list items whose `meta.language`
differs from the page language (compared by primary subtag) get a `lang`
attribute (WCAG 3.1.1 and 3.1.2). The language row in the meta details
block is unchanged.

Ground-truth HTML files are regenerated for the root element only.

Closes docling-project#745

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Jeff Witt <1848307+wittjeff@users.noreply.github.com>
@wittjeff
wittjeff force-pushed the fix/meta-language-serialization branch from a243c16 to 8837555 Compare November 3, 2026 18:27
@github-actions github-actions Bot added ai:ci-safe AI triage found no CI-safety concern (advisory) ai:possible-duplicate AI triage: another PR may fix the same problem labels Nov 3, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai:ci-safe AI triage found no CI-safety concern (advisory) ai:possible-duplicate AI triage: another PR may fix the same problem bug Something isn't working document-model DoclingDocument core model, metadata, hierarchy, validation serialization Output serializers: Markdown, HTML, LaTeX, JSON, plain-text, outline

Projects

None yet

1 participant