spb/firc0001_uqo
Public
1import sys, re, datetime2from docx import Document3from docx.shared import Pt4from docx.enum.text import WD_ALIGN_PARAGRAPH5path = sys.argv[1]; doc = Document(path)6paras = doc.paragraphs7norm = lambda p: re.sub(r"\s+", " ", p.text.replace(" ", " ")).strip()8title = "From Corporate Disclosures to a Russell 3000 Strategic Project Database"9# --- title page: everything before the Abstract block is centred10i_abs = next(i for i, p in enumerate(paras) if norm(p) == "Abstract")11for p in paras[:i_abs - 1]:12 p.alignment = WD_ALIGN_PARAGRAPH.CENTER13 if norm(p) == title:14 for r in p.runs: r.font.size = Pt(18); r.font.bold = True15 elif norm(p).startswith("Identification, Measurement"):16 for r in p.runs: r.font.size = Pt(13)17# --- move References (pandoc appends it at the end) before the appendix, as in the PDF18h1 = [p for p in paras if p.style.name == "Heading 1"]19refs = next(p for p in h1 if norm(p) == "References")20appA = next(p for p in h1 if re.match(r"^A [A-Z]", norm(p)))21body = doc.element.body; kids = list(body)22i_refs, i_app = kids.index(refs._p), kids.index(appA._p)23moved = [k for k in kids[i_refs:] if k.tag.endswith("}sectPr") is False]24for k in moved: appA._p.addprevious(k)25# --- page breaks where the LaTeX source has \newpage26breaks = 027paras[i_abs - 1].paragraph_format.page_break_before = True; breaks += 128for p in h1:29 t = norm(p)30 if t.startswith("1 Introduction") or t == "References" or p is appA:31 p.paragraph_format.page_break_before = True; breaks += 132# --- document properties33cp = doc.core_properties34cp.title = title35cp.subject = "Identification, Measurement, Validation, and Research Applications"36cp.author = "Manel Kammoun; Djerrycharli Tandjambianda; Simon-Pierre Boucher"37cp.keywords = "SEC filings, text as data, corporate investment, strategic projects, information extraction, large language models, entity resolution, Russell 3000"38cp.comments = "UQO Working Paper No. 0001 (FIRC-funded), Version 1.0, October 28, 2026 (working draft under validation)"39cp.created = cp.modified = datetime.datetime(2026, 10, 28, 12, 0)40# --- base fonts closer to the PDF (Times)41from docx.oxml.ns import qn42for st in doc.styles:43 try:44 if st.type == 1 and st.font is not None and (st.name == "Normal" or st.name.startswith("Heading") or st.name in ("Title", "Body Text", "First Paragraph", "Compact", "Caption", "Bibliography", "Block Text")):45 st.font.name = "Times New Roman"46 rpr = st.element.get_or_add_rPr(); rf = rpr.find(qn("w:rFonts"))47 if rf is None:48 rf = rpr.makeelement(qn("w:rFonts"), {}); rpr.append(rf)49 for a in ("w:ascii", "w:hAnsi", "w:eastAsia", "w:cs"): rf.set(qn(a), "Times New Roman")50 if st.name in ("Normal", "Body Text", "First Paragraph", "Compact"): st.font.size = Pt(12)51 except Exception:52 pass53# --- wide tables: smaller font and tight cell padding so that 8-10 column tables fit the page width54from docx.shared import Inches55from docx.oxml import OxmlElement56for sec in doc.sections:57 sec.left_margin = sec.right_margin = Inches(0.85)58for tbl in doc.tables:59 ncol = max(len(r.cells) for r in tbl.rows)60 if ncol >= 7:61 size = Pt(7) if ncol >= 9 else Pt(8)62 tbl.autofit = True63 for row in tbl.rows:64 for cell in row.cells:65 for para in cell.paragraphs:66 para.paragraph_format.space_after = Pt(0); para.paragraph_format.space_before = Pt(0)67 for run in para.runs: run.font.size = size68 tcPr = cell._tc.get_or_add_tcPr(); mar = OxmlElement("w:tcMar")69 for side in ("left", "right"):70 el = OxmlElement(f"w:{side}"); el.set(qn("w:w"), "30"); el.set(qn("w:type"), "dxa"); mar.append(el)71 tcPr.append(mar)72# --- proportional column widths for wide tables (pandoc emits equal 0.55-inch grid columns)73TOTAL = 9792 # twips: 8.5in - 2 x 0.85in margins74for tbl in doc.tables:75 ncol = max(len(r.cells) for r in tbl.rows)76 if ncol < 7: continue77 lens = [1] * ncol78 for row in tbl.rows:79 for j, cell in enumerate(row.cells[:ncol]):80 lens[j] = max(lens[j], min(28, max((len(w) for w in cell.text.split()), default=1)))81 weights = [max(l, 4) for l in lens]; tot = sum(weights); widths = [int(TOTAL * wgt / tot) for wgt in weights]82 grid = tbl._tbl.find(qn("w:tblGrid"))83 if grid is not None:84 for g, wd in zip(grid.findall(qn("w:gridCol")), widths): g.set(qn("w:w"), str(wd))85 tblPr = tbl._tbl.tblPr86 for e in tblPr.findall(qn("w:tblW")): e.set(qn("w:type"), "dxa"); e.set(qn("w:w"), str(TOTAL))87 for e in tblPr.findall(qn("w:tblLayout")): e.set(qn("w:type"), "fixed")88 for row in tbl.rows:89 for j, cell in enumerate(row.cells[:ncol]):90 tcPr = cell._tc.get_or_add_tcPr(); tcW = tcPr.find(qn("w:tcW"))91 if tcW is None: tcW = OxmlElement("w:tcW"); tcPr.insert(0, tcW)92 tcW.set(qn("w:type"), "dxa"); tcW.set(qn("w:w"), str(widths[j]))93doc.save(path)94print(f"post-processed: {i_abs-1} title-page paragraphs centred, {len(moved)} reference paragraphs moved before appendix, {breaks} page breaks, properties set")95