MarklyGuidesHeadings

Headings

This guide explains how to work with headings in Markly, including extracting them for navigation and handling duplicate heading text.

Unique ID Generation

When rendering HTML with ids: true, duplicate heading text automatically gets unique IDs to avoid collisions. This is particularly useful when multiple sections have the same title (e.g., multiple "Deployment" sections under different parent headings).

markdown = <<~MARKDOWN
  ## Kubernetes
  
  ### Deployment
  
  ## Systemd
  
  ### Deployment
MARKDOWN

renderer = Markly::Renderer::HTML.new(ids: true)
html = renderer.render(Markly.parse(markdown))

# Generates:
# <section id="kubernetes">...</section>
# <section id="deployment">...</section>
# <section id="systemd">...</section>
# <section id="deployment-2">...</section>

The first occurrence gets the clean ID, subsequent duplicates get numbered suffixes (-2, -3, etc.).

Extracting Headings for Table of Contents

The Headings class can extract headings for building navigation or table of contents:

document = Markly.parse(markdown)
headings = Markly::Renderer::Headings.extract(document, min_level: 2, max_level: 3)

headings.each do |heading|
	puts "#{heading.level}: #{heading.text} (#{heading.anchor})"
end

# Output:
# 2: Kubernetes (kubernetes)
# 3: Deployment (deployment)
# 2: Systemd (systemd)
# 3: Deployment (deployment-2)

Each Heading object has:

  • level - The heading level (1-6)
  • text - The plain text content
  • anchor - The unique ID/anchor
  • node - The original Markly AST node

Level Filtering

Use min_level and max_level to filter which heading levels to extract:

# Only extract h2 and h3 headings
headings = Markly::Renderer::Headings.extract(document, min_level: 2, max_level: 3)

# Only h1 headings
headings = Markly::Renderer::Headings.extract(document, min_level: 1, max_level: 1)

Custom Heading Strategies

For advanced use cases, you can provide a custom Headings instance to the HTML renderer:

Sharing State Across Documents

To ensure IDs remain unique across multiple documents:

# Share heading state across multiple documents
headings = Markly::Renderer::Headings.new
renderer = Markly::Renderer::HTML.new(headings: headings)

doc1_html = renderer.render(Markly.parse(doc1_markdown))
doc2_html = renderer.render(Markly.parse(doc2_markdown))
# IDs remain unique across both documents

Custom ID Generation

Subclass Headings to implement alternative ID generation strategies:

class HierarchicalHeadings < Markly::Renderer::Headings
	def initialize
		super
		@parent_context = []
	end
	
	def anchor_for(node)
		base = base_anchor_for(node)
		
		# Custom logic: could incorporate parent heading context to generate IDs like "kubernetes-deployment" instead of "deployment-2"
		
		if @ids.key?(base)
			@ids[base] += 1
			"#{base}-#{@ids[base]}"
		else
			@ids[base] = 1
			base
		end
	end
end

renderer = Markly::Renderer::HTML.new(headings: HierarchicalHeadings.new)