diff --git a/dokogen/generator.py b/dokogen/generator.py index 1520989..4afffb6 100644 --- a/dokogen/generator.py +++ b/dokogen/generator.py @@ -293,6 +293,23 @@ def _cleanup_marker_paras(doc_xml: str) -> str: return etree.tostring(root, xml_declaration=True, encoding='UTF-8', standalone=True).decode('utf-8') +def _subst_var_xml(xml: str, var: str, val: str) -> str: + """Замена {{var}} в XML-фрагменте. + + Пустые значения («—»/«») подсвечиваются жёлтым маркером: значение + выносится в отдельный run с , чтобы + подсветка не затиралась при подстановке на уровне XML (циклы). + """ + token = '{{%s}}' % var + if val not in ('—', ''): + return xml.replace(token, val) + hl_run = ('' + '%s' % val) + return xml.replace(token, + '' + hl_run + + '') + + def _expand_nested_loops(template: str, item: Dict) -> str: """Разворачивает вложенные циклы вида ... данными из item (список словарей). Используется для циклов внутри цикла ИС, @@ -342,8 +359,8 @@ def _expand_nested_loops(template: str, item: Dict) -> str: clone = inner for f, v in sub.items(): val = str(v) if v else '' - clone = clone.replace('{{user_%s}}' % f, val) - clone = clone.replace('{{item.%s}}' % f, val) + clone = _subst_var_xml(clone, 'user_%s' % f, val) + clone = _subst_var_xml(clone, 'item.%s' % f, val) parts.append(clone) result = result[:m_start.start()] + ''.join(parts) + result[m_end.end():] return result @@ -467,13 +484,13 @@ def expand_loops_in_zip(docx_path: str, loops: Dict[str, List[Dict]]) -> str: continue # списки — данные вложенных циклов, не подставляем val_str = str(value) if value else ('—' if value is None else '') # С префиксом item. - clone = clone.replace('{{item.%s}}' % field, val_str) + clone = _subst_var_xml(clone, 'item.%s' % field, val_str) # Без префикса (алиасы {{name}}, {{pd_list}}, {{document}} и т.д.) - clone = clone.replace('{{%s}}' % field, val_str) + clone = _subst_var_xml(clone, '%s' % field, val_str) # Альтернативный синтаксис {{item_поле}} (без точки) - clone = clone.replace('{{item_%s}}' % field, val_str) - clone = clone.replace('{{item.number}}', str(idx)) - clone = clone.replace('{{number}}', str(idx)) + clone = _subst_var_xml(clone, 'item_%s' % field, val_str) + clone = _subst_var_xml(clone, 'item.number', str(idx)) + clone = _subst_var_xml(clone, 'number', str(idx)) expanded_parts.append(clone) if br_in_suffix: expanded_parts.append('') diff --git a/dokogen/ui.py b/dokogen/ui.py index 2de5dbf..f8c69a3 100644 --- a/dokogen/ui.py +++ b/dokogen/ui.py @@ -303,6 +303,28 @@ class DokoGenApp: btn_row.pack(fill=tk.X) ttk.Button(btn_row, text="✓ Применить", command=self._verify_apply).pack(side=tk.LEFT, padx=2) ttk.Button(btn_row, text="Очистить", command=self._verify_clear).pack(side=tk.LEFT, padx=2) + + # Выборочное применение: чекбоксы «что подставлять» + self._apply_flags = { + 'declension': tk.BooleanVar(value=True), + 'short_declension': tk.BooleanVar(value=True), + 'address': tk.BooleanVar(value=True), + 'requisites': tk.BooleanVar(value=True), + 'chief': tk.BooleanVar(value=True), + } + apply_row = ttk.Frame(right) + apply_row.pack(fill=tk.X, pady=(4, 0)) + ttk.Label(apply_row, text="Подставлять:", font=('', 8)).pack(side=tk.LEFT, padx=(0, 4)) + ttk.Checkbutton(apply_row, text="склонения", variable=self._apply_flags['declension'], + font=('', 8)).pack(side=tk.LEFT) + ttk.Checkbutton(apply_row, text="кр. склон.", variable=self._apply_flags['short_declension'], + font=('', 8)).pack(side=tk.LEFT) + ttk.Checkbutton(apply_row, text="адрес", variable=self._apply_flags['address'], + font=('', 8)).pack(side=tk.LEFT) + ttk.Checkbutton(apply_row, text="ИНН/КПП/ОГРН", variable=self._apply_flags['requisites'], + font=('', 8)).pack(side=tk.LEFT) + ttk.Checkbutton(apply_row, text="руковод.", variable=self._apply_flags['chief'], + font=('', 8)).pack(side=tk.LEFT) self._verify_result = None # ---------- ПРОВЕРКА ДАННЫХ (Морфер / DaData / ИИ) ---------- @@ -594,8 +616,15 @@ class DokoGenApp: if not result: return apply_data = result.get('apply', {}) + flags = getattr(self, '_apply_flags', None) + + def want(key): + if flags is None: + return True + return flags[key].get() if key in flags else True + idx_map = {'nomn': 1, 'gent': 2, 'datv': 3, 'accs': 4, 'ablt': 5, 'loct': 6} - if 'declension' in apply_data: + if 'declension' in apply_data and want('declension'): for case, val in apply_data['declension'].items(): code = self._CASE_TO_CODE.get(case) if code and code in self.decl_entries: @@ -603,7 +632,7 @@ class DokoGenApp: self.decl_entries[code].insert(0, val) setattr(self.model, f'company_name_{idx_map[code]}', val) self.log("✅ Склонения полного названия применены") - if 'short_declension' in apply_data: + if 'short_declension' in apply_data and want('short_declension'): for case, val in apply_data['short_declension'].items(): code = self._CASE_TO_CODE.get(case) if code and code in self.short_decl_entries: @@ -619,18 +648,23 @@ class DokoGenApp: if comp.get('short_name'): self.short_name_entry.delete(0, tk.END) self.short_name_entry.insert(0, comp['short_name']) - mapping = {'address': 'address', 'kpp': 'kpp', 'ogrn': 'ogrn', - 'ogrn_date': 'ogrn_date', 'inn': 'inn'} - for data_key, ui_key in mapping.items(): - if comp.get(data_key) and ui_key in self._company_entries: - self._company_entries[ui_key].delete(0, tk.END) - self._company_entries[ui_key].insert(0, comp[data_key]) - if comp.get('chief_position') and 'chief_position' in self._official_entries: - self._official_entries['chief_position'].delete(0, tk.END) - self._official_entries['chief_position'].insert(0, comp['chief_position']) - if comp.get('chief_fio') and 'chief_fio' in self._official_entries: - self._official_entries['chief_fio'].delete(0, tk.END) - self._official_entries['chief_fio'].insert(0, comp['chief_fio']) + if want('address') and comp.get('address'): + if 'address' in self._company_entries: + self._company_entries['address'].delete(0, tk.END) + self._company_entries['address'].insert(0, comp['address']) + if want('requisites'): + for data_key, ui_key in {'kpp': 'kpp', 'ogrn': 'ogrn', + 'ogrn_date': 'ogrn_date', 'inn': 'inn'}.items(): + if comp.get(data_key) and ui_key in self._company_entries: + self._company_entries[ui_key].delete(0, tk.END) + self._company_entries[ui_key].insert(0, comp[data_key]) + if want('chief'): + if comp.get('chief_position') and 'chief_position' in self._official_entries: + self._official_entries['chief_position'].delete(0, tk.END) + self._official_entries['chief_position'].insert(0, comp['chief_position']) + if comp.get('chief_fio') and 'chief_fio' in self._official_entries: + self._official_entries['chief_fio'].delete(0, tk.END) + self._official_entries['chief_fio'].insert(0, comp['chief_fio']) self.log("✅ Реквизиты применены") # ---------- ВКЛАДКА "ИНФОРМАЦИОННЫЕ СИСТЕМЫ" ----------