"""Table-specific Markdown probes. Run in the existing renderer environment.
Outputs stay under this run. No network, dependency installation, or UI claims.
"""
from __future__ import annotations
import hashlib
import importlib.metadata
import inspect
import json
import platform
import subprocess
from datetime import datetime, timezone
from html.parser import HTMLParser
from pathlib import Path
import markdown
from markdown.extensions.tables import TableProcessor
from markdown_it import MarkdownIt
from markdown_it.rules_block import table as table_rule
HERE = Path(__file__).resolve().parent
OUT = HERE / "renderers"
SAMPLES = {
"table-bare-markers": "| Status | Task |\n| --- | --- |\n| [ ] | Draft release notes |\n| [x] | Review schema |\n",
"table-list-markers": "| Task | Owner |\n| --- | --- |\n| - [ ] Draft release notes | Writer |\n| - [x] Review schema | Reviewer |\n",
"table-break-markers": "| Task | Status |\n| --- | --- |\n| Draft release notes | Pending
- [ ] Approve |\n| Review schema | Done
- [x] Approve |\n",
"table-status-text": "| Task | Status |\n| --- | --- |\n| Draft release notes | ☐ Pending |\n| Review schema | ☑ Done |\n",
"table-html-inputs": '| Task | Status |\n| --- | --- |\n| Draft release notes | |\n| Review schema | |\n',
"outside-task-list": "- [ ] Draft release notes\n- [x] Review schema\n",
}
class Structure(HTMLParser):
def __init__(self):
super().__init__(convert_charrefs=True)
self.tags = {}
self.inputs = []
self.cells = []
self.cell = None
self.table_depth = 0
self.checkboxes_in_tables = 0
def handle_starttag(self, tag, attrs):
self.tags[tag] = self.tags.get(tag, 0) + 1
if tag == "table":
self.table_depth += 1
if tag in ("td", "th"):
self.cell = ""
a = dict(attrs)
if tag == "input" and a.get("type") == "checkbox":
self.inputs.append({"checked": "checked" in a, "disabled": "disabled" in a})
self.checkboxes_in_tables += bool(self.table_depth)
def handle_endtag(self, tag):
if tag == "table":
self.table_depth -= 1
if tag in ("td", "th") and self.cell is not None:
self.cells.append(self.cell)
self.cell = None
def handle_data(self, data):
if self.cell is not None:
self.cell += data
def run():
OUT.mkdir(exist_ok=True)
results = json.loads((OUT / "results.json").read_text())
# Preserve the upstream baseline; replace only this script's named cases.
rows = [r for r in results["results"] if r["sample"] not in SAMPLES]
versions = {
"markdown-it-commonmark": importlib.metadata.version("markdown-it-py"),
"markdown-it-table-html": importlib.metadata.version("markdown-it-py"),
"markdown-it-table-escaped": importlib.metadata.version("markdown-it-py"),
"python-markdown": importlib.metadata.version("Markdown"),
"pandoc-gfm": subprocess.check_output(["pandoc", "--version"], text=True).splitlines()[0],
}
options = {
"markdown-it-commonmark": 'MarkdownIt("commonmark", {"html": True})',
"markdown-it-table-html": 'MarkdownIt("commonmark", {"html": True}).enable("table")',
"markdown-it-table-escaped": 'MarkdownIt("commonmark", {"html": False}).enable("table")',
"python-markdown": 'markdown.markdown(source, extensions=["tables", "fenced_code"])',
"pandoc-gfm": "pandoc -f gfm -t html",
}
traces = {}
for name, source in SAMPLES.items():
(OUT / f"{name}.md").write_text(source)
rendered = {
"markdown-it-commonmark": MarkdownIt("commonmark", {"html": True}).render(source),
"markdown-it-table-html": MarkdownIt("commonmark", {"html": True}).enable("table").render(source),
"markdown-it-table-escaped": MarkdownIt("commonmark", {"html": False}).enable("table").render(source),
"python-markdown": markdown.markdown(source, extensions=["tables", "fenced_code"]),
"pandoc-gfm": subprocess.run(["pandoc", "-f", "gfm", "-t", "html"], input=source,
text=True, capture_output=True, check=True).stdout,
}
traces[name] = [{"type": t.type, "tag": t.tag, "nesting": t.nesting, "content": t.content}
for t in MarkdownIt("commonmark").enable("table").parse(source)]
for renderer, html in rendered.items():
path = OUT / f"{name}--{renderer}.txt"
path.write_text(html)
p = Structure()
p.feed(html)
rows.append({"sample": name, "renderer": renderer, "tags": p.tags,
"checkbox_inputs": len(p.inputs),
"checked": sum(x["checked"] for x in p.inputs),
"disabled": sum(x["disabled"] for x in p.inputs),
"checkboxes_in_tables": p.checkboxes_in_tables, "cells": p.cells,
"source": f"lab/renderers/{name}.md",
"output": f"lab/renderers/{path.name}",
"source_sha256": hashlib.sha256(source.encode()).hexdigest(),
"output_sha256": hashlib.sha256(html.encode()).hexdigest()})
results["results"] = rows
results["checkbox_table_experiment"] = {
"date": datetime.now(timezone.utc).isoformat(), "python": platform.python_version(),
"versions": versions, "options": options,
"command": "PYTHONDONTWRITEBYTECODE=1 .venv/bin/python runs/2026-11-04-markovo-markdown-checkbox-in-table/lab/checkbox_table_probe.py",
"scope": "Local HTML structure only; no GitHub.com, GitLab, VS Code, Obsidian UI, browser clicks, or source write-back tested.",
"samples": list(SAMPLES),
"metrics": {"tags": "HTMLParser start-tag counts, including XHTML start-end tags once",
"checkbox_inputs": "input elements whose type attribute is checkbox",
"checked": "checkbox input elements carrying the checked attribute",
"disabled": "checkbox input elements carrying the disabled attribute",
"checkboxes_in_tables": "checkbox input elements with a table ancestor",
"cells": "decoded text inside th/td, excluding markup and attributes"},
}
(OUT / "results.json").write_text(json.dumps(results, indent=2, ensure_ascii=False) + "\n")
(OUT / "table-token-traces.json").write_text(json.dumps(traces, indent=2) + "\n")
source_record = []
for title, obj in [("markdown_it/rules_block/table.py:table", table_rule),
("markdown/extensions/tables.py:TableProcessor._build_row", TableProcessor._build_row)]:
file = Path(inspect.getfile(obj))
source_record.extend([title, f"installed_file_sha256={hashlib.sha256(file.read_bytes()).hexdigest()}",
f"function_start_line={inspect.getsourcelines(obj)[1]}", inspect.getsource(obj)])
(HERE / "table-parser-source.txt").write_text("\n".join(source_record))
lines = ["# Checkbox-in-table local experiment", "", results["checkbox_table_experiment"]["scope"], "",
"| Sample | Configuration | tables | li | checkbox inputs | checked | disabled | table checkboxes |",
"| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |"]
for r in rows:
if r["sample"] in SAMPLES:
lines.append(f'| {r["sample"]} | {r["renderer"]} | {r["tags"].get("table",0)} | {r["tags"].get("li",0)} | {r["checkbox_inputs"]} | {r["checked"]} | {r["disabled"]} | {r["checkboxes_in_tables"]} |')
(OUT / "checkbox-table-summary.md").write_text("\n".join(lines) + "\n")
manifest = ["# Inputs and raw HTML outputs", "", "All paths below are relative to this directory.", "",
"| Input | " + " | ".join(versions) + " |",
"| --- | " + " | ".join("---" for _ in versions) + " |"]
for name in SAMPLES:
manifest.append(f"| [{name}.md]({name}.md) | " + " | ".join(
f"[{name}--{renderer}.txt]({name}--{renderer}.txt)" for renderer in versions) + " |")
(OUT / "checkbox-table-files.md").write_text("\n".join(manifest) + "\n")
print(json.dumps({"cases": len(SAMPLES) * len(versions), "versions": versions,
"python": platform.python_version(), "outputs": str(OUT)}))
if __name__ == "__main__":
run()