render_article() {
	dir=$1
	source=$2
	slug=${dir##*/}
	heading_meta=$tmp/headings-$slug.tsv

	exec 3< "$heading_meta"
	while IFS= read -r line || [ -n "$line" ]; do
		case $line in
			*'<div'*'class="article-downloads"'*)
				require_empty_marker "$source" "$line" div "Download"
				case $line in
					*'data-downloads='*)
						die "Download marker uses obsolete data-downloads; use one data-src per download: $source"
						;;
				esac
				write_attrs "$line" "$attrs_file" \
					data-src data-title data-note
				{
					IFS= read -r data_src
					IFS= read -r data_title
					IFS= read -r data_note
				} < "$attrs_file"
				render_download "$dir" "$data_src" "$data_title" "$data_note"
				;;
			*'<pre'*'class="code-block"'*)
				require_empty_marker "$source" "$line" pre "Code block"
				write_attrs "$line" "$attrs_file" \
					data-src data-lang data-open data-title data-note data-footer data-header
				{
					IFS= read -r data_src
					IFS= read -r data_lang
					IFS= read -r data_open
					IFS= read -r data_title
					IFS= read -r data_note
					IFS= read -r data_footer
					IFS= read -r data_header
				} < "$attrs_file"
				render_code_block "$dir" "$data_src" "$data_lang" \
					"$data_open" "$data_title" "$data_note" "$data_footer" "$data_header"
				;;
			*'<figure'*'class="image-block"'*)
				require_empty_marker "$source" "$line" figure "Image"
				write_attrs "$line" "$attrs_file" \
					data-src data-alt data-caption data-open data-title data-note data-footer data-header
				{
					IFS= read -r data_src
					IFS= read -r data_alt
					IFS= read -r data_caption
					IFS= read -r data_open
					IFS= read -r data_title
					IFS= read -r data_note
					IFS= read -r data_footer
					IFS= read -r data_header
				} < "$attrs_file"
				render_image_block "$dir" "$data_src" "$data_alt" \
					"$data_caption" "$data_open" "$data_title" "$data_note" "$data_footer" "$data_header"
				;;
			*'<h2'*'</h2>'*|*'<h3'*'</h3>'*)
				case $line in
					*'<h2'*) heading_tag=h2 ;;
					*) heading_tag=h3 ;;
				esac
				IFS="$tab" read -r indexed_tag heading_id heading_title parent_id <&3 ||
					die "Heading index mismatch while rendering $source"
				[ "$indexed_tag" = "$heading_tag" ] ||
					die "Heading index mismatch while rendering $source"
				if [ -z "$(attr id "$line")" ]; then
					printf '%s\n' "$line" |
						sed "s/<$heading_tag/<$heading_tag id=\"$heading_id\"/"
				else
					printf '%s\n' "$line"
				fi
				;;
			*)
				printf '%s\n' "$line"
			;;
		esac
	done < "$source"
	if IFS="$tab" read -r indexed_tag heading_id heading_title parent_id <&3; then
		die "Heading index mismatch while rendering $source"
	fi
	exec 3<&-
}
