diff --git a/111/analysis_result.json b/111/analysis_result.json index d834b0d..732cb45 100644 --- a/111/analysis_result.json +++ b/111/analysis_result.json @@ -5,8 +5,8 @@ "technical_count": 6, "commercial_count": 3, "deviation_count": 0, - "chapter_count": 8, - "execution_time": 259.9994738101959, + "chapter_count": 7, + "execution_time": 415.01412439346313, "warnings": [ "AI审查: 5条优化建议" ], @@ -58,79 +58,29 @@ "deviation_items": [], "chapters": [ { - "id": "evaluation_index", + "id": "chapter_1", "title": "评标索引表(技术评分完全对应)", "level": 1, - "score": null, - "template_placeholder": "{{evaluation_index_content}}", + "score": 0.0, + "template_placeholder": null, "children": [] }, { - "id": "chapter_1_after_sales_service", - "title": "售后服务", - "level": 1, - "score": 3.0, - "template_placeholder": null, - "children": [ - { - "id": "chapter_2_1_warranty_service", - "title": "质保期服务承诺", - "level": 2, - "score": 0.0, - "template_placeholder": null, - "children": [ - { - "id": "chapter_3_1_warranty_scope", - "title": "质保期限及范围", - "level": 3, - "score": 0.0, - "template_placeholder": null, - "children": [] - }, - { - "id": "chapter_3_2_service_response", - "title": "质保期内服务响应机制", - "level": 3, - "score": 0.0, - "template_placeholder": null, - "children": [] - }, - { - "id": "chapter_3_3_fault_handling", - "title": "质保期故障处理流程", - "level": 3, - "score": 0.0, - "template_placeholder": null, - "children": [] - }, - { - "id": "chapter_3_4_service_guarantee", - "title": "质保期服务保障措施", - "level": 3, - "score": 0.0, - "template_placeholder": null, - "children": [] - } - ] - } - ] - }, - { - "id": "chapter_1_equipment_specification", - "title": "设备规格", + "id": "chapter_2", + "title": "设备规格 (8.0分)", "level": 1, "score": 8.0, "template_placeholder": null, "children": [ { - "id": "chapter_2_1_hardware_config", + "id": "chapter_2_1", "title": "硬件配置方案", "level": 2, "score": 0.0, "template_placeholder": null, "children": [ { - "id": "chapter_3_1_core_hardware", + "id": "chapter_2_1_1", "title": "核心硬件技术参数", "level": 3, "score": 0.0, @@ -138,7 +88,7 @@ "children": [] }, { - "id": "chapter_3_2_performance_indicators", + "id": "chapter_2_1_2", "title": "设备性能指标说明", "level": 3, "score": 0.0, @@ -146,16 +96,16 @@ "children": [] }, { - "id": "chapter_3_3_hardware_compatibility", - "title": "硬件兼容性与扩展性", + "id": "chapter_2_1_3", + "title": "硬件兼容性分析", "level": 3, "score": 0.0, "template_placeholder": null, "children": [] }, { - "id": "chapter_3_4_config_optimization", - "title": "硬件配置优化方案", + "id": "chapter_2_1_4", + "title": "配置方案优势阐述", "level": 3, "score": 0.0, "template_placeholder": null, @@ -166,71 +116,62 @@ ] }, { - "id": "chapter_1_software_design", - "title": "软件功能设计", + "id": "chapter_3", + "title": "技术方案 (13.0分)", "level": 1, - "score": 0.0, + "score": 13.0, "template_placeholder": null, "children": [ { - "id": "chapter_2_1_software_functions", + "id": "chapter_3_1", "title": "软件功能设计", "level": 2, "score": 0.0, "template_placeholder": null, "children": [ { - "id": "chapter_3_1_core_modules", - "title": "核心功能模块", + "id": "chapter_3_1_1", + "title": "核心功能模块设计", "level": 3, "score": 0.0, "template_placeholder": null, "children": [] }, { - "id": "chapter_3_2_user_interaction", - "title": "用户交互功能", + "id": "chapter_3_1_2", + "title": "用户交互界面设计", "level": 3, "score": 0.0, "template_placeholder": null, "children": [] }, { - "id": "chapter_3_3_data_management", - "title": "数据管理功能", + "id": "chapter_3_1_3", + "title": "数据处理与分析功能", "level": 3, "score": 0.0, "template_placeholder": null, "children": [] }, { - "id": "chapter_3_4_system_management", - "title": "系统管理功能", + "id": "chapter_3_1_4", + "title": "系统集成与接口设计", "level": 3, "score": 0.0, "template_placeholder": null, "children": [] } ] - } - ] - }, - { - "id": "chapter_1_system_architecture", - "title": "系统架构设计", - "level": 1, - "score": 0.0, - "template_placeholder": null, - "children": [ + }, { - "id": "chapter_2_1_architecture_design", + "id": "chapter_3_2", "title": "系统架构设计", "level": 2, "score": 0.0, "template_placeholder": null, "children": [ { - "id": "chapter_3_1_overall_architecture", + "id": "chapter_3_2_1", "title": "总体架构设计", "level": 3, "score": 0.0, @@ -238,16 +179,16 @@ "children": [] }, { - "id": "chapter_3_2_technology_selection", - "title": "技术架构选型", + "id": "chapter_3_2_2", + "title": "技术选型与框架设计", "level": 3, "score": 0.0, "template_placeholder": null, "children": [] }, { - "id": "chapter_3_3_deployment_architecture", - "title": "系统部署架构", + "id": "chapter_3_2_3", + "title": "性能与扩展性设计", "level": 3, "score": 0.0, "template_placeholder": null, @@ -258,87 +199,54 @@ ] }, { - "id": "chapter_1_project_implementation", - "title": "项目实施方案", - "level": 1, - "score": 0.0, - "template_placeholder": null, - "children": [ - { - "id": "chapter_2_1_project_plan", - "title": "项目计划", - "level": 2, - "score": 0.0, - "template_placeholder": null, - "children": [] - }, - { - "id": "chapter_2_2_implementation_team", - "title": "实施团队", - "level": 2, - "score": 0.0, - "template_placeholder": null, - "children": [] - }, - { - "id": "chapter_2_3_schedule_arrangement", - "title": "进度安排", - "level": 2, - "score": 0.0, - "template_placeholder": null, - "children": [] - } - ] - }, - { - "id": "chapter_1_quality_security", - "title": "质量安全", + "id": "chapter_4", + "title": "质量安全 (5.0分)", "level": 1, "score": 5.0, "template_placeholder": null, "children": [ { - "id": "chapter_2_1_network_security", + "id": "chapter_4_1", "title": "网络安全防护体系", "level": 2, "score": 0.0, "template_placeholder": null, "children": [ { - "id": "chapter_3_1_border_security", - "title": "网络边界安全防护", + "id": "chapter_4_1_1", + "title": "网络安全架构设计", "level": 3, "score": 0.0, "template_placeholder": null, "children": [] }, { - "id": "chapter_3_2_intrusion_detection", - "title": "入侵检测与防御系统", + "id": "chapter_4_1_2", + "title": "网络安全防护措施", "level": 3, "score": 0.0, "template_placeholder": null, "children": [] }, { - "id": "chapter_3_3_security_audit", - "title": "网络安全审计与监控", + "id": "chapter_4_1_3", + "title": "网络安全监控与预警", "level": 3, "score": 0.0, "template_placeholder": null, "children": [] }, { - "id": "chapter_3_4_access_control", - "title": "网络访问控制策略", + "id": "chapter_4_1_4", + "title": "网络安全应急响应", "level": 3, "score": 0.0, "template_placeholder": null, "children": [] }, { - "id": "chapter_3_5_security_emergency", - "title": "网络安全应急预案", + "id": "chapter_4_1_5", + "title": "网络安全管理制度", "level": 3, "score": 0.0, "template_placeholder": null, @@ -349,21 +257,21 @@ ] }, { - "id": "chapter_1_compliance_response", - "title": "合规响应", + "id": "chapter_5", + "title": "合规响应 (3.0分)", "level": 1, "score": 3.0, "template_placeholder": null, "children": [ { - "id": "chapter_2_1_technical_strength", + "id": "chapter_5_1", "title": "技术实力", "level": 2, "score": 0.0, "template_placeholder": null, "children": [ { - "id": "chapter_3_1_core_team", + "id": "chapter_5_1_1", "title": "核心技术团队介绍", "level": 3, "score": 0.0, @@ -371,24 +279,214 @@ "children": [] }, { - "id": "chapter_3_2_technical_certification", - "title": "技术资质与认证", + "id": "chapter_5_1_2", + "title": "技术研发能力展示", "level": 3, "score": 0.0, "template_placeholder": null, "children": [] }, { - "id": "chapter_3_3_rd_capability", - "title": "技术研发能力", + "id": "chapter_5_1_3", + "title": "技术设备与工具配置", "level": 3, "score": 0.0, "template_placeholder": null, "children": [] }, { - "id": "chapter_3_4_implementation_experience", - "title": "技术实施经验", + "id": "chapter_5_1_4", + "title": "技术成果与专利情况", + "level": 3, + "score": 0.0, + "template_placeholder": null, + "children": [] + }, + { + "id": "chapter_5_1_5", + "title": "技术质量管理体系", + "level": 3, + "score": 0.0, + "template_placeholder": null, + "children": [] + } + ] + } + ] + }, + { + "id": "chapter_6", + "title": "培训服务", + "level": 1, + "score": 0.0, + "template_placeholder": null, + "children": [ + { + "id": "chapter_6_1", + "title": "培训计划", + "level": 2, + "score": 0.0, + "template_placeholder": null, + "children": [] + }, + { + "id": "chapter_6_2", + "title": "培训内容", + "level": 2, + "score": 0.0, + "template_placeholder": null, + "children": [] + }, + { + "id": "chapter_6_3", + "title": "培训方式", + "level": 2, + "score": 0.0, + "template_placeholder": null, + "children": [] + } + ] + }, + { + "id": "chapter_7", + "title": "售后服务 (3.0分)", + "level": 1, + "score": 3.0, + "template_placeholder": null, + "children": [ + { + "id": "chapter_7_1", + "title": "质保期服务承诺", + "level": 2, + "score": 0.0, + "template_placeholder": null, + "children": [ + { + "id": "chapter_7_1_1", + "title": "质保期限与范围", + "level": 3, + "score": 0.0, + "template_placeholder": null, + "children": [] + }, + { + "id": "chapter_7_1_2", + "title": "质保期内服务内容", + "level": 3, + "score": 0.0, + "template_placeholder": null, + "children": [] + }, + { + "id": "chapter_7_1_3", + "title": "质保期响应机制", + "level": 3, + "score": 0.0, + "template_placeholder": null, + "children": [] + }, + { + "id": "chapter_7_1_4", + "title": "质保期满后服务延续方案", + "level": 3, + "score": 0.0, + "template_placeholder": null, + "children": [] + } + ] + }, + { + "id": "chapter_7_2", + "title": "技术支持服务", + "level": 2, + "score": 0.0, + "template_placeholder": null, + "children": [ + { + "id": "chapter_7_2_1", + "title": "技术支持团队配置", + "level": 3, + "score": 0.0, + "template_placeholder": null, + "children": [] + }, + { + "id": "chapter_7_2_2", + "title": "技术支持响应时间", + "level": 3, + "score": 0.0, + "template_placeholder": null, + "children": [] + }, + { + "id": "chapter_7_2_3", + "title": "技术支持服务方式", + "level": 3, + "score": 0.0, + "template_placeholder": null, + "children": [] + } + ] + }, + { + "id": "chapter_7_3", + "title": "维护服务", + "level": 2, + "score": 0.0, + "template_placeholder": null, + "children": [ + { + "id": "chapter_7_3_1", + "title": "定期维护计划", + "level": 3, + "score": 0.0, + "template_placeholder": null, + "children": [] + }, + { + "id": "chapter_7_3_2", + "title": "故障处理流程", + "level": 3, + "score": 0.0, + "template_placeholder": null, + "children": [] + }, + { + "id": "chapter_7_3_3", + "title": "备品备件保障", + "level": 3, + "score": 0.0, + "template_placeholder": null, + "children": [] + } + ] + }, + { + "id": "chapter_7_4", + "title": "客户服务保障", + "level": 2, + "score": 0.0, + "template_placeholder": null, + "children": [ + { + "id": "chapter_7_4_1", + "title": "客户服务热线", + "level": 3, + "score": 0.0, + "template_placeholder": null, + "children": [] + }, + { + "id": "chapter_7_4_2", + "title": "客户满意度调查", + "level": 3, + "score": 0.0, + "template_placeholder": null, + "children": [] + }, + { + "id": "chapter_7_4_3", + "title": "客户投诉处理机制", "level": 3, "score": 0.0, "template_placeholder": null, diff --git a/src/bidmaster/agents/builders/toc_builder.py b/src/bidmaster/agents/builders/toc_builder.py index af0946d..ec03aef 100644 --- a/src/bidmaster/agents/builders/toc_builder.py +++ b/src/bidmaster/agents/builders/toc_builder.py @@ -18,6 +18,8 @@ from ...nodes.toc import ( AdjustChaptersNode, FinalizeChaptersNode ) +from ...nodes.toc.user_feedback import UserFeedbackNode +from ...nodes.toc.optimize_with_feedback import OptimizeWithFeedbackNode from ...nodes.toc.base_mixins import WorkflowUtilsMixin logger = logging.getLogger(__name__) @@ -49,7 +51,9 @@ class TocAgentBuilder(AgentBuilder): .add_node(ReviewStructureNode()) \ .add_node(ApplyReviewSuggestionsNode()) \ .add_node(AdjustChaptersNode()) \ - .add_node(FinalizeChaptersNode()) + .add_node(FinalizeChaptersNode()) \ + .add_node(UserFeedbackNode()) \ + .add_node(OptimizeWithFeedbackNode()) # 设置入口点 builder.set_entry("group_criteria") @@ -99,8 +103,28 @@ class TocAgentBuilder(AgentBuilder): {"continue": "finalize_chapters", "end": "END"} ) - # 最后一个节点直接结束 - self.add_edge("finalize_chapters", "END") + # finalize_chapters连接到user_feedback + self.add_conditional_edge( + "finalize_chapters", + should_continue, + {"continue": "user_feedback", "end": "END"} + ) + + # user_feedback的条件边 + def check_needs_optimization(state: Dict[str, Any]) -> str: + """检查是否需要根据用户反馈进行优化""" + if state.get("needs_optimization", False): + return "optimize" + return "end" + + self.add_conditional_edge( + "user_feedback", + check_needs_optimization, + {"optimize": "optimize_with_feedback", "end": "END"} + ) + + # 优化后循环回finalize_chapters + self.add_edge("optimize_with_feedback", "finalize_chapters") class TocAgent(BaseAgent, BaseAgentFactory): diff --git a/src/bidmaster/cli/project.py b/src/bidmaster/cli/project.py index 1bc78e8..2a49784 100644 --- a/src/bidmaster/cli/project.py +++ b/src/bidmaster/cli/project.py @@ -282,9 +282,54 @@ def new(mode: str, presets: str): if not success: console.print("⚠️ Word模板生成失败,但项目已创建", style="yellow") + else: + # Word生成成功后,询问是否调整标题格式 + if click.confirm("\n📝 是否需要调整Word文档的标题编号格式?", default=False): + from ..tools.word_formatter import WordFormatter + + console.print("\n请选择格式来源:", style="blue") + console.print(" 1. 默认格式 (1. / 1.1 / 1.1.1)") + console.print(" 2. 自定义输入格式") + console.print(" 3. 从参考Word文件提取") + + format_choice = click.prompt("请选择", type=click.IntRange(1, 3), default=1) + + format_dict = None + if format_choice == 1: + format_dict = WordFormatter.get_default_format() + console.print("✓ 使用默认格式", style="green") + + elif format_choice == 2: + format_input = click.prompt( + "请输入格式规则(例如:1. 章 1.1 节 1.1.1 小节)", + default="1. 1.1 1.1.1" + ) + format_dict = WordFormatter.parse_user_format(format_input) + console.print(f"✓ 解析格式: {format_dict}", style="green") + + elif format_choice == 3: + reference_file = click.prompt("请输入参考Word文档路径", type=str) + if Path(reference_file).exists(): + format_dict = WordFormatter.extract_format_from_word(reference_file) + console.print(f"✓ 提取格式: {format_dict}", style="green") + else: + console.print("⚠️ 文件不存在,使用默认格式", style="yellow") + format_dict = WordFormatter.get_default_format() + + # 应用格式 + with console.status("正在调整标题格式..."): + format_success = WordFormatter.apply_format_to_document( + str(template_file), + format_dict + ) + + if format_success: + console.print("✅ 标题格式调整成功!", style="green") + else: + console.print("⚠️ 格式调整失败", style="yellow") # 显示创建结果 - console.print("✅ 项目创建成功!", style="green") + console.print("\n✅ 项目创建成功!", style="green") # 显示统计信息 console.print(f"\n💰 识别到商务评分项: {result.commercial_count}项(已排除)") diff --git a/src/bidmaster/nodes/toc/__init__.py b/src/bidmaster/nodes/toc/__init__.py index 866bbc3..8453699 100644 --- a/src/bidmaster/nodes/toc/__init__.py +++ b/src/bidmaster/nodes/toc/__init__.py @@ -11,6 +11,8 @@ from .review_structure import ReviewStructureNode from .apply_suggestions import ApplyReviewSuggestionsNode from .adjust_chapters import AdjustChaptersNode from .finalize_chapters import FinalizeChaptersNode +from .user_feedback import UserFeedbackNode +from .optimize_with_feedback import OptimizeWithFeedbackNode # 辅助组件 from .factories import ChapterFactory @@ -32,6 +34,8 @@ __all__ = [ "ApplyReviewSuggestionsNode", "AdjustChaptersNode", "FinalizeChaptersNode", + "UserFeedbackNode", + "OptimizeWithFeedbackNode", # 辅助组件 "ChapterFactory", diff --git a/src/bidmaster/nodes/toc/user_feedback.py b/src/bidmaster/nodes/toc/user_feedback.py new file mode 100644 index 0000000..bf66923 --- /dev/null +++ b/src/bidmaster/nodes/toc/user_feedback.py @@ -0,0 +1,138 @@ +"""用户反馈收集节点 + +在目录生成完成后,收集用户对目录结构的满意度和修改意见。 +""" + +import logging +from typing import Dict, List, Any + +from rich.console import Console + +from ..base import BaseNode, NodeContext +from ...tools.parser import DocumentChapter +from .base_mixins import TocNodeBase + +logger = logging.getLogger(__name__) + + +class UserFeedbackNode(BaseNode, TocNodeBase): + """收集用户对目录的反馈""" + + @property + def name(self) -> str: + return "user_feedback" + + @property + def description(self) -> str: + return "收集用户对目录的反馈" + + def execute(self, state: Dict[str, Any], context: NodeContext) -> Dict[str, Any]: + """执行用户反馈收集""" + return self.safe_execute(self._do_user_feedback, state, "收集用户对目录的反馈") + + def _do_user_feedback(self, state: Dict[str, Any]) -> Dict[str, Any]: + """执行实际的用户反馈收集逻辑""" + # 验证必需字段 + if not self.validate_required_fields(state, ["final_chapters"]): + raise ValueError("缺少最终章节数据") + + final_chapters = state.get("final_chapters", []) + + # 获取交互处理器 + interaction_handler = state.get("interaction_handler") + + if not interaction_handler: + # 无交互处理器时直接结束(静默模式或程序化模式) + logger.info("无交互处理器,跳过用户反馈收集") + return self._update_state(state, + should_continue=False, + user_feedback="", + needs_optimization=False) + + # 创建Console实例展示目录结构 + console = Console() + + # 展示目录结构 + console.print("\n" + "="*60, style="dim") + console.print("📚 生成的标书目录结构:", style="bold blue") + console.print("="*60, style="dim") + + # 格式化并显示目录 + toc_display = self._format_chapters_for_display(final_chapters) + console.print(toc_display) + console.print("="*60 + "\n", style="dim") + + # 询问用户是否满意 + satisfied = interaction_handler( + interaction_type="confirm", + prompt="您对上述生成的目录结构满意吗?", + default=True, + key="user_satisfied_with_toc" + ) + + if satisfied: + logger.info("用户对目录结构满意,结束工作流") + return self._update_state(state, + should_continue=False, + user_feedback="", + needs_optimization=False) + + # 收集用户意见 + feedback = interaction_handler( + interaction_type="text", + prompt="请输入您的修改意见(例如:调整章节顺序、增加缺失内容、优化章节标题等)", + key="user_toc_feedback" + ) + + if not feedback or feedback.strip() == "": + # 用户没有输入意见,默认满意 + logger.info("用户未提供具体意见,默认满意") + return self._update_state(state, + should_continue=False, + user_feedback="", + needs_optimization=False) + + logger.info(f"收集到用户反馈: {feedback[:50]}...") + + return self._update_state(state, + should_continue=True, + user_feedback=feedback, + needs_optimization=True) + + def _format_chapters_for_display(self, chapters: List[DocumentChapter], indent_level: int = 0) -> str: + """格式化章节用于显示 + + Args: + chapters: 章节列表 + indent_level: 缩进级别 + + Returns: + 格式化的章节字符串 + """ + lines = [] + + for i, chapter in enumerate(chapters, 1): + indent = " " * indent_level + + # 根据层级使用不同的标记 + if indent_level == 0: + marker = f"{i}." + elif indent_level == 1: + marker = f"{i})" + else: + marker = "-" + + # 添加分值信息 + score_text = f" ({chapter.score:.1f}分)" if chapter.score and chapter.score > 0 else "" + + lines.append(f"{indent}{marker} {chapter.title}{score_text}") + + # 递归处理子章节 + if chapter.children: + child_text = self._format_chapters_for_display( + chapter.children, + indent_level + 1 + ) + lines.append(child_text) + + return "\n".join(lines) \ No newline at end of file diff --git a/src/bidmaster/tools/word.py b/src/bidmaster/tools/word.py index 5130f9f..c10e55b 100644 --- a/src/bidmaster/tools/word.py +++ b/src/bidmaster/tools/word.py @@ -55,12 +55,17 @@ class WordProcessor: return False def _add_chapters_to_doc(self, doc: Document, chapters: List[DocumentChapter]): - """递归添加章节到文档(不含编号)""" + """递归添加章节到文档""" for chapter in chapters: - # 提取标题文本(去除现有编号) - title_text = self._extract_title_text(chapter.title) + # 检查标题是否已包含编号(如 "1." "1.1" 开头) + title_text = chapter.title + has_numbering = self._check_has_numbering(title_text) - # 使用Word的标题样式,让Word自动编号 + # 如果没有编号,尝试提取纯标题文本 + if not has_numbering: + title_text = self._extract_title_text(title_text) + + # 使用Word的标题样式 if chapter.level <= 9: # Word支持Heading 1-9 heading = doc.add_heading(title_text, level=chapter.level) else: @@ -94,6 +99,28 @@ class WordProcessor: if chapter.level == 1: doc.add_paragraph() # 添加空行 + def _check_has_numbering(self, title: str) -> bool: + """检查标题是否包含编号 + + Args: + title: 标题文本 + + Returns: + True 如果标题以编号开头 + """ + import re + # 匹配各种编号格式 + patterns = [ + r'^(\d+(?:\.\d+)*\.?\s+)', # 1. / 1.1 / 1.1.1 + r'^([一二三四五六七八九十]+、\s+)', # 中文数字 + r'^(\([0-9]+\)\s+)', # (1) + r'^([A-Z]\.\s+)', # A. + ] + for pattern in patterns: + if re.match(pattern, title): + return True + return False + def _extract_title_text(self, title: str) -> str: """提取标题文本,去除编号""" import re diff --git a/src/bidmaster/tools/word_formatter.py b/src/bidmaster/tools/word_formatter.py new file mode 100644 index 0000000..3ad826c --- /dev/null +++ b/src/bidmaster/tools/word_formatter.py @@ -0,0 +1,320 @@ +"""Word文档标题格式调整工具 + +读取已生成的Word文档,根据用户输入或参考文件调整标题编号格式。 +""" + +import logging +import re +from pathlib import Path +from typing import Dict, Optional +from enum import Enum + +from docx import Document + +logger = logging.getLogger(__name__) + + +class NumberingStyle(str, Enum): + """编号样式枚举""" + ARABIC = "arabic" # 1, 2, 3 + CHINESE = "chinese" # 一, 二, 三 + ROMAN_UPPER = "roman_upper" # I, II, III + ROMAN_LOWER = "roman_lower" # i, ii, iii + LETTER_UPPER = "letter_upper" # A, B, C + LETTER_LOWER = "letter_lower" # a, b, c + + +# 中文数字映射 +CHINESE_NUMBERS = { + '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, + '六': 6, '七': 7, '八': 8, '九': 9, '十': 10 +} + + +class WordFormatter: + """Word文档格式调整器""" + + @staticmethod + def parse_user_format(format_str: str) -> Dict[int, Dict[str, any]]: + """解析用户输入的格式字符串 + + Args: + format_str: 如 "一. 一.1 一.1.1" 或 "1. 1.1 1.1.1" + + Returns: + 格式字典 { + 1: {"style": "chinese", "suffix": ".", "pattern": "一."}, + 2: {"style": "arabic", "suffix": "", "pattern": "一.1"}, + 3: {"style": "arabic", "suffix": "", "pattern": "一.1.1"} + } + """ + format_dict = {} + + # 匹配各种格式的编号 + patterns = [ + (r'([一二三四五六七八九十]+)([\.、]?)', NumberingStyle.CHINESE), # 一. 或 一、 + (r'([IVXLCDM]+)([\.、]?)', NumberingStyle.ROMAN_UPPER), # I. 或 I、 + (r'([ivxlcdm]+)([\.、]?)', NumberingStyle.ROMAN_LOWER), # i. 或 i、 + (r'([A-Z])([\.、]?)', NumberingStyle.LETTER_UPPER), # A. 或 A、 + (r'([a-z])([\.、]?)', NumberingStyle.LETTER_LOWER), # a. 或 a、 + (r'(\d+(?:\.\d+)*)([\.、]?)', NumberingStyle.ARABIC), # 1. 或 1.1.1 + ] + + # 按空格分割输入 + parts = format_str.split() + + for level, part in enumerate(parts, 1): + matched = False + for pattern, style in patterns: + match = re.match(f'^{pattern}$', part.strip()) + if match: + number_part = match.group(1) + suffix = match.group(2) if match.lastindex >= 2 else "" + + format_dict[level] = { + "style": style, + "suffix": suffix, + "pattern": part.strip() + } + matched = True + logger.info(f"识别第{level}级: {part} -> 样式:{style}, 后缀:{suffix}") + break + + if not matched: + logger.warning(f"无法识别格式: {part}") + + if not format_dict: + logger.warning(f"未识别到任何格式,使用默认") + return WordFormatter.get_default_format() + + return format_dict + + @staticmethod + def get_default_format() -> Dict[int, Dict[str, any]]: + """默认的多级编号格式""" + return { + 1: {"style": NumberingStyle.ARABIC, "suffix": ".", "pattern": "1."}, + 2: {"style": NumberingStyle.ARABIC, "suffix": "", "pattern": "1.1"}, + 3: {"style": NumberingStyle.ARABIC, "suffix": "", "pattern": "1.1.1"} + } + + @staticmethod + def apply_format_to_document( + doc_path: str, + format_dict: Dict[int, Dict[str, any]], + output_path: Optional[str] = None + ) -> bool: + """应用格式到Word文档 + + Args: + doc_path: Word文档路径 + format_dict: 格式字典 + output_path: 输出路径(None则覆盖原文件) + + Returns: + 是否成功 + """ + try: + doc = Document(doc_path) + + # 为每个层级的标题添加编号 + counters = {} # {level: current_number} + + for para in doc.paragraphs: + if para.style.name.startswith('Heading'): + level = int(para.style.name.split()[-1]) + + # 更新计数器 + counters[level] = counters.get(level, 0) + 1 + + # 重置更低级别的计数器 + for lower_level in range(level + 1, 10): + if lower_level in counters: + counters[lower_level] = 0 + + # 获取格式配置 + format_config = format_dict.get(level, format_dict.get(1)) + + # 构建编号 + numbering = WordFormatter._build_numbering( + level, + counters, + format_config + ) + + # 移除旧编号并添加新编号 + clean_text = WordFormatter._remove_numbering(para.text) + + # 重要:直接设置文本(python-docx的限制) + para.text = f"{numbering} {clean_text}" + + logger.debug(f"设置标题: {para.text}") + + # 保存文档 + save_path = output_path or doc_path + doc.save(save_path) + logger.info(f"格式应用成功: {save_path}") + return True + + except Exception as e: + logger.error(f"应用格式失败: {e}", exc_info=True) + return False + + @staticmethod + def _build_numbering( + level: int, + counters: Dict[int, int], + format_config: Dict[str, any] + ) -> str: + """构建编号字符串 + + Args: + level: 当前层级 + counters: 各层级计数器 + format_config: 格式配置 + + Returns: + 编号字符串,如 "一." 或 "1.1.1" + """ + style = format_config.get("style", NumberingStyle.ARABIC) + suffix = format_config.get("suffix", ".") + + # 收集当前层级及以上的编号 + numbers = [] + for lv in range(1, level + 1): + num = counters.get(lv, 1) + + # 根据第一级的样式来决定各级的显示方式 + if lv == 1: + # 第一级使用指定的样式 + numbers.append(WordFormatter._format_number(num, style)) + else: + # 其他层级使用阿拉伯数字 + numbers.append(str(num)) + + # 组合编号 + if level == 1: + # 一级标题:样式 + 后缀 + return f"{numbers[0]}{suffix}" + else: + # 多级标题:一级样式.数字.数字 + return '.'.join(numbers) + + @staticmethod + def _format_number(num: int, style: NumberingStyle) -> str: + """将数字转换为指定样式 + + Args: + num: 数字 + style: 样式 + + Returns: + 格式化后的字符串 + """ + if style == NumberingStyle.CHINESE: + # 中文数字 + chinese_map = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'] + if num <= 10: + return chinese_map[num] + else: + # 超过10简化处理 + return f"第{num}" + + elif style == NumberingStyle.ROMAN_UPPER: + return WordFormatter._int_to_roman(num).upper() + + elif style == NumberingStyle.ROMAN_LOWER: + return WordFormatter._int_to_roman(num).lower() + + elif style == NumberingStyle.LETTER_UPPER: + if num <= 26: + return chr(64 + num) # A=65 + else: + return f"({num})" + + elif style == NumberingStyle.LETTER_LOWER: + if num <= 26: + return chr(96 + num) # a=97 + else: + return f"({num})" + + else: # ARABIC + return str(num) + + @staticmethod + def _int_to_roman(num: int) -> str: + """整数转罗马数字""" + val = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1] + syms = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I'] + roman_num = '' + for i in range(len(val)): + count = int(num / val[i]) + roman_num += syms[i] * count + num -= val[i] * count + return roman_num + + @staticmethod + def _remove_numbering(text: str) -> str: + """移除标题中的编号""" + patterns = [ + r'^(\d+(?:\.\d+)*\.?\s+)', # 1. / 1.1 / 1.1.1 + r'^([一二三四五六七八九十]+[\.、]\s+)', # 一. / 一、 + r'^([IVXLCDM]+[\.、]\s+)', # I. / I、 + r'^([ivxlcdm]+[\.、]\s+)', # i. / i、 + r'^([A-Z][\.、]\s+)', # A. / A、 + r'^([a-z][\.、]\s+)', # a. / a、 + r'^(\([0-9]+\)\s+)', # (1) + ] + + for pattern in patterns: + text = re.sub(pattern, '', text, flags=re.IGNORECASE) + + return text.strip() + + @staticmethod + def extract_format_from_word(reference_file: str) -> Dict[int, Dict[str, any]]: + """从参考Word文档提取标题编号格式 + + Args: + reference_file: 参考Word文档路径 + + Returns: + 格式字典 + """ + try: + doc = Document(reference_file) + format_patterns = {} + + for para in doc.paragraphs: + if para.style.name.startswith('Heading'): + level = int(para.style.name.split()[-1]) + text = para.text.strip() + + # 提取编号部分 + for pattern_re, style in [ + (r'^([一二三四五六七八九十]+)([\.、])', NumberingStyle.CHINESE), + (r'^([IVXLCDM]+)([\.、])', NumberingStyle.ROMAN_UPPER), + (r'^(\d+(?:\.\d+)*)([\.、]?)', NumberingStyle.ARABIC), + ]: + match = re.match(pattern_re, text) + if match and level not in format_patterns: + number_part = match.group(1) + suffix = match.group(2) if match.lastindex >= 2 else "" + + format_patterns[level] = { + "style": style, + "suffix": suffix, + "pattern": f"{number_part}{suffix}" + } + logger.info(f"从参考文档提取: 级别{level} = {number_part}{suffix}") + break + + if not format_patterns: + logger.warning(f"未从 {reference_file} 提取到格式") + return WordFormatter.get_default_format() + + return format_patterns + + except Exception as e: + logger.error(f"提取Word格式失败: {e}") + return WordFormatter.get_default_format() \ No newline at end of file