Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 64 additions & 8 deletions docling_core/transforms/serializer/doclang.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ class DocLangParams(CommonParams):
_advanced_field(detail="Types of content to serialize (only relevant if add_content is True)."),
] = _DEFAULT_CONTENT_TYPES
layer_mode: Annotated[LayerMode, _advanced_field()] = LayerMode.AUTO
emit_picture_layer: Annotated[
bool,
_advanced_field(detail="Whether to emit `<layer .../>` in picture element heads."),
] = True
# DocLang formatting
pretty_indentation: Annotated[
Optional[str],
Expand All @@ -249,8 +253,11 @@ class DocLangParams(CommonParams):
bool,
_advanced_field(
detail=(
"When True, text items that produce no content (no text, no location) are "
"completely omitted rather than emitting an empty open/close tag pair."
"When True, elements that produce no serialized body content are completely "
"omitted rather than emitting an empty open/close tag pair or a head-only "
"shell (e.g. layer/thread/location metadata without text). When "
"``content_types`` excludes an item's body type, head-only shells are "
"suppressed as well."
),
),
] = False
Expand All @@ -275,12 +282,23 @@ class DocLangParams(CommonParams):
] = False


def _text_item_content_type_active(item: TextItem, params: DocLangParams) -> bool:
"""Return whether the item's primary text content type is enabled in ``params``."""
if isinstance(item, CodeItem):
return ContentType.TEXT_CODE in params.content_types
if isinstance(item, FormulaItem):
return ContentType.TEXT_FORMULA in params.content_types
return ContentType.TEXT_OTHER in params.content_types


def _create_layer_token(
*,
item: DocItem,
params: DocLangParams,
) -> str:
"""Create `<layer value="..."/>` in element head."""
if isinstance(item, PictureItem) and not params.emit_picture_layer:
return ""
if params.layer_mode == LayerMode.ALWAYS or (
params.layer_mode == LayerMode.AUTO and item.content_layer != ContentLayer.BODY
):
Expand Down Expand Up @@ -954,20 +972,34 @@ def _serialize_single_item( # noqa: C901
)
text_part = checkbox_token + text_part

if text_part:
parts.append(text_part)

cap_text = ""
if params.add_referenced_caption and isinstance(item, FloatingItem):
cap_text = doc_serializer.serialize_captions(item=item, **kwargs).text
if cap_text:
cap_text = _escape_text(cap_text, params)
parts.append(cap_text)

ftn_text = ""
if params.add_referenced_footnote and isinstance(item, FloatingItem):
ftn_text = doc_serializer.serialize_footnotes(item=item, **kwargs).text
if ftn_text:
ftn_text = _escape_text(ftn_text, params)
parts.append(ftn_text)

if (
params.suppress_empty_elements
and not _text_item_content_type_active(item, params)
and not text_part
and not cap_text
and not ftn_text
and not (params.add_location and item.prov)
):
return create_ser_result(text="", span_source=item)

if text_part:
parts.append(text_part)
if cap_text:
parts.append(cap_text)
if ftn_text:
parts.append(ftn_text)

text_res = "".join(parts)

Expand Down Expand Up @@ -1144,6 +1176,13 @@ def serialize(
raw_label=_picture_classification_label_value(item),
params=params,
)
# Under content-filtered suppression (opt-in via ``suppress_empty_elements``),
# the classification label is emitted only when picture/chart/chemistry content
# is requested. Default behavior keeps the resolved label unchanged.
if params.suppress_empty_elements and not any_match:
label_for_head = None
else:
label_for_head = picture_label
custom_head = ""
if any_match and item.meta:
meta_kwargs = dict(**kwargs)
Expand Down Expand Up @@ -1194,7 +1233,7 @@ def serialize(
item=item,
doc=doc,
params=params,
label_value=picture_label,
label_value=label_for_head,
caption_text=caption_head or None,
custom_text=custom_head or None,
)
Expand All @@ -1212,6 +1251,15 @@ def serialize(
footnote_text = ftn_res.text
res_parts.append(ftn_res)

if (
params.suppress_empty_elements
and not any_match
and not picture_body_parts
and not footnote_text
and not (params.add_location and item.prov)
):
return create_ser_result()

if not inner and not footnote_text:
if params.suppress_empty_elements:
return create_ser_result()
Expand Down Expand Up @@ -1446,6 +1494,14 @@ def _serialize_single_table(
footnote_text = ftn_res.text
res_parts.append(ftn_res)

if (
params.suppress_empty_elements
and ContentType.TABLE not in params.content_types
and not inner_parts
and not footnote_text
and not (params.add_location and item.prov)
):
return create_ser_result()
if not (head or inner_parts) and not footnote_text:
if params.suppress_empty_elements:
return create_ser_result()
Expand Down
Loading
Loading