"""In-place editing of Word paragraphs: only the sentence changes, formatting and special runs stay.""" import io from docx import Document import docx_report as DX import smart as S def _para(*runs): """runs: (text, fmt) with fmt in '', 'b', 'i', 'sup'.""" d = Document() p = d.add_paragraph() for text, fmt in runs: r = p.add_run(text) r.bold = True if fmt == "b" else None r.italic = True if fmt == "i" else None if fmt == "sup": r.font.superscript = True return d, p def _reload(d): buf = io.BytesIO() d.save(buf) return Document(io.BytesIO(buf.getvalue())).paragraphs[0] def test_plain_sentence_is_replaced_and_neighbours_stay(): d, p = _para(("First sentence stays. Second sentence is replaced here. Third stays too.", "")) raw = DX.para_text(p._p) a = raw.index("Second") b = raw.index(" Third") assert S.replace_span(p._p, a, b, "A new second sentence.") assert _reload(d).text == "First sentence stays. A new second sentence. Third stays too." def test_bold_term_and_italic_name_keep_their_format(): d, p = _para(("In this study, a ", ""), ("phase change material (PCM)", "b"), (" is integrated with ", ""), ("ResNet-50", "i"), (" for control. Next sentence.", "")) raw = DX.para_text(p._p) b = raw.index(" Next") new = "This study couples a phase change material (PCM) to ResNet-50 for control." assert S.replace_span(p._p, 0, b, new) q = _reload(d) assert q.text == new + " Next sentence." assert [r.text for r in q.runs if r.bold] == ["phase change material (PCM)"] assert [r.text for r in q.runs if r.italic] == ["ResNet-50"] def test_format_is_dropped_only_when_the_formatted_words_are_gone(): d, p = _para(("The ", ""), ("robust", "b"), (" model works well here.", "")) new = "The sturdy model performs well here." assert S.replace_span(p._p, 0, len(DX.para_text(p._p)), new) q = _reload(d) assert q.text == new and not any(r.bold for r in q.runs) def test_formatted_word_is_not_matched_inside_another_word(): d, p = _para(("An ", ""), ("ion", "b"), (" moves through the cell quickly.", "")) new = "Through the cell, motion of an ion is quick." assert S.replace_span(p._p, 0, len(DX.para_text(p._p)), new) q = _reload(d) assert q.text == new and [r.text for r in q.runs if r.bold] == ["ion"] assert "mot" in "".join(r.text for r in q.runs if not r.bold) def test_superscript_citation_is_never_edited(): d, p = _para(("The difference stays below 2.1 degrees", ""), ("12", "sup"), (". Next sentence here.", "")) raw = DX.para_text(p._p) end = raw.index(". Next") + 1 assert not S.replace_span(p._p, 0, end, "Anything at all.") # crosses the superscript: refused sub = S.safe_subspan(p._p, 0, end) assert raw[sub[0]:sub[1]] == "The difference stays below 2.1 degrees" assert S.lock_reason(p._p, 0, end) == "superscript / subscript" assert S.replace_span(p._p, sub[0], sub[1], "The gap remains under 2.1 degrees") q = _reload(d) assert q.text == "The gap remains under 2.1 degrees12. Next sentence here." assert [r.text for r in q.runs if r.font.superscript] == ["12"] def test_clean_stem_handles_both_path_styles(): assert S._clean_stem("/tmp/x/My thesis_humanized.docx") == "My thesis" assert S._clean_stem("C:\\Users\\me\\Docs\\paper_paraphrased_humanized.DOCX") == "paper"