from __future__ import annotations import re from pathlib import Path from docx import Document from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.table import WD_TABLE_ALIGNMENT, WD_CELL_VERTICAL_ALIGNMENT from docx.oxml import OxmlElement from docx.oxml.ns import qn from docx.shared import Inches, Pt, RGBColor ROOT = Path(__file__).resolve().parents[1] SOURCE = ROOT / "docs" / "final_report_draft.md" OUTPUT = ROOT / "deliverables" / "Turkish_Legal_RAG_Final_Report.docx" def set_cell_shading(cell, fill: str) -> None: tc_pr = cell._tc.get_or_add_tcPr() shd = OxmlElement("w:shd") shd.set(qn("w:fill"), fill) tc_pr.append(shd) def set_cell_margins(cell, top=80, start=120, bottom=80, end=120) -> None: tc = cell._tc tc_pr = tc.get_or_add_tcPr() tc_mar = tc_pr.first_child_found_in("w:tcMar") if tc_mar is None: tc_mar = OxmlElement("w:tcMar") tc_pr.append(tc_mar) for margin, value in [("top", top), ("start", start), ("bottom", bottom), ("end", end)]: node = tc_mar.find(qn(f"w:{margin}")) if node is None: node = OxmlElement(f"w:{margin}") tc_mar.append(node) node.set(qn("w:w"), str(value)) node.set(qn("w:type"), "dxa") def style_document(doc: Document) -> None: section = doc.sections[0] section.top_margin = Inches(1) section.bottom_margin = Inches(1) section.left_margin = Inches(1) section.right_margin = Inches(1) styles = doc.styles normal = styles["Normal"] normal.font.name = "Calibri" normal.font.size = Pt(11) normal.paragraph_format.space_after = Pt(6) normal.paragraph_format.line_spacing = 1.10 title = styles["Title"] title.font.name = "Calibri" title.font.size = Pt(22) title.font.bold = True title.font.color.rgb = RGBColor(11, 37, 69) title.paragraph_format.space_after = Pt(8) for style_name, size, color in [ ("Heading 1", 16, RGBColor(46, 116, 181)), ("Heading 2", 13, RGBColor(46, 116, 181)), ("Heading 3", 12, RGBColor(31, 77, 120)), ]: style = styles[style_name] style.font.name = "Calibri" style.font.size = Pt(size) style.font.bold = True style.font.color.rgb = color style.paragraph_format.space_before = Pt(10) style.paragraph_format.space_after = Pt(5) def add_table(doc: Document, rows: list[list[str]]) -> None: if not rows: return table = doc.add_table(rows=len(rows), cols=len(rows[0])) table.alignment = WD_TABLE_ALIGNMENT.CENTER table.style = "Table Grid" for r_idx, row in enumerate(rows): for c_idx, value in enumerate(row): cell = table.cell(r_idx, c_idx) cell.text = value.strip() cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER set_cell_margins(cell) for paragraph in cell.paragraphs: paragraph.paragraph_format.space_after = Pt(0) for run in paragraph.runs: run.font.size = Pt(9) run.font.name = "Calibri" if r_idx == 0: run.font.bold = True if r_idx == 0: set_cell_shading(cell, "F2F4F7") doc.add_paragraph() def parse_table(lines: list[str], start: int) -> tuple[list[list[str]], int]: rows = [] i = start while i < len(lines) and lines[i].strip().startswith("|"): raw = lines[i].strip() parts = [part.strip() for part in raw.strip("|").split("|")] if not all(re.fullmatch(r":?-{3,}:?", part or "") for part in parts): rows.append(parts) i += 1 return rows, i def build() -> None: doc = Document() style_document(doc) title = doc.add_paragraph(style="Title") title.alignment = WD_ALIGN_PARAGRAPH.CENTER title.add_run("Turkish Legal RAG: Base vs Fine-Tuned RAG Evaluation") meta = doc.add_paragraph() meta.alignment = WD_ALIGN_PARAGRAPH.CENTER run = meta.add_run("CENG493 Term Project - Final Technical Report") run.italic = True run.font.color.rgb = RGBColor(85, 85, 85) doc.add_paragraph() lines = SOURCE.read_text(encoding="utf-8").splitlines() in_code = False code_buffer: list[str] = [] i = 0 while i < len(lines): line = lines[i].rstrip() stripped = line.strip() if stripped.startswith("```"): if in_code: para = doc.add_paragraph() para.style = doc.styles["No Spacing"] code_run = para.add_run("\n".join(code_buffer)) code_run.font.name = "Consolas" code_run.font.size = Pt(9) code_buffer = [] in_code = False else: in_code = True i += 1 continue if in_code: code_buffer.append(line) i += 1 continue if not stripped: i += 1 continue if stripped.startswith("|"): rows, i = parse_table(lines, i) add_table(doc, rows) continue if stripped.startswith("# "): # The title is already rendered as a cover title. i += 1 continue if stripped.startswith("## "): doc.add_paragraph(stripped[3:], style="Heading 1") elif stripped.startswith("### "): doc.add_paragraph(stripped[4:], style="Heading 2") elif stripped.startswith("- "): doc.add_paragraph(stripped[2:], style="List Bullet") elif re.match(r"^\d+\. ", stripped): doc.add_paragraph(re.sub(r"^\d+\. ", "", stripped), style="List Number") else: doc.add_paragraph(stripped) i += 1 footer = doc.sections[0].footer.paragraphs[0] footer.alignment = WD_ALIGN_PARAGRAPH.CENTER footer.add_run("Turkish Legal RAG - CENG493").font.size = Pt(9) OUTPUT.parent.mkdir(parents=True, exist_ok=True) doc.save(OUTPUT) print(f"Wrote {OUTPUT}") if __name__ == "__main__": build()