How the Fallback Mechanism for Chapter Detection Works in book-to-skill

The fallback mechanism for chapter detection in book-to-skill activates when no numeric chapter headings are present, switching to a structural analysis of document markup to count chapters based on heading hierarchy.

The book-to-skill library (virgiliojr94/book-to-skill) extracts structured content from technical books using a two-tier detection system. When explicit numeric chapter labels like "Chapter 5" or "Capítulo 5" are absent, the library automatically employs a sophisticated fallback mechanism for chapter detection that parses Markdown, AsciiDoc, and reStructuredText markup to infer document structure.

Primary Method: Numeric Chapter Detection

Before the fallback engages, book-to-skill attempts to locate numeric chapter headings using the _chapter_number() function in book_to_skill/utils.py. This parser recognizes patterns like "Chapter 5", Roman numerals, and localized variants across CJK, Thai, Korean, and Persian languages. The primary detection logic resides in detect_structure() at lines 48-62, which first aggregates all numeric matches into numeric_count.

The Fallback Mechanism: Structural Heading Analysis

When numeric_count == 0, the extractor immediately falls back to structural analysis. The branch logic in detect_structure() is explicit:

if numeric_count > 0:
    chapters_detected = numeric_count
    chapters_method = "numeric"
else:
    chapters_detected = _structural_chapter_count(text)   # ← fallback

    chapters_method = "structural" if chapters_detected else "none"

(source)

How _structural_chapter_count Parses Documents

The core fallback implementation in _structural_chapter_count() (lines 54-78 define the signature; lines 80-124 implement detection) performs a multi-step analysis of raw text:

  1. Preprocessing: Splits text into lines while excluding fenced code blocks using _closed_fence_line_numbers() to prevent false positives from code comments.

  2. Heading Recognition: Identifies ATX headings (e.g., # Title, ## Section) via regex matching, and Setext/RST underlines (e.g., ===, ---) by comparing line lengths.

  3. Normalization: Handles numbered headings like ## 1 Intro by measuring body length to distinguish chapter titles from short headers.

  4. Hierarchy Grouping: Organizes detected headings by depth level.

Depth Selection Logic

After scanning, the algorithm selects the shallowest heading level containing at least two distinct titles—the typical "# Book Title / ## Chapter" hierarchy:


# After scanning, pick the shallowest level with ≥2 distinct headings

for depth in sorted(levels):
    if len(levels[depth]) >= 2:
        return len(levels[depth])

# If no level qualifies, return the sum of all levels (fallback for thin docs)

return sum(len(titles) for titles in levels.values())

This ensures the method captures actual chapter divisions rather than minor subsections or metadata headers.

Practical Code Examples

Detecting Chapters Automatically

Use detect_structure() to let the library choose between numeric and fallback methods:

from book_to_skill.utils import detect_structure

with open("example.md", "r", encoding="utf-8") as f:
    text = f.read()

info = detect_structure(text)
print(info["chapters_detected"], info["chapters_method"])

# → 12 structural   (numeric headings not found, structural fallback applied)

Forcing Structural Analysis Manually

Directly invoke the fallback logic when you know numeric headings are absent:

from book_to_skill.utils import _structural_chapter_count

markdown = """

# My Book

## Introduction

## Chapter One

## Chapter Two

"""

print(_structural_chapter_count(markdown))

# → 2  (counts the two distinct level‑2 headings)

Comparing Detection Methods

Demonstrate the difference between numeric and structural counting on mixed content:

from book_to_skill.utils import _chapter_number, _structural_chapter_count

doc = """
Chapter 1
========

# Introduction

## Chapter One

"""

# Numeric detection

numeric = sum(1 for line in doc.splitlines() if _chapter_number(line))
print("numeric:", numeric)               # → 1

# Structural fallback

print("structural:", _structural_chapter_count(doc))  # → 2

Summary

  • Two-tier architecture: The system prioritizes explicit numeric chapter labels before falling back to structural analysis.
  • Markup agnostic: The fallback handles Markdown, AsciiDoc, and reStructuredText via unified heading detection.
  • Code-aware parsing: Fenced code blocks are excluded from analysis to prevent comment lines from being mistaken for headings.
  • Hierarchical intelligence: The shallowest heading level with two or more distinct titles is selected as the chapter layer.
  • Graceful degradation: If no hierarchical pattern qualifies, the function returns the sum of all heading levels to ensure thin documents still return counts.

Frequently Asked Questions

What triggers the fallback mechanism for chapter detection?

The fallback triggers when _chapter_number() finds zero numeric chapter headings in the document. Specifically, when numeric_count == 0 inside detect_structure() (lines 48-62 of book_to_skill/utils.py), the code path switches to _structural_chapter_count() to analyze heading hierarchy instead of explicit labels.

Does the structural fallback work with all markup formats?

Yes. The implementation recognizes ATX-style headings (#, ##) common in Markdown, Setext underlines (===, ---) used in early Markdown and AsciiDoc, and reStructuredText title markers. The regex patterns _ATX_HEADING and _SETEXT_UNDERLINE handle these variants uniformly.

How does the fallback avoid counting code comments as chapters?

The _structural_chapter_count() function calls _closed_fence_line_numbers() to identify and exclude lines appearing within fenced code blocks (e.g., triple backtick sections) before applying heading regexes. This prevents false positives from commented-out code or markdown examples within the document.

Can I force the structural method even if numeric headings exist?

While detect_structure() automatically prefers numeric detection when available, you can bypass this by calling _structural_chapter_count(text) directly. This function is exposed in book_to_skill/utils.py and performs purely structural analysis regardless of numeric pattern presence.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →