feat: клавиши (Ctrl/Alt/Del) в исключения + подсветка маркером только переменной
- IGNORED_OLD_VARS: <Ctrl>, <Alt>, <Del> и др. клавиши больше не
считаются незаменёнными переменными (7.6 Инструкция Пользователя)
- Подсветка: вместо красного цвета текста — красный маркер
(w:highlight) на самой переменной, а не на всём абзаце
- OLD_VAR_MAP: ShortCompanyName1-6 (склонения краткого названия),
IspdnName → {{is_name_1}}
This commit is contained in:
+62
-19
@@ -30,6 +30,17 @@ VAR_PATTERN = re.compile(r'\{\{[^}]+\}\}')
|
||||
OLD_VAR_PATTERN = re.compile(r'<[A-Za-z0-9_]+>')
|
||||
UNMATCHED_PATTERN = re.compile(r'\{\{[^}]+\}\}|<[A-Za-z0-9_]+>')
|
||||
|
||||
# «Переменные», которые на самом деле не переменные, а клавиши в тексте
|
||||
# инструкций (Ctrl+Alt+Del и т.п.) — их не считаем незаменёнными
|
||||
IGNORED_OLD_VARS = {
|
||||
'<Ctrl>', '<Alt>', '<Del>', '<Delete>', '<Shift>', '<Enter>', '<Tab>',
|
||||
'<Esc>', '<Escape>', '<Insert>', '<Home>', '<End>', '<PageUp>', '<PageDown>',
|
||||
'<Backspace>', '<Space>', '<CapsLock>', '<NumLock>', '<ScrollLock>',
|
||||
'<PrintScreen>', '<Pause>', '<Break>', '<Up>', '<Down>', '<Left>', '<Right>',
|
||||
'<F1>', '<F2>', '<F3>', '<F4>', '<F5>', '<F6>', '<F7>', '<F8>', '<F9>',
|
||||
'<F10>', '<F11>', '<F12>',
|
||||
}
|
||||
|
||||
LOOP_START = re.compile(r'(?:<!--|<!--)\s*loop:(\w+)\s*(?:-->|-->)')
|
||||
LOOP_END = re.compile(r'(?:<!--|<!--)\s*loop_end\s*(?:-->|-->)')
|
||||
|
||||
@@ -61,6 +72,14 @@ OLD_VAR_MAP = {
|
||||
'<DocumentNumber>': '{{document_number}}',
|
||||
'<DocumentDate>': '{{document_date}}',
|
||||
'<Date>': '{{date}}',
|
||||
# Краткое наименование по падежам (старые шаблоны)
|
||||
'<ShortCompanyName1>': '{{company_short_name}}',
|
||||
'<ShortCompanyName2>': '{{company_short_name_genitive}}',
|
||||
'<ShortCompanyName3>': '{{company_short_name_dative}}',
|
||||
'<ShortCompanyName4>': '{{company_short_name_accs}}',
|
||||
'<ShortCompanyName5>': '{{company_short_name_ablt}}',
|
||||
'<ShortCompanyName6>': '{{company_short_name_loct}}',
|
||||
'<IspdnName>': '{{is_name_1}}',
|
||||
}
|
||||
|
||||
|
||||
@@ -342,42 +361,64 @@ def _strip_prooferr(doc):
|
||||
|
||||
|
||||
def _highlight_unmatched(doc):
|
||||
"""Подсвечивает незаменённые переменные красным."""
|
||||
"""Выделяет незаменённые переменные красным маркером (только саму переменную)."""
|
||||
ns = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'
|
||||
|
||||
def _highlight_run(run):
|
||||
rpr = run._element.find(f'{{{ns}}}rPr')
|
||||
def _apply_highlight(run_el):
|
||||
rpr = run_el.find(f'{{{ns}}}rPr')
|
||||
if rpr is None:
|
||||
rpr = OxmlElement('w:rPr')
|
||||
run._element.insert(0, rpr)
|
||||
color = OxmlElement('w:color')
|
||||
color.set(f'{{{ns}}}val', 'FF0000')
|
||||
rpr.append(color)
|
||||
run_el.insert(0, rpr)
|
||||
hl = OxmlElement('w:highlight')
|
||||
hl.set(f'{{{ns}}}val', 'red')
|
||||
rpr.append(hl)
|
||||
|
||||
def _process_para(para):
|
||||
full = para.text
|
||||
matches = [m for m in UNMATCHED_PATTERN.finditer(full)
|
||||
if m.group(0) not in IGNORED_OLD_VARS]
|
||||
if not matches:
|
||||
return
|
||||
|
||||
# Пересобираем параграф: обычный текст и переменные отдельными run'ами,
|
||||
# переменные — с красным маркером. Сохраняем стиль первого run.
|
||||
first = para.runs[0] if para.runs else None
|
||||
para.clear()
|
||||
pos = 0
|
||||
for m in matches:
|
||||
if m.start() > pos:
|
||||
run = para.add_run(full[pos:m.start()])
|
||||
if first is not None:
|
||||
_copy_run_style(first, run)
|
||||
run = para.add_run(m.group(0))
|
||||
if first is not None:
|
||||
_copy_run_style(first, run)
|
||||
_apply_highlight(run._element)
|
||||
pos = m.end()
|
||||
if pos < len(full):
|
||||
run = para.add_run(full[pos:])
|
||||
if first is not None:
|
||||
_copy_run_style(first, run)
|
||||
|
||||
for para in doc.paragraphs:
|
||||
for run in para.runs:
|
||||
if UNMATCHED_PATTERN.search(run.text or ''):
|
||||
_highlight_run(run)
|
||||
_process_para(para)
|
||||
|
||||
for table in doc.tables:
|
||||
for row in table.rows:
|
||||
for cell in row.cells:
|
||||
for para in cell.paragraphs:
|
||||
for run in para.runs:
|
||||
if UNMATCHED_PATTERN.search(run.text or ''):
|
||||
_highlight_run(run)
|
||||
_process_para(para)
|
||||
|
||||
for section in doc.sections:
|
||||
for hf in [section.header, section.footer]:
|
||||
if hf:
|
||||
for hf in [section.header, section.footer,
|
||||
section.first_page_header, section.first_page_footer]:
|
||||
if hf is not None:
|
||||
for para in hf.paragraphs:
|
||||
for run in para.runs:
|
||||
if UNMATCHED_PATTERN.search(run.text or ''):
|
||||
_highlight_run(run)
|
||||
_process_para(para)
|
||||
|
||||
|
||||
def _log_unmatched(doc, replacements=None, log_func=None):
|
||||
"""Логирует незаменённые переменные."""
|
||||
"""Логирует незаменённые переменные (без клавиш-исключений)."""
|
||||
if not log_func:
|
||||
return
|
||||
unmatched = []
|
||||
@@ -385,6 +426,8 @@ def _log_unmatched(doc, replacements=None, log_func=None):
|
||||
for para in doc.paragraphs:
|
||||
for m in UNMATCHED_PATTERN.finditer(para.text):
|
||||
var = m.group(0)
|
||||
if var in IGNORED_OLD_VARS:
|
||||
continue
|
||||
if var not in seen:
|
||||
seen.add(var)
|
||||
unmatched.append(var)
|
||||
|
||||
Reference in New Issue
Block a user