# Heading Styles Book-to-Skill Supports for Chapter Detection: ATX, Setext, and Numbered Explained

> Explore book-to-skill heading styles ATX Setext and numbered titles for chapter detection. Learn how it automatically excludes code blocks and punctuation.

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

---

**Book-to-skill recognizes three distinct heading styles for chapter detection—ATX hash-based syntax, Setext underline notation, and numbered titles—while automatically excluding headings inside fenced code blocks and punctuation-only structures.**

The open-source *book-to-skill* library automatically partitions book content by parsing structural headings in Markdown-style documents. Accurate chapter detection depends on understanding which specific syntax patterns the library identifies as chapter boundaries. According to the [`book_to_skill/utils.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/utils.py) implementation, the detection engine supports three heading families with rigorous validation rules to avoid false positives.

## ATX-Style Headings

ATX-style headings use hash characters (`#`) or AsciiDoc-style equal signs (`=`) at the line start. The detection regex `^(#{1,6}|={1,6})\s+(.+?)\s*#*$` captures levels 1–6 for both Markdown and AsciiDoc formats.

Valid examples include:

- `# Chapter 1` (Markdown level 1)

- `## Section 2` (Markdown level 2)

- `= Title` (AsciiDoc level 1)
- `== Section` (AsciiDoc level 2)

The parser allows optional trailing hashes and requires at least one space after the prefix character. Empty or punctuation-only titles are discarded during processing.

## Setext Underline Headings

Setext notation uses underline characters to designate heading levels without hash prefixes. The library detects level 1 headings using two or more equal signs (`=`) and level 2 headings using two or more hyphens (`-`).

The regex `^(={2,}|-{2,})$` identifies underline lines. When matched, the parser examines the immediately preceding non-blank line; if it contains word characters, that line becomes the heading title. This pattern handles structures like:

```markdown
Chapter 1
=========

Section 2
---------

```

The implementation specifically guards against punctuation-only underline lines to prevent table borders or decorative lines from being misclassified as headings.

## Numbered Headings

Numbered headings combine ATX syntax with digit-led titles (e.g., `## 1 Introduction`). While detected by the standard ATX regex, these receive additional validation through the `_numbered_titles_are_structural` function.

To qualify as structural chapters, numbered headings must satisfy two thresholds:

- **Minimum count**: At least 3 numbered titles must exist in the document
- **Content length**: The median body length across detected sections must equal or exceed 200 characters

This prevents single-digit lists or short outlines from triggering false chapter detection.

## Detection Flow and Safety Mechanisms

The chapter detection pipeline in [`book_to_skill/utils.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/utils.py) (lines 305–440) implements a multi-stage filtering process:

1. **Code block exclusion**: The `_closed_fence_line_numbers` function identifies lines inside fenced code blocks (triple backticks), ensuring that commented headings like `# Not a chapter` are ignored.

2. **Setext processing**: Lines matching the underline regex trigger evaluation of the preceding text (lines 313–330).
3. **ATX extraction**: Lines matching the ATX regex yield titles, with empty or punctuation-only results discarded (lines 334–444).
4. **Numbered validation**: Digit-led titles undergo structural validation via `_numbered_titles_are_structural` (lines 448–521).
5. **Depth selection**: The system selects the shallowest heading depth containing at least two distinct titles as the chapter level (lines 545–557).

## Practical Implementation Examples

The `_structural_chapter_count` function demonstrates how these rules apply to real content:

```python
from book_to_skill.utils import _structural_chapter_count

markdown = """

# My Book Title

## Chapter 1

Content of the first chapter.

## Chapter 2

More text.

```python

# Not a chapter

def foo():
    pass

```

Section 1
---
"""

print(_structural_chapter_count(markdown))

# → 2  (detects the two ATX headings, skips the fenced-code comment)

```

Setext-style detection works similarly:

```python
from book_to_skill.utils import _structural_chapter_count

setext = """
My Book Title
==============

Chapter 1
---------

Text…

Chapter 2
---------

More text…
"""

print(_structural_chapter_count(setext))

# → 2  (detects the level-1 "=…" title and the level-2 "-" headings)

```

Numbered headings require sufficient content depth:

```python
from book_to_skill.utils import _structural_chapter_count

numbered = """

## 1 Setup

Short intro.

## 2 Usage

Longer description that exceeds the median-body threshold.

## 3 Advanced

Even more content.
"""

print(_structural_chapter_count(numbered))

# → 3  (recognises the numbered headings as structural chapters)

```

## Test Coverage and Validation

The library's test suite validates each heading style through dedicated test files:

- [`tests/test_numbered_headings.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/tests/test_numbered_headings.py) verifies numeric chapter thresholds
- [`tests/test_setext_punctuation_guard.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/tests/test_setext_punctuation_guard.py) ensures decorative lines are not misclassified
- [`tests/test_unbalanced_code_fence.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/tests/test_unbalanced_code_fence.py) confirms headings inside unclosed fences are excluded

## Summary

- **ATX-style headings** using `#` or `=` prefixes (levels 1–6) are detected via regex `^(#{1,6}|={1,6})\s+(.+?)\s*#*$`
- **Setext underlines** using `===` or `---` patterns are recognized when paired with valid preceding text
- **Numbered headings** require digit-led titles plus minimum count (3+) and median body length (≥200 chars) thresholds
- **Safety guards** exclude fenced code blocks, punctuation-only lines, and unclosed fence content
- The core logic resides in [`book_to_skill/utils.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/utils.py) with validation distributed across lines 305–557

## Frequently Asked Questions

### Does book-to-skill support AsciiDoc heading syntax?

Yes. The ATX detection regex explicitly includes AsciiDoc-style equal signs (`=`) alongside Markdown hash characters. Patterns like `= Title` and `== Section` are parsed identically to their Markdown counterparts, though the underlying detection logic treats them as equivalent structural indicators.

### How does the library avoid detecting headings inside code blocks?

The `_closed_fence_line_numbers` function pre-scans the document to identify line ranges inside triple-backtick fenced blocks. During the main detection loop, any heading patterns appearing within these ranges are automatically excluded, preventing code comments like `# Initialize variable` from being counted as chapters.

### What prevents table borders from being detected as Setext headings?

The Setext detection logic requires the line preceding an underline match to contain word characters (`\w`). Additionally, the [`tests/test_setext_punctuation_guard.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/tests/test_setext_punctuation_guard.py) test suite validates that lines consisting solely of punctuation marks or decorative characters are rejected, ensuring that table borders like `------` are not misclassified as level-2 headings.

### Why are numbered headings subject to additional validation?

Numbered headings (e.g., `## 1 Introduction`) are validated through `_numbered_titles_are_structural` to distinguish true chapter structures from simple numbered lists or outlines. The function enforces a minimum of three numbered titles and requires a median body length of at least 200 characters, ensuring substantial content exists between detected boundaries before classification as structural chapters.