Heading Styles Book-to-Skill Supports for Chapter Detection: ATX, Setext, and Numbered Explained
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 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:
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 (lines 305–440) implements a multi-stage filtering process:
-
Code block exclusion: The
_closed_fence_line_numbersfunction identifies lines inside fenced code blocks (triple backticks), ensuring that commented headings like# Not a chapterare ignored. -
Setext processing: Lines matching the underline regex trigger evaluation of the preceding text (lines 313–330).
-
ATX extraction: Lines matching the ATX regex yield titles, with empty or punctuation-only results discarded (lines 334–444).
-
Numbered validation: Digit-led titles undergo structural validation via
_numbered_titles_are_structural(lines 448–521). -
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:
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))
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:
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.pyverifies numeric chapter thresholdstests/test_setext_punctuation_guard.pyensures decorative lines are not misclassifiedtests/test_unbalanced_code_fence.pyconfirms 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.pywith 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →