SPB Git

spb/wp27_uqo

Public
5.4 KB · 135 lines python
1"""Flatten a LaTeX working paper and resolve numbering from main.aux so that2pandoc produces a Word file with correct section/table/figure/equation numbers."""3import re, sys, pathlib45PAPER = pathlib.Path(sys.argv[1]).resolve()6OUT = pathlib.Path(sys.argv[2])7warn = lambda *a: print("WARN:", *a, file=sys.stderr)89def strip_comments(txt):10    out = []11    for line in txt.splitlines():12        if re.match(r'^\s*%', line):13            continue14        # drop trailing unescaped % comments15        line = re.sub(r'(?<!\\)%.*$', '', line)16        out.append(line)17    return "\n".join(out) + "\n"1819def inline(path):20    txt = strip_comments(path.read_text())21    def rep(m):22        name = m.group(1)23        p = PAPER / (name if name.endswith('.tex') else name + '.tex')24        if not p.exists():25            warn("missing input", p); return m.group(0)26        return inline(p)27    return re.sub(r'\\input\{([^}]+)\}', rep, txt)2829def brace_arg(s, i):30    """s[i] == '{' -> return (content, index after closing brace)"""31    assert s[i] == '{', s[i:i+20]32    depth = 033    for j in range(i, len(s)):34        if s[j] == '{' and (j == 0 or s[j-1] != '\\'): depth += 135        elif s[j] == '}' and s[j-1] != '\\':36            depth -= 137            if depth == 0:38                return s[i+1:j], j+139    raise ValueError("unbalanced braces")4041# ---------------------------------------------------------------- aux42aux = (PAPER / 'main.aux').read_text()43labels = {}44for m in re.finditer(r'\\newlabel\{([^}]+)\}\{', aux):45    outer, _ = brace_arg(aux, m.end()-1)46    num, _ = brace_arg(outer, 0)47    labels[m.group(1)] = num.strip()48toc = []49for m in re.finditer(r'\\contentsline \{(section|subsection|subsubsection)\}\{\\numberline \{([^}]*)\}(.*?)\}\{\d+\}\{', aux):50    toc.append((m.group(1), m.group(2), m.group(3)))51print(f"aux: {len(labels)} labels, {len(toc)} numbered headings")5253# ---------------------------------------------------------------- flatten54tex = inline(PAPER / 'main.tex')5556# titlepage: pandoc cannot evaluate \ifx -> keep the \else branch only57tex = re.sub(r'\\ifx\\WPsubtitle\\empty\\else(.*?)\\fi', r'\1', tex, flags=re.S)5859# appendix environment / counters that pandoc does not understand60tex = re.sub(r'\\(begin|end)\{appendices\}', '', tex)61tex = re.sub(r'\\renewcommand\{\\the(table|figure)\}\{[^{}]*(\{[^{}]*\})*[^{}]*\}', '', tex)62tex = re.sub(r'\\setcounter\{(table|figure)\}\{0\}', '', tex)6364# ---------------------------------------------------------------- headings65pos = 0; out = []; ti = 066pat = re.compile(r'\\(section|subsection|subsubsection)\{')67while True:68    m = pat.search(tex, pos)69    if not m: break70    title, end = brace_arg(tex, m.end()-1)71    out.append(tex[pos:m.start()])72    lvl = m.group(1)73    if ti < len(toc) and toc[ti][0] == lvl:74        num = toc[ti][1]; ti += 175        if re.sub(r'\W', '', toc[ti-1][2])[:12] != re.sub(r'\W', '', title)[:12]:76            warn("heading mismatch:", repr(toc[ti-1][2][:40]), "vs", repr(title[:40]))77        out.append(f"\\{lvl}{{{num}~{title}}}")78    else:79        warn("unnumbered heading kept:", lvl, title[:40])80        out.append(tex[m.start():end])81    pos = end82out.append(tex[pos:]); tex = "".join(out)83if ti != len(toc): warn(f"only {ti}/{len(toc)} toc entries consumed")8485# ---------------------------------------------------------------- floats: caption prefix86def fix_float(m):87    kind, body = m.group(1), m.group(2)88    labs = [l for l in re.findall(r'\\label\{([^}]+)\}', body) if l in labels]89    if not labs:90        warn("float without known label", body[:60]); return m.group(0)91    num = labels[labs[0]]92    word = 'Table' if kind == 'table' else 'Figure'93    cm = re.search(r'\\caption(\[[^\]]*\])?\{', body)94    if not cm: return m.group(0)95    cap, end = brace_arg(body, cm.end()-1)96    body = body[:cm.start()] + f"\\caption{{{word} {num}: {cap}}}" + body[end:]97    return f"\\begin{{{kind}}}{body}\\end{{{kind}}}"98tex = re.sub(r'\\begin\{(table|figure)\}(.*?)\\end\{\1\}', fix_float, tex, flags=re.S)99100# ---------------------------------------------------------------- equations: number tag101def fix_eq(m):102    body = m.group(1)103    lm = re.search(r'\\label\{([^}]+)\}', body)104    if lm and lm.group(1) in labels:105        body = body.replace(lm.group(0), '')106        body = body.rstrip() + f"\\qquad\\text{{({labels[lm.group(1)]})}}\n"107    return "\\begin{equation}" + body + "\\end{equation}"108tex = re.sub(r'\\begin\{equation\}(.*?)\\end\{equation\}', fix_eq, tex, flags=re.S)109110# ---------------------------------------------------------------- cross-references111missing = set()112def ref(m):113    k = m.group(2)114    if k not in labels:115        missing.add(k); return '??'116    return f"({labels[k]})" if m.group(1) == 'eqref' else labels[k]117tex = re.sub(r'\\(eqref|ref)\{([^}]+)\}', ref, tex)118if missing: warn("unresolved labels:", sorted(missing))119tex = re.sub(r'\\label\{[^}]+\}', '', tex)120121# booktabs partial rules have no Word equivalent and leak as text122tex = re.sub(r'\\cmidrule(\([^)]*\))?\{[^}]*\}', '', tex)123124# ---------------------------------------------------------------- pandoc shims125shims = r"""126\newcommand{\num}[1]{#1}127\renewcommand{\tablelegend}[1]{\par\textit{Notes:} #1}128\renewcommand{\tstat}[1]{(\textit{t}=#1)}129\newcommand{\textperiodcentered}{·}130"""131tex = tex.replace("\\begin{document}", shims + "\\begin{document}", 1)132tex = re.sub(r'(\\newcommand\{\\WPauthor\}\{[^}]*)\}', lambda m: m.group(1).replace('\\quad', '~~~~') + '}', tex)133OUT.write_text(tex)134print("wrote", OUT, len(tex), "chars")135
Files · main
Menu

Theme