"""Reproduce this guide's HTML conversions without network access or installation."""
from pathlib import Path
from importlib.metadata import version
import argparse
import datetime
import hashlib
import json
import platform
import shutil
import subprocess
import time
from bs4 import BeautifulSoup
from markdownify import markdownify
from markitdown import MarkItDown
LAB = Path(__file__).resolve().parent
SAMPLES = ['article', 'headerless', 'colgroup-header', 'colgroup-thead']
def language(pre):
code = pre.find('code')
for value in (code or pre).get('class', []):
if value.startswith('language-'):
return value.removeprefix('language-')
return ''
def selected_html(text):
soup = BeautifulSoup(text, 'html.parser')
selected = soup.select_one('main article') or soup.find('article') or soup.body or soup
return str(selected)
def fixtures():
corpus = LAB / 'corpus'
probes = {
'headerless': (
'
Queue export
\n'
'The source contains data cells without column labels.
\n'
'| Amber | Queued |
\n'
'| Birch | Done |
\n',
{'headings': [{'level': 1, 'text': 'Queue export'}],
'paragraphs': ['The source contains data cells without column labels.'],
'tables': [{'header': [], 'rows': [['Amber', 'Queued'], ['Birch', 'Done']]}]}),
'colgroup-header': (
'Labelled queue export
\n'
'The column group precedes a row containing explicit header cells.
\n'
'\n'
'| Task | State |
\n'
'| Amber | Queued |
\n'
'| Birch | Done |
\n',
{'headings': [{'level': 1, 'text': 'Labelled queue export'}],
'paragraphs': ['The column group precedes a row containing explicit header cells.'],
'tables': [{'header': ['Task', 'State'], 'rows': [['Amber', 'Queued'], ['Birch', 'Done']]}]})
}
for name, (html, truth) in probes.items():
(corpus / (name + '.html')).write_text(html)
(corpus / (name + '.truth.json')).write_text(json.dumps(
{'kind': 'doc', 'format': 'html', **truth}, indent=2) + '\n')
html, truth = probes['colgroup-header']
wrapped = html.replace('| Task | State |
',
'| Task | State |
')
(corpus / 'colgroup-thead.html').write_text(wrapped)
(corpus / 'colgroup-thead.truth.json').write_text(json.dumps(
{'kind': 'doc', 'format': 'html', **truth}, indent=2) + '\n')
# Publishable plain-text mirrors: exact bytes, never advertised as rendered HTML.
for name in SAMPLES:
(corpus / (name + '.txt')).write_bytes((corpus / (name + '.html')).read_bytes())
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--examples', action='store_true')
args = parser.parse_args()
if args.examples:
raise SystemExit('Run the standalone example scripts with your own input.html.')
fixtures()
manifest = LAB / 'runs.json'
archive = LAB / 'upstream-runs.json'
if not archive.exists():
archive.write_bytes(manifest.read_bytes())
old = json.loads(archive.read_text())
tools = {
'markitdown': version('markitdown'),
'markdownify': version('markdownify'),
'markdownify-selected': version('markdownify'),
'markdownify-infer-header': version('markdownify'),
'pandoc-gfm': subprocess.check_output(['pandoc', '--version'], text=True).splitlines()[0],
}
manifest_data = {**old, 'python': platform.python_version(),
'date': datetime.date.today().isoformat(),
'tools': {**old['tools'], **tools}}
manifest_data['results'] = [r for r in old['results'] if r['tool'] not in tools]
converter = MarkItDown(enable_plugins=False)
observations = []
for tool in tools:
output_dir = LAB / tool
output_dir.mkdir(exist_ok=True)
for name in SAMPLES:
source = LAB / 'corpus' / (name + '.html')
html = source.read_text()
start = time.perf_counter()
error = None
try:
if tool == 'markitdown':
output = converter.convert(str(source)).text_content
elif tool == 'markdownify':
output = markdownify(html, heading_style='ATX')
elif tool == 'markdownify-selected':
output = markdownify(selected_html(html), heading_style='ATX',
code_language_callback=language)
elif tool == 'markdownify-infer-header':
output = markdownify(html, heading_style='ATX', table_infer_header=True)
else:
output = subprocess.check_output(
['pandoc', '-f', 'html', '-t', 'gfm', '--wrap=none', str(source)], text=True)
except Exception as exc:
output, error = '', repr(exc)
elapsed = round(time.perf_counter() - start, 6)
destination = output_dir / (name + '.md')
destination.write_text(output)
manifest_data['results'].append({
'tool': tool, 'sample': name, 'output': str(destination.relative_to(LAB)),
'elapsed_s': elapsed, 'error': error})
observations.append({'tool': tool, 'sample': name, 'elapsed_s': elapsed,
'error': error, 'sha256': hashlib.sha256(output.encode()).hexdigest()})
manifest.write_text(json.dumps(manifest_data, indent=2) + '\n')
details = {
'executed_at': datetime.datetime.now().astimezone().isoformat(),
'python': platform.python_version(), 'tools': tools,
'pandoc_path': shutil.which('pandoc'),
'beautifulsoup4': version('beautifulsoup4'),
'options': {'markdownify': {'heading_style': 'ATX'},
'markdownify-selected': {'heading_style': 'ATX', 'selector': 'main article > article > body fallback',
'code_language_callback': 'language(pre)'},
'markdownify-infer-header': {'heading_style': 'ATX', 'table_infer_header': True},
'markitdown': {'enable_plugins': False},
'pandoc-gfm': ['-f', 'html', '-t', 'gfm', '--wrap=none']},
'results': observations,
'boundary': 'HTML conversion only; no GitHub.com rendering, no fetching, no JavaScript execution; Markovo upstream snapshot not rerun.'}
(LAB / 'html-run.json').write_text(json.dumps(details, indent=2) + '\n')
print(json.dumps({'python': details['python'], 'tools': tools, 'conversions': len(observations),
'errors': [o for o in observations if o['error']]}, indent=2))
if __name__ == '__main__':
main()