Files
2026-07-13 12:08:54 +08:00

97 lines
3.3 KiB
Python

"""Native DOCX engine adapter (implements NativeParserBase hooks)."""
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING, Any
from lightrag.constants import PARSER_ENGINE_NATIVE
from lightrag.parser.native_base import NativeParserBase
from lightrag.utils import logger
if TYPE_CHECKING:
from lightrag.sidecar.ir import IRDoc
class NativeDocxParser(NativeParserBase):
"""Native DOCX parser for LightRAG's production parsing path.
``extract_docx_blocks`` performs only heading-driven structural splitting
(one block per DOCX heading). Block sizing is intentionally left to the
downstream paragraph-semantic chunker, so this parser emits the
one-heading-one-block sidecar contract that chunking consumes.
"""
engine_name = PARSER_ENGINE_NATIVE
sidecar_path_style = "basename_only" # legacy native docx convention
empty_content_label = "DOCX"
def validate_source(self, source: Path, file_path: str) -> None:
if not (
source.exists() and source.is_file() and source.suffix.lower() == ".docx"
):
raise ValueError(
f"Native parser does not support pending file: {file_path}"
)
def extract(
self, source: Path, *, parsed_dir: Path, asset_dir: Path, base_name: str
) -> tuple[list[dict[str, Any]], dict[str, Any], dict[str, Any]]:
"""Extract heading-scoped DOCX blocks (sizing left to the chunker)."""
from lightrag.parser.docx.drawing_image_extractor import (
DrawingExtractionContext,
load_relationships,
)
from lightrag.parser.docx.parse_document import extract_docx_blocks
ctx = DrawingExtractionContext(
docx_path=source,
blocks_output_path=parsed_dir / f"{base_name}.blocks.jsonl",
export_dir_name=asset_dir.name,
export_dir_path=asset_dir,
)
load_relationships(ctx)
warnings: dict[str, Any] = {}
metadata: dict[str, Any] = {}
blocks = extract_docx_blocks(
str(source),
drawing_context=ctx,
parse_warnings=warnings,
parse_metadata=metadata,
)
return blocks, warnings, metadata
def build_ir(
self,
blocks: list[dict[str, Any]],
*,
document_name: str,
asset_dir_name: str,
metadata: dict[str, Any],
) -> "IRDoc":
from lightrag.parser.docx.ir_builder import NativeDocxIRBuilder
return NativeDocxIRBuilder().normalize(
blocks,
document_name=document_name,
asset_dir_name=asset_dir_name,
parse_metadata=metadata,
)
def surface_warnings(
self, warnings: dict[str, Any], source: Path
) -> dict[str, Any] | None:
missing = int(warnings.get("missing_paraid_count", 0) or 0)
if missing > 0:
# Surface once per document; affected blocks emit
# ``positions: [{"type": "paraid", "range": null}]``.
logger.warning(
"[parse_native] %s: %d paragraphs lack paraId; "
"Re-saving file in Word 2013+ to regenerate ids.",
source.name,
missing,
)
return {"missing_paraid_count": missing}
return None