Skip to content

Feat: Native hierarchies for docx element types#1505

Merged
newelh merged 93 commits into
mainfrom
newelh/hierarchy-fast/docx
Sep 27, 2023
Merged

Feat: Native hierarchies for docx element types#1505
newelh merged 93 commits into
mainfrom
newelh/hierarchy-fast/docx

Conversation

@newelh

@newelh newelh commented Sep 22, 2023

Copy link
Copy Markdown
Contributor

Summary

Improves hierarchy from docx files by leveraging natural hierarchies built into docx documents. Hierarchy can now be detected from an indentation level for list bullets/numbers and by style name (e.g. Heading 1, List Bullet 2, List Number).

Hierarchy detection is improved by determining category depth via the following:

  1. Check if the paragraph item has an indentation level (ilvl) xpath - these are typically on list bullet/numbers. Return the indentation level if it exists
  2. Check the name of the paragraph style if it contains any category depth information (e.g. Heading 1 vs Heading 2 or List Bullet vs List Bullet 2). Return the category depth if found, else default to depth of 0.
  3. Check the paragraph ilvl via the paragraph's style name. Outside of the paragraph's metadata, docx stores default ilvls for various style names, which requires a complex lookup. This check is yet to be implemented, as the above methods cover most usecases but the implementation is stubbed out.

Testing

This PR adds an additional docx file to example-docs: category-level.docx. This document illustrates heading and bullet hierarchies in a docx file.

Unit tests

  1. test_unstructured/partition/docx/test_docx::test_parse_category_depth_by_style-- Verifies that a paragraph correctly returns the expected category depth.
  2. test_unstructured/partition/docx/test_docx::test_parse_category_depth_by_style_name-- Verifies that the category depth for the majority of style names work correctly.
  3. test_unstructured/partition/docx/test_docx::test_parse_category_depth_by_style_ilvl-- Verifies the stubbed out implementation returns 0 for category depth.

Example

Sample document:
Screenshot of word document

Before

document_structure

After

document_structure_hierarchy

newelh and others added 30 commits September 1, 2023 10:21
new document_to_element_list location
and execution to metadata cleaning in filetype
non-deterministic hierachy check
add more robust test for checking depth
* The file used in one of the getting-started examples has moved, update
  the code-block so it works.

* Remedy bulleted list that was rendering in-line. Bulletted list start
  needs to be separated by blank line. Bullet-paragraph continuation
  lines need to be indented to align with text of first line.
Project-specific Black configuration like line-length cannot be
specified in setup.cfg, only the more modern pyproject.toml.

Since we probably need to move to pyproject.toml soon for building and
distribution anyway, add a `pyproject.toml` file and add our project's
Black configuration values to it. This is only line-length for now but
is likely to grow.
@newelh
newelh marked this pull request as ready for review September 26, 2023 20:22
@newelh
newelh requested a review from scanny September 26, 2023 20:24

@scanny scanny 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.

LGTM Newel :)

A couple of remarks to consider.


# Remember that category depths are 0-indexed and relative to the category type
# Title, list item, bullet, narrative text, etc.
paragraph = partitioner._document.paragraphs[0]

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.

You might extracting paragraphs = partitioner._document.paragraphs to a line at the top here just to reduce the word-count below. The repeated dereferencings don't buy you anything as far as I can see. They're also expensive, but of course that only matters at scale; still, as a matter of style you wouldn't want repeated identical dereferencings (say more than two; each dot is a dict lookup) in your production code so consistent style would be to apply the same to test code.

You could also consider a more compact structure like:

    test_cases = [
        (0, "Some text before any heading"),
        (0, "A Heading 1"),
        # etc. ...
    ]

    partitioner = _DocxPartitioner("example-docs/category-level.docx", None, None, False, None)
    paragraphs = partitioner._document.paragraphs

    for idx, (depth, text) in enumerate(test_cases):
        paragraph = paragraphs[idx]
        actual_depth = partitioner._parse_category_depth_by_style(paragraph)
        assert (
            actual_depth == depth
        ), f"expected paragraph[{idx}] to have depth=={depth}, got {actual_depth}"
        assert text in paragraph.text, f"paragraph[{idx}].text does not contain {text}"

Then you could probably see the whole test without scrolling. This also has the benefit being able to produce nice test-failure messages without repeating yourself a lot.

Your call though, just an idea :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Much more elegant. Thank you!

Comment thread test_unstructured/partition/docx/test_docx.py Outdated
Comment thread unstructured/partition/common.py
@newelh
newelh merged commit 55315cf into main Sep 27, 2023
@newelh
newelh deleted the newelh/hierarchy-fast/docx branch September 27, 2023 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants