# Markdown Recipes ## Contents - Parse document structure (headings, sections) - Replace one section in place - Generate a table of contents - Check links and images - Extract tables and code blocks - Pandoc conversion options - Gotchas (flavors, fences, whitespace) ## Parse document structure (headings, sections) Heading detection must ignore ``` fenced regions — `#` inside code is not a heading: ```python def headings(path): """Yield (line_no, level, title) for real headings only.""" in_fence = False for i, line in enumerate(open(path, encoding="utf-8")): if line.lstrip().startswith("```"): in_fence = not in_fence elif not in_fence and line.startswith("#"): level = len(line) - len(line.lstrip("#")) if level <= 6 and line[level:level + 1] == " ": yield i, level, line[level:].strip() ``` ## Replace one section in place A section spans from its heading to the next heading of the same or higher level: ```python def replace_section(path, title, new_body): lines = open(path, encoding="utf-8").read().splitlines(keepends=True) hs = list(headings(path)) match = [h for h in hs if h[2] == title] if len(match) != 1: raise ValueError(f"'{title}': found {len(match)} occurrences, need exactly 1") start_line, level, _ = match[0] later = [h for h in hs if h[0] > start_line and h[1] <= level] end_line = later[0][0] if later else len(lines) new = lines[:start_line + 1] + [new_body.rstrip() + "\n\n"] + lines[end_line:] open(path, "w", encoding="utf-8").writelines(new) ``` ## Generate a table of contents GitHub anchor rule: lowercase, spaces → `-`, strip everything except word chars and hyphens: ```python import re def toc(path, max_level=3): out = [] for _, level, title in headings(path): if 2 <= level <= max_level: # skip the H1 itself anchor = re.sub(r"[^\w\- ]", "", title).strip().lower().replace(" ", "-") out.append(f"{' ' * (level - 2)}- [{title}](#{anchor})") return "\n".join(out) ``` Duplicate titles get `-1`, `-2` suffixes on GitHub — deduplicate with a counter if titles repeat. ## Check links and images Run before any pandoc conversion; missing images abort PDF builds: ```python import os, re def broken_refs(path): text = open(path, encoding="utf-8").read() base = os.path.dirname(os.path.abspath(path)) broken = [] for target in re.findall(r"!?\[[^\]]*\]\(([^)#\s]+)[^)]*\)", text): if not target.startswith(("http://", "https://", "mailto:")): if not os.path.exists(os.path.join(base, target)): broken.append(target) return broken ``` (External URLs: check only if the user asks — needs network calls.) ## Extract tables and code blocks ```python import re text = open("doc.md", encoding="utf-8").read() # Fenced code blocks with language → [(lang, code), ...] blocks = re.findall(r"```(\w*)\n(.*?)```", text, flags=re.S) # Pipe tables → rows of cells (skip the |---| separator line) rows = [[c.strip() for c in line.strip().strip("|").split("|")] for line in text.splitlines() if line.lstrip().startswith("|") and not re.match(r"^\s*\|[\s:|-]+\|\s*$", line)] ``` ## Pandoc conversion options ```bash # Standalone HTML with title metadata (otherwise pandoc emits a fragment) pandoc README.md -s --metadata title="README" -o README.html # GFM input explicitly (tables, task lists, strikethrough) pandoc -f gfm README.md -o out.docx # PDF with margins and a TOC pandoc report.md --toc -V geometry:margin=1in -o report.pdf # Word → Markdown, keep images pandoc report.docx --extract-media=./media -t gfm -o report.md # Custom Word styling: reuse an existing doc's styles pandoc report.md --reference-doc=template.docx -o styled.docx ``` ## Gotchas (flavors, fences, whitespace) - **Flavor differences.** Tables, task lists (`- [ ]`), footnotes, and strikethrough are GFM/extensions — original Markdown renderers ignore them. Pandoc's default input is *pandoc markdown*, not GFM: pass `-f gfm` when the source came from GitHub. - **Fences must balance.** An unclosed ``` swallows the rest of the document. Count fence lines before structural edits (the `headings()` recipe already guards this). - **Indented code blocks.** 4-space-indented lines are code in classic Markdown — a list continuation indented 4+ spaces can silently become a code block. Prefer fenced blocks everywhere. - **Two trailing spaces = line break.** Invisible but meaningful; don't strip trailing whitespace blindly in an existing file. - **Bare URLs** don't auto-link in all renderers — wrap in `<...>` or `[text](url)`. - **HTML inside Markdown** passes through most renderers but is stripped by some (and by pandoc to some formats) — flag it when converting. - **Setext headings** (`Title\n=====`) are equivalent to `#`/`##`; the structure parser above misses them — normalize them first if a file mixes both styles, but only with the user's agreement (it rewrites lines). - **Anchor links differ across renderers.** The GitHub rule in the TOC recipe does not match GitLab/MkDocs exactly for punctuation-heavy titles; verify on the target platform.