track_changes_importer.py
Helper script for the track_changes workflow.
import sys
import docx
from docx.oxml.ns import qn
def is_bold(run_element):
"""Check if a Word run element has bold formatting (w:b in w:rPr)."""
rPr = run_element.find(qn('w:rPr'))
if rPr is None:
return False
b = rPr.find(qn('w:b'))
if b is None:
return False
# w:b with val="0" or val="false" means explicitly NOT bold
val = b.get(qn('w:val'))
if val is not None and val.lower() in ('0', 'false'):
return False
return True
def get_run_text(run_element):
"""Extract text from w:t elements inside a run."""
return ''.join(t.text for t in run_element.findall(qn('w:t')) if t.text)
def extract_runs(para_element):
"""Extract (text, is_bold) tuples from a paragraph.
Includes normal runs and inserted content (w:ins).
Skips deleted content (w:del) since that text was removed.
Handles hyperlinks (w:hyperlink) which also contain runs.
"""
runs = []
for child in para_element:
if child.tag == qn('w:del'):
continue
elif child.tag == qn('w:r'):
text = get_run_text(child)
if text:
runs.append((text, is_bold(child)))
elif child.tag in (qn('w:ins'), qn('w:hyperlink')):
for r in child.findall(qn('w:r')):
text = get_run_text(r)
if text:
runs.append((text, is_bold(r)))
return runs
def rebuild_line(runs):
"""Reconstruct a text line from runs, wrapping bold runs in ** markers."""
if not runs:
return ''
parts = []
prev_bold = False
for text, bold in runs:
if bold != prev_bold:
parts.append('**')
prev_bold = bold
parts.append(text)
if prev_bold:
parts.append('**')
return ''.join(parts)
def extract(docx_file, out):
doc = docx.Document(docx_file)
with open(out, 'w', encoding='utf-8') as f:
for p in doc.paragraphs:
runs = extract_runs(p._element)
line = rebuild_line(runs)
f.write(line + '\n')
if __name__ == '__main__':
extract(*sys.argv[1:3])