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

> Discover how book-to-skill's fallback mechanism identifies chapters. It analyzes document markup and heading hierarchy when numeric headings are absent for seamless chapter detection.

- Repository: [Virgilio Junior/book-to-skill](https://github.com/virgiliojr94/book-to-skill)
- Tags: internals
- Published: 2026-08-23

---

**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`](https://github.com/virgiliojr94/book-to-skill/blob/main/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:

```python
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](https://github.com/virgiliojr94/book-to-skill/blob/master/book_to_skill/utils.py#L48-L62))

### 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:

```python

# 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:

```python
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:

```python
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:

```python
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`](https://github.com/virgiliojr94/book-to-skill/blob/main/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`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/utils.py) and performs purely structural analysis regardless of numeric pattern presence.