Skip to main content

doc23 banner

📚 doc23

Convert documents into structured JSON effortlessly.
A Python library for extracting text from various document formats and structuring it hierarchically into JSON.


📌 Features

  • ✅ Extract text from PDFs, DOCX, TXT, RTF, ODT, MD, and images.
  • 🖼️ OCR support for scanned documents and images.
  • ⚙️ Flexible configuration using regex patterns and field mapping.
  • 🌳 Nested hierarchical structure output in JSON.
  • ✨ Explicit leaf-level control using is_leaf=True.
  • 🔍 Built-in validations to catch config mistakes (regex, hierarchy, field conflicts).
  • 🧪 Comprehensive pytest suite with coverage reporting.

📦 Installation

pip install doc23

To enable OCR:

sudo apt install tesseract-ocr
pip install pytesseract

🚀 Quickstart Example

Basic Text Extraction

from doc23 import extract_text

# Extract text from any supported document
text = extract_text("document.pdf", scan_or_image="auto")
print(text)

Structured Document Parsing

from doc23 import Doc23, Config, LevelConfig

config = Config(
    root_name="art_of_war",
    sections_field="chapters",
    description_field="description",
    levels={
        "chapter": LevelConfig(
            pattern=r"^CHAPTER\s+([IVXLCDM]+)\n(.+)$",
            name="chapter",
            title_field="title",
            description_field="description",
            sections_field="paragraphs"
        ),
        "paragraph": LevelConfig(
            pattern=r"^(\d+)\.\s+(.+)$",
            name="paragraph",
            title_field="number",
            description_field="text",
            is_leaf=True
        )
    }
)

with open("art_of_war.txt") as f:
    text = f.read()

doc = Doc23(text, config)
structure = doc.prune()

print(structure["chapters"][0]["title"])  # → I

🧾 Output Example

{
  "description": "",
  "chapters": [
    {
      "type": "chapter",
      "title": "I",
      "description": "Laying Plans",
      "paragraphs": [
        {
          "type": "paragraph",
          "number": "1",
          "text": "Sun Tzu said: The art of war is of vital importance to the State."
        }
      ]
    }
  ]
}

🛠️ Document Configuration

Use Config and LevelConfig to define how your document is parsed:

Field Purpose
pattern Regex to match each level
title_field Field to assign the first regex group
description_field (Optional) Field for second group
sections_field (Optional) Where sublevels go
paragraph_field (Optional) Where text/nodes go if leaf
is_leaf (Optional) Forces this level to be terminal

Capture Group Rules

Fields Defined Required Groups in Regex
title_field only ≥1
title_field + description_field ≥2
title_field + paragraph_field ≥1 (second group optional)

🏗️ Architecture Overview

doc23 consists of several key components:

Doc23 (core.py)
├── Extractors (extractors/)
│   ├── PDFExtractor
│   ├── DocxExtractor
│   ├── TextExtractor
│   └── ...
├── Config (config_tree.py)
│   └── LevelConfig
└── Gardener (gardener.py)
  1. Doc23: Main entry point, handles file detection and orchestration
  2. Extractors: Convert various document types to plain text
  3. Config: Defines how to structure the document hierarchy
  4. Gardener: Parses text and builds the JSON structure

✅ Built-in Validation

The library validates your config when creating Doc23:

  • ✋ Ensures all parents exist.
  • 🔁 Detects circular relationships.
  • ⚠️ Checks field name reuse.
  • 🧪 Verifies group counts match pattern.

If any issue is found, a ValueError will be raised immediately.


🧪 Testing

The library includes a comprehensive test suite covering various scenarios:

Basic Initialization

def test_gardener_initialization():
    config = Config(
        root_name="document",
        sections_field="sections",
        description_field="description",
        levels={
            "book": LevelConfig(
                pattern=r"^BOOK\s+(.+)$",
                name="book",
                title_field="title",
                description_field="description",
                sections_field="sections"
            ),
            "article": LevelConfig(
                pattern=r"^ARTICLE\s+(\d+)\.\s*(.*)$",
                name="article",
                title_field="title",
                description_field="content",
                paragraph_field="paragraphs",
                parent="book"
            )
        }
    )
    gardener = Gardener(config)
    assert gardener.leaf == "article"

Document Structure

def test_prune_basic_structure():
    config = Config(
        root_name="document",
        sections_field="sections",
        description_field="description",
        levels={
            "book": LevelConfig(
                pattern=r"^BOOK\s+(.+)$",
                name="book",
                title_field="title",
                description_field="description",
                sections_field="sections"
            ),
            "article": LevelConfig(
                pattern=r"^ARTICLE\s+(\d+)\.\s*(.*)$",
                name="article",
                title_field="title",
                description_field="content",
                paragraph_field="paragraphs",
                parent="book"
            )
        }
    )
    gardener = Gardener(config)
    text = """BOOK First Book
This is a description
ARTICLE 1. First article
This is article content
More content"""
    result = gardener.prune(text)
    assert result["sections"][0]["title"] == "First Book"
    assert result["sections"][0]["sections"][0]["paragraphs"] == ["This is article content", "More content"]

Edge Cases

def test_prune_empty_document():
    config = Config(
        root_name="document",
        sections_field="sections",
        description_field="description",
        levels={}
    )
    gardener = Gardener(config)
    result = gardener.prune("")
    assert result["sections"] == []

Free Text Handling

def test_prune_with_free_text():
    config = Config(
        root_name="document",
        sections_field="sections",
        description_field="description",
        levels={
            "title": LevelConfig(
                pattern=r"^TITLE\s+(.+)$",
                name="title",
                title_field="title",
                description_field="description",
                sections_field="sections"
            )
        }
    )
    gardener = Gardener(config)
    text = """This is free text at the top level
TITLE First Title
Title description"""
    result = gardener.prune(text)
    assert result["description"] == "This is free text at the top level"

Run tests with:

python -m pytest tests/

❓ Troubleshooting FAQ

OCR not working

Make sure Tesseract is installed and accessible in your PATH.

Text extraction issues

Different document formats may require specific libraries. Check your dependencies:

  • PDF: pdfplumber, pdf2image
  • DOCX: docx2txt
  • ODT: odf

Regex pattern not matching

Test your patterns with tools like regex101.com and ensure you have the correct number of capture groups.


🔄 Compatibility

  • Python 3.8+
  • Tested on Linux, macOS, and Windows

👥 Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

MIT


🔗 Resources


🧠 Advanced Usage

For advanced patterns, dynamic configs, exception handling and OCR examples, see:

📄 ADVANCED_USAGE_doc23.md

Metadata

Release files for doc23 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for doc23 0.1.2
File Size Uploaded Provenance Actions
doc23-0.1.2.tar.gz 21.1 kB Without provenance Details for doc23-0.1.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for doc23 0.1.2
File Interpreter ABI Platform Actions
doc23-0.1.2-py3-none-any.whl Python 3 none any Details for doc23-0.1.2-py3-none-any.whl

Total release size: 46.3 kB

Release files / doc23-0.1.2.tar.gz

Download URL doc23-0.1.2.tar.gz
Size 21.1 kB
Tags Source
SHA-256 checksum
How to use checksums
2c8a68b29768b96f9511eb34bbd79b2254c05dd8c59c1c9318844c6b2f99f771
BLAKE2b-256 checksum
How to use checksums
daf9812be10f4d7740d670af23dcfbea0946bdb0596eaaccdda46464ece2b900
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.1

Release files / doc23-0.1.2-py3-none-any.whl

Download URL doc23-0.1.2-py3-none-any.whl
Size 25.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e7c76a2a0f87ad30d6cadc73cc579570006aa93fbad7cccf100391abaeaddb63
BLAKE2b-256 checksum
How to use checksums
96b746a9a26b1180ee1850888d9b1ae845f1a10f3864e924cc09bc2cee2d66c7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.1

Security for doc23 0.1.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page