From c18f9d553632569b97c2cc8838f0379e0e18b49a Mon Sep 17 00:00:00 2001 From: JA <1323160571@qq.com> Date: Fri, 21 Aug 2026 10:39:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wenzijiaoben/.workbuddy/memory/2026-08-21.md | 42 ++ wenzijiaoben/.workbuddy/memory/MEMORY.md | 15 + .../memories/data_source_mapping.json | 36 +- wenzijiaoben/memories/doc_format_spec.json | 4 +- .../memories/icon_resource_mapping.json | 12 +- wenzijiaoben/memories/project_config.json | 36 +- wenzijiaoben/memories/system_prompts.json | 5 +- .../__pycache__/generate_doc.cpython-311.pyc | Bin 27388 -> 0 bytes wenzijiaoben/py/generate_doc.py | 568 ------------------ wenzijiaoben/py/universal_tool.py | 215 +++++-- wenzijiaoben/py/xlsx_to_csv.py | 104 ---- wenzijiaoben/使用请看.txt | 31 +- 12 files changed, 285 insertions(+), 783 deletions(-) create mode 100644 wenzijiaoben/.workbuddy/memory/2026-08-21.md create mode 100644 wenzijiaoben/.workbuddy/memory/MEMORY.md delete mode 100644 wenzijiaoben/py/__pycache__/generate_doc.cpython-311.pyc delete mode 100644 wenzijiaoben/py/generate_doc.py delete mode 100644 wenzijiaoben/py/xlsx_to_csv.py diff --git a/wenzijiaoben/.workbuddy/memory/2026-08-21.md b/wenzijiaoben/.workbuddy/memory/2026-08-21.md new file mode 100644 index 0000000..4b9b916 --- /dev/null +++ b/wenzijiaoben/.workbuddy/memory/2026-08-21.md @@ -0,0 +1,42 @@ +# 2026-08-21 工作日志 + +## universal_tool.py 改造(两项) + +### 1. 直接读取 xlsx,不再转 csv +- 新增 `read_table(filepath)` 统一入口 + `_read_xlsx` / `_read_csv`。(`read_csv` 兼容别名后已删除) +- `find_file` 改为优先 `.xlsx`,其次 `.csv`。 +- `get_csv_list_data` / `get_csv_table_data` / `get_player_names` 去掉 `.csv` 限制,支持任意表格格式。 +- `build_document` 删除 `xlsx_to_csv_batch` 自动转换逻辑,直接从 config_dir / csv_dir 读 xlsx。 +- `xlsx_to_csv_batch` 函数定义后已在第二轮清理中删除。 + +### 2. Word 自动编号替代硬编码数字 +- 新增 `_get_numbering_element` / `_create_decimal_numbering` / `_apply_numbering`。 +- 重写 `add_numbered_items`:每次调用新建独立 numId(decimal, "1." 格式),段落用 numPr,文本不再带 "1." 前缀。 +- 效果:在 Word 中增删条目后编号自动重排。 +- 章节标题 `add_section_heading` 仍用硬编码中文数字("一、""二、"),因章节固定不增删。 + +## 验证 +- 用 managed python venv (3.13.12) + python-docx/openpyxl 实测: + - 6 个编号段落均带 numPr;2 个章节用 2 个不同 numId 独立从 1 计数;无硬编码数字前缀。 +- venv 位置:C:\Users\Administrator\.workbuddy\binaries\python\envs\default + +## 项目清理(第二轮:删无用 + 更新文档) + +### 删除 +- `py/generate_doc.py`(旧硬编码版,路径写死)、`py/xlsx_to_csv.py`(不再需要转 csv)、`py/__pycache__`。 +- 清理后 `py/` 仅剩 `universal_tool.py`。 + +### 清理 universal_tool.py +- 删除 `xlsx_to_csv_batch` 函数、`read_csv` 兼容别名、`import glob`。 +- 更新文件头/章节注释:去除 "CSV 缓存" 说法,注明直接读 xlsx、Word 自动编号。 +- py_compile + import 验证通过(read_table 存在,xlsx_to_csv_batch/read_csv 已无)。 + +### 更新 memories/*.json +- `data_source_mapping.json`:`csv_base_dir`→`table_base_dir`,各 source `.csv`→`.xlsx/csv`,`csv_format`→`table_format` 补充 xlsx 直读。 +- `project_config.json`:去掉被删脚本引用,路径改为示例说明,补充 data_reading 策略。 +- `system_prompts.json`:`source_file`→`source_file_example` + note(自动解析说明)。 +- `icon_resource_mapping.json`:`base_dir`→示例,`csv_field`→`table_field` (.xlsx/csv)。 +- `doc_format_spec.json`:标题改示例标注(格式规范本身通用,numbering 已是 Word 自动编号)。 + +### 更新 使用请看.txt +- 改为准确说明:自动扫描项目结构、直接读 xlsx、Word 自动编号、memories 仅为参考不被脚本读取。 diff --git a/wenzijiaoben/.workbuddy/memory/MEMORY.md b/wenzijiaoben/.workbuddy/memory/MEMORY.md new file mode 100644 index 0000000..1000e53 --- /dev/null +++ b/wenzijiaoben/.workbuddy/memory/MEMORY.md @@ -0,0 +1,15 @@ +# 项目长期记忆 + +## 项目概述 +游戏文字脚本文档生成工具:扫描项目目录,从配置表(xlsx/csv)和 GameTextConst.cs 提取文本,生成 Word 文档。 +入口:`py/universal_tool.py`(通用版);`py/generate_doc.py` 为早期硬编码版(路径写死,仅参考)。 + +## 关键约定 +- **数据读取**:直接读取 xlsx(优先),无需先转 csv。表头结构固定为第3行英文字段名、第4行起数据。 +- **文档编号**:正文条目用 Word 自动编号(numPr + decimal),每章节独立 numId 从 1 开始;段落文本不硬编码 "1.",便于增删后自动重排。章节标题仍用硬编码中文数字。 +- **字体**:标题宋体二号加粗居中;章节标题/正文仿宋四号。 +- **图标**:表格图标列高 3cm 锁定纵横比,按 atlas_* 目录匹配,支持末尾数字补零。 + +## 环境 +- 推荐用 managed python 3.13.12 + venv(C:\Users\Administrator\.workbuddy\binaries\python\envs\default),依赖:python-docx、openpyxl。 +- 项目配置记忆在 `memories/*.json`(数据源映射、格式规范、图标映射、项目配置)。 diff --git a/wenzijiaoben/memories/data_source_mapping.json b/wenzijiaoben/memories/data_source_mapping.json index 62193be..b1ce026 100644 --- a/wenzijiaoben/memories/data_source_mapping.json +++ b/wenzijiaoben/memories/data_source_mapping.json @@ -1,42 +1,43 @@ { - "description": "CSV数据源到文档章节的映射关系", - "csv_base_dir": "d:\\p-L12\\work\\cvs", + "description": "配置表数据源到文档章节的映射关系", + "table_base_dir": "*_config 目录(优先读 .xlsx,其次 .csv,无需预先转换)", + "read_strategy": "universal_tool.find_file 优先查找 name.xlsx,找不到再找 name.csv;两种格式都直接读取,表头结构一致", "mappings": { "system_prompts": { "source": "GameTextConst.cs (代码引用分析)", "section": "一、系统提示", "field": "常量值文本", - "filter": "仅保留在代码中被实际引用的常量(排除仅在注释中出现的)", + "filter": "仅保留在代码中被实际引用的常量(排除仅在注释中出现的)", "dedup": "按文本值去重" }, "quest_texts": { - "source": "quest.csv", + "source": "quest.xlsx/csv", "section": "二、任务文本", "field": "desc (第4列, index=3)", "filter": "category == 1 或 category == 2", "dedup": "按desc文本去重" }, "achievement_texts": { - "source": "quest.csv", + "source": "quest.xlsx/csv", "section": "三、成就文本", "field": "desc (第4列, index=3)", "filter": "category == 9", "dedup": "按desc文本去重" }, "player_names": { - "source": "原始文档固定数据 (100对姓名)", + "source": "name.xlsx/csv (找不到则用内置默认100对姓名)", "section": "四、玩家随机名称文本", "format": "表格: 姓 | 名" }, "guide_texts": { - "source": "guide.csv", + "source": "guide.xlsx/csv", "section": "五、新手指引文本", "field": "desc (第8列, index=7)", "filter": "无", "dedup": "按desc文本去重" }, "props": { - "source": "prop.csv", + "source": "prop.xlsx/csv", "section": "六、道具说明", "fields": { "name": "第3列 (index=2)", @@ -47,7 +48,7 @@ "icon_naming": "icon_product_XX.png (XX为补零两位数, 如 icon_product_01)" }, "skills": { - "source": "skill.csv", + "source": "skill.xlsx/csv", "section": "七、技能", "fields": { "name": "第2列 (index=1)", @@ -58,7 +59,7 @@ "icon_naming": "skillX.png (X为数字, 不补零, 如 skill1, skill10)" }, "runes": { - "source": "rune.csv", + "source": "rune.xlsx/csv", "section": "八、灵石", "fields": { "name": "第2列 (index=1)", @@ -69,7 +70,7 @@ "icon_naming": "rune_icon_X.png (X为数字, 不补零)" }, "talents": { - "source": "playerlevel.csv", + "source": "playerlevel.xlsx/csv", "section": "九、天赋", "fields": { "name": "第14列 (index=13)", @@ -81,7 +82,7 @@ "dedup": "按name去重" }, "equipment": { - "source": "equip.csv", + "source": "equip.xlsx/csv", "section": "十、装备", "fields": { "name": "第2列 (index=1)", @@ -90,19 +91,20 @@ "icon_dir": "atlas_equip_icons", "icon_naming": "equip_icon_X.png (X为数字, 不补零)", "dedup": "按name去重", - "note": "equip.csv无desc字段" + "note": "equip表无desc字段" }, "level_names": { - "source": "fight_sample.csv", + "source": "fight_sample.xlsx/csv", "section": "十一、关卡名称", "field": "name (第3列, index=2)", "filter": "无", "dedup": "按name去重" } }, - "csv_format": { + "table_format": { "header_rows": 3, - "header_structure": "第1行:中文头, 第2行:类型定义, 第3行:英文字段名", - "encoding_note": "部分文件为GBK编码,需自动检测" + "header_structure": "第1行:中文头, 第2行:类型定义, 第3行:英文字段名(用作字段映射)", + "data_start_row": "第4行起", + "encoding_note": "csv需自动检测(优先级 utf-8-sig -> gbk -> gb2312 -> utf-8);xlsx由openpyxl直接读取无编码问题" } } diff --git a/wenzijiaoben/memories/doc_format_spec.json b/wenzijiaoben/memories/doc_format_spec.json index 185edd1..021ae97 100644 --- a/wenzijiaoben/memories/doc_format_spec.json +++ b/wenzijiaoben/memories/doc_format_spec.json @@ -1,5 +1,5 @@ { - "document_title": "《不朽封仙》文字脚本", + "document_title_example": "《<项目名>》文字脚本 (universal_tool 用扫描到的项目名生成)", "sections": [ {"number": "一", "title": "系统提示", "type": "numbered_list"}, {"number": "二", "title": "任务文本", "type": "numbered_list"}, @@ -35,7 +35,7 @@ "font_en": "FangSong", "size": "四号 (14pt)", "bold": false, - "numbering": "阿拉伯数字 (1. 2. 3. ...)" + "numbering": "Word自动编号列表 (decimal, 每章节独立numId从1开始),增删条目后编号自动重排;段落文本不再硬编码 1. 2. 3." }, "table_images": { "height": "3cm", diff --git a/wenzijiaoben/memories/icon_resource_mapping.json b/wenzijiaoben/memories/icon_resource_mapping.json index 3640e3e..df10b76 100644 --- a/wenzijiaoben/memories/icon_resource_mapping.json +++ b/wenzijiaoben/memories/icon_resource_mapping.json @@ -1,10 +1,10 @@ { "description": "图标资源名称到图片文件的映射规则与目录结构", - "base_dir": "d:\\p-L12\\P-L12_game\\unity\\Assets\\Resources\\Resources_moved\\gui\\atlas_ui", + "base_dir_example": "/unity/Assets/Resources/Resources_moved/gui/atlas_ui (universal_tool 自动扫描 atlas_* 子目录)", "directories": { "atlas_product_icons": { "section": "六、道具说明", - "csv_field": "prop.csv icon (index=7)", + "table_field": "prop.xlsx/csv icon (index=7)", "naming_pattern": "icon_product_XX.png", "naming_note": "XX为补零两位数 (01-63), 如 icon_product_1 -> icon_product_01.png", "file_count": 63, @@ -12,7 +12,7 @@ }, "atlas_skill": { "section": "七、技能", - "csv_field": "skill.csv icon (index=6)", + "table_field": "skill.xlsx/csv icon (index=6)", "naming_pattern": "skillX.png", "naming_note": "X为数字不补零 (1-99), 如 skill1, skill10, skill99", "file_count": 99, @@ -20,7 +20,7 @@ }, "atlas_rune_icons": { "section": "八、灵石", - "csv_field": "rune.csv icon (index=2)", + "table_field": "rune.xlsx/csv icon (index=2)", "naming_pattern": "rune_icon_X.png", "naming_note": "X为数字不补零 (1-18)", "file_count": 12, @@ -28,7 +28,7 @@ }, "atlas_talent_icons": { "section": "九、天赋", - "csv_field": "playerlevel.csv icon (index=15)", + "table_field": "playerlevel.xlsx/csv icon (index=15)", "naming_pattern": "icon_talent_X.png", "naming_note": "X为数字不补零 (1-6)", "file_count": 6, @@ -36,7 +36,7 @@ }, "atlas_equip_icons": { "section": "十、装备", - "csv_field": "equip.csv icon (index=5)", + "table_field": "equip.xlsx/csv icon (index=5)", "naming_pattern": "equip_icon_X.png", "naming_note": "X为数字不补零 (1-6)", "file_count": 6, diff --git a/wenzijiaoben/memories/project_config.json b/wenzijiaoben/memories/project_config.json index ffe1ee8..2c5d2b3 100644 --- a/wenzijiaoben/memories/project_config.json +++ b/wenzijiaoben/memories/project_config.json @@ -1,17 +1,15 @@ { - "project_name": "P-L12《不朽封仙》", - "workspace_root": "d:\\p-L12", - "paths": { - "config_dir": "d:\\p-L12\\P-L12_config", - "game_dir": "d:\\p-L12\\P-L12_game", - "csv_dir": "d:\\p-L12\\work\\cvs", - "scripts_dir": "d:\\p-L12\\work\\py", - "memories_dir": "d:\\p-L12\\work\\memories", - "output_doc": "d:\\p-L12\\work\\《不朽封仙》游戏文字脚本.docx" + "project_name": "P-L12《不朽封仙》(历史参考项目)", + "note": "本文件记录历史项目的路径约定,作为通用工具 universal_tool.py 的参考。当前工具自动扫描项目结构,不再依赖固定路径。", + "paths_example": { + "config_dir": "/<项目名>_config (xlsx/csv 配置表)", + "game_dir": "/<项目名>_game", + "scripts_dir": "/<项目名>_game/unity/Assets/Scripts", + "memories_dir": "<本项目>/memories", + "output_doc": "/work/《项目名》游戏文字脚本.docx" }, "scripts": { - "xlsx_to_csv": "d:\\p-L12\\work\\py\\xlsx_to_csv.py", - "generate_doc": "d:\\p-L12\\work\\py\\generate_doc.py" + "universal_tool": "<本项目>/py/universal_tool.py (唯一入口,自动扫描+生成)" }, "dependencies": { "python_packages": ["openpyxl", "python-docx"], @@ -20,14 +18,14 @@ "pip install python-docx" ] }, - "csv_encoding": { - "note": "部分CSV文件使用GBK/GB2312编码,非UTF-8", - "read_strategy": "按优先级尝试 utf-8-sig -> gbk -> gb2312 -> utf-8", - "write_encoding": "xlsx_to_csv.py 输出使用 UTF-8 with BOM (utf-8-sig)" + "data_reading": { + "strategy": "直接读取 xlsx(优先)或 csv,无需预先转换", + "csv_encoding": "按优先级尝试 utf-8-sig -> gbk -> gb2312 -> utf-8 (仅csv需要)", + "xlsx_reading": "openpyxl read_only/data_only 直接读取" }, - "game_code_paths": { - "game_text_const": "d:\\p-L12\\P-L12_game\\unity\\Assets\\Scripts\\mgame\\error\\GameTextConst.cs", - "scripts_root": "d:\\p-L12\\P-L12_game\\unity\\Assets\\Scripts" + "game_code_paths_example": { + "game_text_const": "/unity/Assets/Scripts/.../GameTextConst.cs", + "scripts_root": "/unity/Assets/Scripts" }, - "icon_resource_base": "d:\\p-L12\\P-L12_game\\unity\\Assets\\Resources\\Resources_moved\\gui\\atlas_ui" + "icon_resource_base_example": "/unity/Assets/Resources/Resources_moved/gui/atlas_ui" } diff --git a/wenzijiaoben/memories/system_prompts.json b/wenzijiaoben/memories/system_prompts.json index 179d32d..0025e93 100644 --- a/wenzijiaoben/memories/system_prompts.json +++ b/wenzijiaoben/memories/system_prompts.json @@ -1,6 +1,7 @@ { - "description": "GameTextConst.cs 中被代码实际引用的系统提示常量", - "source_file": "d:\\p-L12\\P-L12_game\\unity\\Assets\\Scripts\\mgame\\error\\GameTextConst.cs", + "description": "GameTextConst.cs 中被代码实际引用的系统提示常量(历史项目参考)", + "source_file_example": "/unity/Assets/Scripts/.../GameTextConst.cs (universal_tool 自动扫描定位)", + "note": "universal_tool.parse_game_text_const 自动解析 *Const*.cs 中所有 public const string,扫描其他 .cs 确认被引用后按文本去重;本文件为历史项目的静态快照参考", "total_referenced": 85, "total_unique_text": 83, "note": "85个被引用常量中,有2对文本值重复('{0}不足。' 和 '保存失败。'),去重后为83条", diff --git a/wenzijiaoben/py/__pycache__/generate_doc.cpython-311.pyc b/wenzijiaoben/py/__pycache__/generate_doc.cpython-311.pyc deleted file mode 100644 index b73db0bd60481600cbaa6eb54b70042c7b707b23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27388 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 diff --git a/wenzijiaoben/py/generate_doc.py b/wenzijiaoben/py/generate_doc.py deleted file mode 100644 index 960fbfe..0000000 --- a/wenzijiaoben/py/generate_doc.py +++ /dev/null @@ -1,568 +0,0 @@ -# -*- coding: utf-8 -*- -""" -生成《不朽封仙》游戏文字脚本文档 -""" - -import csv -import os -import glob -from docx import Document -from docx.shared import Pt, Cm, Inches -from docx.enum.text import WD_ALIGN_PARAGRAPH -from docx.enum.table import WD_TABLE_ALIGNMENT -from docx.oxml.ns import qn - -# ============ 路径配置 ============ -CSV_DIR = r"d:\p-L12\work\cvs" -GAME_DIR = r"d:\p-L12\P-L12_game\unity\Assets\Resources\Resources_moved\gui\atlas_ui" -ICON_DIRS = { - "product": os.path.join(GAME_DIR, "atlas_product_icons"), - "skill": os.path.join(GAME_DIR, "atlas_skill"), - "rune": os.path.join(GAME_DIR, "atlas_rune_icons"), - "talent": os.path.join(GAME_DIR, "atlas_talent_icons"), - "equip": os.path.join(GAME_DIR, "atlas_equip_icons"), -} -OUTPUT_PATH = r"d:\p-L12\work\《不朽封仙》游戏文字脚本.docx" - -# ============ 辅助函数 ============ - -def read_csv(filename): - """读取CSV文件,跳过前3行(中文头、类型头、英文头),返回数据行""" - path = os.path.join(CSV_DIR, filename) - # 尝试多种编码 - for encoding in ["utf-8-sig", "gbk", "gb2312", "utf-8"]: - try: - with open(path, "r", encoding=encoding) as f: - reader = csv.reader(f) - rows = list(reader) - return rows[3:] # skip header rows - except (UnicodeDecodeError, UnicodeError): - continue - raise ValueError(f"无法读取文件 {filename},尝试了所有编码") - -def find_image(icon_name, icon_dir_key): - """根据icon名称查找图片文件路径,返回完整路径或None""" - icon_dir = ICON_DIRS.get(icon_dir_key) - if not icon_dir or not os.path.isdir(icon_dir): - return None - # 尝试直接匹配 - for ext in [".png", ".jpg"]: - p = os.path.join(icon_dir, icon_name + ext) - if os.path.isfile(p): - return p - # 尝试补零匹配(如 icon_product_1 -> icon_product_01) - import re - m = re.match(r"^(.+?)(\d+)$", icon_name) - if m: - prefix, num = m.group(1), m.group(2) - for pad in [2, 3]: - padded = prefix + num.zfill(pad) - for ext in [".png", ".jpg"]: - p = os.path.join(icon_dir, padded + ext) - if os.path.isfile(p): - return p - return None - -def add_image_to_cell(cell, image_path, height_cm=3): - """在表格单元格中插入图片,锁定纵横比,设置高度""" - paragraph = cell.paragraphs[0] - paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER - run = paragraph.add_run() - run.add_picture(image_path, height=Cm(height_cm)) - -def set_cell_text(cell, text): - """设置单元格文本""" - cell.text = "" - paragraph = cell.paragraphs[0] - paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER - run = paragraph.add_run(str(text)) - run.font.size = Pt(10) - -def add_table_with_icons(doc, headers, rows_data, icon_dir_key): - """添加带图标的表格""" - table = doc.add_table(rows=1, cols=len(headers), style="Table Grid") - table.alignment = WD_TABLE_ALIGNMENT.CENTER - # 设置表头 - for i, header in enumerate(headers): - cell = table.rows[0].cells[i] - cell.text = header - for paragraph in cell.paragraphs: - paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER - for run in paragraph.runs: - run.font.bold = True - run.font.size = Pt(10) - # 添加数据行 - for row_data in rows_data: - row = table.add_row() - name, icon_name, desc = row_data[0], row_data[1], row_data[2] if len(row_data) > 2 else "" - set_cell_text(row.cells[0], name) - # 图标列 - img_path = find_image(icon_name, icon_dir_key) if icon_name else None - if img_path: - add_image_to_cell(row.cells[1], img_path) - else: - set_cell_text(row.cells[1], "") - # 描述列 - if desc: - set_cell_text(row.cells[2], desc) - -def add_table_equip(doc, headers, rows_data, icon_dir_key): - """添加装备表格(只有名称和图标两列)""" - table = doc.add_table(rows=1, cols=len(headers), style="Table Grid") - table.alignment = WD_TABLE_ALIGNMENT.CENTER - for i, header in enumerate(headers): - cell = table.rows[0].cells[i] - cell.text = header - for paragraph in cell.paragraphs: - paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER - for run in paragraph.runs: - run.font.bold = True - run.font.size = Pt(10) - for row_data in rows_data: - row = table.add_row() - name, icon_name = row_data[0], row_data[1] - set_cell_text(row.cells[0], name) - img_path = find_image(icon_name, icon_dir_key) if icon_name else None - if img_path: - add_image_to_cell(row.cells[1], img_path) - else: - set_cell_text(row.cells[1], "") - - -# ============ 数据准备 ============ - -def get_system_prompts(): - """获取被引用的GameTextConst系统提示""" - # 所有GameTextConst常量及其文本值 - all_consts = { - "ErrorTaskFailed": "领取失败。", - "ErrorBuyFailed": "购买失败。", - "ErrorUseFailed": "使用失败。", - "ErrorTakesuccess": "领取成功。", - "ErrorStrengthLimit": "强化已达上限。", - "ErrorChallengeTimesNotEnough": "今日挑战次数不足。", - "ErrorRecruitTimesNotEnough": "今日可招募次数不足。", - "ErrorRecruitTicketNotEnough": "召唤道具与月灵玉璧不足。", - "ErrorRecruitDiamondNotEnough": "星棱幻晶不足,无法进行招募。", - "ErrorUpStageFirst": "请先升阶。", - "ErrorUpStage1First": "请先升品。", - "ErrorUpLevelFirst": "请先升级。", - "ErrorUpStarFirst": "请先升星。", - "ErrorUpStrengthFirst": "请先强化。", - "ErrorLevelNotExist": "关卡不存在。", - "ErrorInvalidGuideId": "无效的引导。", - "ErrorItemNotEnough": "道具不足。", - "ErrorRewardAlreadyClaimed": "您已领奖。", - "ErrorNotSettlementTime": "未到结算时间。", - "ErrorNoReward": "暂无奖励。", - "ErrorHeroStarUpSuccess": "升星成功。", - "ErrorTujianProgressNotEnough": "图鉴进度未达到领取条件。", - "ErrorUpLevelSuccess": "升级成功。", - "ErrorUpStageSuccess": "升阶成功。", - "ErrorGuildSetSuccess": "设置公会成功。", - "ErrorNoPackagePurchased": "未购买礼包。", - "ErrorNotMeetClaimCondition": "暂未达到领取条件。", - "ErrorNameNotAvailable": "含有敏感字符,无法使用该名字。", - "ErrorNameOnlyChinese": "昵称只能包含中文。", - "ErrorPleaseInputName": "请输入昵称。", - "ErrorSetSuccess": "修改成功。", - "ErrorCurrencyNotEnough": "货币不足。", - "ErrorHeroMaxCountReached": "伙伴达到最大人数。", - "ErrorNoHeroToCombine": "没有可合成的伙伴。", - "ErrorJoinSuccess": "加入成功。", - "ErrorApplySuccess": "申请成功。", - "ErrorEnergyNotEnough": "体力不足。", - "ErrorBuySuccess": "购买成功。", - "ErrorQuitGuildSuccess": "退出公会成功。", - "ErrorGuildNameEmpty": "公会名称不能为空。", - "ErrorGuildNameTooLong": "公会名称不能超过6个字符。", - "ErrorCreateGuildSuccess": "创建公会成功。", - "ErrorMaterialNotEnough": "材料不足。", - "ErrorEquipStrengthSuccess": "装备强化成功。", - "ErrorEquipBreakthroughSuccess": "装备升品成功。", - "ErrorStrengthSuccess": "强化成功。", - "ErrorDiamondNotEnough": "星棱幻晶不足。", - "ErrorQuickSweepTimesNotEnough": "快速游历次数不足。", - "ErrorCopySuccess": "复制成功。", - "ErrorReceiveConditionNotMet": "未达成领取条件。", - "ErrorAlreadyClaimed": "已领取。", - "ErrorItemDailyUseTimesReached": "该道具今日使用次数已达上限。", - "ErrorConditionNotMet": "条件不足。", - "ErrorVipLevelNotEnough": "权益等级不足。", - "ErrorLevelNotEnough": "等级不足。", - "ErrorScoreNotEnough": "积分不足。", - "ErrorPassNotOpen": "关卡未开放。", - "ErrorPreyFailed": "祈愿失败。", - "ErrorInvalidId": "无效的ID。", - "ErrorSaveSuccess": "保存成功。", - "ErrorSaveFailed": "保存失败。", - "ErrorNoHeroCarry": "无上阵伙伴。", - "ErrorNoHeroInTeam": "至少上阵一名修士。", - "ErrorNoFreePos": "人数已达上限。", - "ErrorRefreshTimesNotEnough": "刷新次数不足。", - "ErrorTimesNotEnough": "次数不足。", - "ErrorHeroLevelUnlock": "修士{0}级解锁。", - "TipChallengeTimesConfirm": "\u3000是否消耗{0}进行挑战。\n\u3000今日剩余({1}/{2})次。", - "ErrorFusionRuneCountNotEnough": "合成铭文所需数量不足。", - "TipFusionSuccess": "合成成功。", - "ErrorFusionRuneRarityNotSame": "合成铭文必须是相同稀有度。", - "ErrorFusionRuneCountMax": "合成铭文所需数量已达上限。", - "ErrorFusionRuneNotFusion": "该铭文不能合成。", - "ErrorRunePutOnSuccess": "镶嵌成功。", - "ErrorTitleNotUnlock": "称号未解锁。", - "ErrorHeroNotUnlock": "该伙伴尚未获得,需前往旅人驿站招募哦。", - "ErrorRuneSlotFull": "铭文槽已满,请先卸下。", - "ErrorPushBoxLock": "通过{0}后解锁。", - "ErrorFriendApplySuccess": "已同意成为好友。", - "ErrorFriendApplyRefuse": "已拒绝成为好友。", - "ErrorFriendAddOrNotExist": "玩家已添加或不存在。", - "ErrorBagFull": "背包已满。", - "ErrorTargetNotEnough": "{0}不足。", - "ErrorPassNotUnlock": "通关后解锁。", - "ErrorSaveHostage": "解救{0}个人质后解锁。", - "ErrorSomethingNotEnough": "{0}不足。", - "ErrorRetreatNotNow": "现在还不能撤离。", - "ErrorNotUnlock": "未解锁。", - "ErrorSendGiftSuccess": "赠送成功", - "ErrorCollectionUnlock": "请在关卡中拾取藏品解锁。", - "ErrorTaskNotFinish": "任务未完成。", - "ErrorTaskRewardNotClaimed": "任务奖励未领取。", - "ErrorNotNewApply": "暂无新的好友申请。", - "ErrorNotPassBigBox": "请通关当前所有关卡后领取。", - "ErrorNotPassBigBoxStar": "满星通过前置关卡后解锁。", - "ErrorBreakthroughSuccess": "突破成功。", - "ErrorLearnSuccess": "学习成功。", - } - # 被引用的常量(排除只在注释中引用的) - referenced = { - "ErrorAlreadyClaimed", "ErrorApplySuccess", "ErrorBagFull", - "ErrorBreakthroughSuccess", "ErrorBuyFailed", "ErrorBuySuccess", - "ErrorChallengeTimesNotEnough", "ErrorCollectionUnlock", - "ErrorConditionNotMet", "ErrorCopySuccess", "ErrorCreateGuildSuccess", - "ErrorCurrencyNotEnough", "ErrorDiamondNotEnough", "ErrorEnergyNotEnough", - "ErrorEquipBreakthroughSuccess", "ErrorEquipStrengthSuccess", - "ErrorFriendAddOrNotExist", "ErrorFriendApplyRefuse", - "ErrorFriendApplySuccess", "ErrorFusionRuneCountMax", - "ErrorFusionRuneCountNotEnough", "ErrorFusionRuneNotFusion", - "ErrorFusionRuneRarityNotSame", "ErrorGuildNameEmpty", - "ErrorGuildNameTooLong", "ErrorGuildSetSuccess", "ErrorHeroLevelUnlock", - "ErrorHeroNotUnlock", "ErrorHeroStarUpSuccess", "ErrorInvalidGuideId", - "ErrorItemDailyUseTimesReached", "ErrorItemNotEnough", - "ErrorJoinSuccess", "ErrorLearnSuccess", "ErrorLevelNotEnough", - "ErrorLevelNotExist", "ErrorMaterialNotEnough", "ErrorNameNotAvailable", - "ErrorNameOnlyChinese", "ErrorNoFreePos", "ErrorNoHeroCarry", - "ErrorNoHeroInTeam", "ErrorNoPackagePurchased", "ErrorNoReward", - "ErrorNotMeetClaimCondition", "ErrorNotNewApply", "ErrorNotPassBigBox", - "ErrorNotPassBigBoxStar", "ErrorNotSettlementTime", "ErrorNotUnlock", - "ErrorPassNotOpen", "ErrorPassNotUnlock", "ErrorPleaseInputName", - "ErrorPreyFailed", "ErrorPushBoxLock", "ErrorQuickSweepTimesNotEnough", - "ErrorQuitGuildSuccess", "ErrorRefreshTimesNotEnough", - "ErrorRetreatNotNow", "ErrorRewardAlreadyClaimed", - "ErrorRunePutOnSuccess", "ErrorRuneSlotFull", "ErrorSaveHostage", - "ErrorSaveSuccess", "ErrorScoreNotEnough", "ErrorSendGiftSuccess", - "ErrorSetSuccess", "ErrorSomethingNotEnough", "ErrorTakesuccess", - "ErrorTargetNotEnough", "ErrorTaskFailed", "ErrorTaskNotFinish", - "ErrorTaskRewardNotClaimed", "ErrorTimesNotEnough", - "ErrorTitleNotUnlock", "ErrorUpLevelFirst", "ErrorUpLevelSuccess", - "ErrorUpStage1First", "ErrorUpStageFirst", "ErrorUpStageSuccess", - "ErrorUpStrengthFirst", "ErrorVipLevelNotEnough", - "TipChallengeTimesConfirm", "TipFusionSuccess", - } - # 只保留被引用的,去重文本值 - seen_texts = set() - result = [] - for name, text in all_consts.items(): - if name in referenced and text not in seen_texts: - seen_texts.add(text) - result.append(text) - return result - -def get_quest_texts(): - """获取任务文本(category 1和2的desc字段,去重)""" - rows = read_csv("quest.csv") - seen = set() - result = [] - for row in rows: - if len(row) > 3: - category = row[1].strip() - desc = row[3].strip() - if category in ("1", "2") and desc and desc not in seen: - seen.add(desc) - result.append(desc) - return result - -def get_achievement_texts(): - """获取成就文本(category 9的desc字段,去重)""" - rows = read_csv("quest.csv") - seen = set() - result = [] - for row in rows: - if len(row) > 3: - category = row[1].strip() - desc = row[3].strip() - if category == "9" and desc and desc not in seen: - seen.add(desc) - result.append(desc) - return result - -def get_guide_texts(): - """获取新手指引文本(guide.csv的desc字段,去重)""" - rows = read_csv("guide.csv") - seen = set() - result = [] - for row in rows: - if len(row) > 7: - desc = row[7].strip() - if desc and desc not in seen: - seen.add(desc) - result.append(desc) - return result - -def get_prop_data(): - """获取道具数据 [(name, icon, tips), ...]""" - rows = read_csv("prop.csv") - result = [] - for row in rows: - if len(row) > 7: - name = row[2].strip() - tips = row[6].strip() - icon = row[7].strip() - if name: - result.append((name, icon, tips)) - return result - -def get_skill_data(): - """获取技能数据 [(name, icon, desc), ...]""" - rows = read_csv("skill.csv") - result = [] - for row in rows: - if len(row) > 21: - name = row[1].strip() - icon = row[6].strip() - desc = row[21].strip() - if name: - result.append((name, icon, desc)) - return result - -def get_rune_data(): - """获取灵石数据 [(name, icon, desc), ...]""" - rows = read_csv("rune.csv") - result = [] - for row in rows: - if len(row) > 8: - name = row[1].strip() - icon = row[2].strip() - desc = row[8].strip() - if name: - result.append((name, icon, desc)) - return result - -def get_talent_data(): - """获取天赋数据 [(name, icon, desc), ...] 去重""" - rows = read_csv("playerlevel.csv") - seen = set() - result = [] - for row in rows: - if len(row) > 15: - name = row[13].strip() - desc = row[14].strip() - icon = row[15].strip() - if name and name not in seen: - seen.add(name) - result.append((name, icon, desc)) - return result - -def get_equip_data(): - """获取装备数据 [(name, icon), ...] 去重""" - rows = read_csv("equip.csv") - seen = set() - result = [] - for row in rows: - if len(row) > 5: - name = row[1].strip() - icon = row[5].strip() - if name and name not in seen: - seen.add(name) - result.append((name, icon)) - return result - -def get_level_names(): - """获取关卡名称(fight_sample.csv的name字段,去重)""" - rows = read_csv("fight_sample.csv") - seen = set() - result = [] - for row in rows: - if len(row) > 2: - name = row[2].strip() - if name and name not in seen: - seen.add(name) - result.append(name) - return result - -# 玩家随机名称文本(保持不变) -PLAYER_NAMES = [ - ("李", "子轩"), ("王", "梓涵"), ("张", "浩然"), ("刘", "欣怡"), - ("陈", "宇轩"), ("杨", "晨曦"), ("赵", "俊杰"), ("黄", "雅静"), - ("周", "天佑"), ("吴", "梦琪"), ("徐", "博文"), ("孙", "思彤"), - ("胡", "睿智"), ("朱", "静怡"), ("高", "昊天"), ("林", "雨桐"), - ("何", "泽宇"), ("郭", "婉清"), ("马", "俊熙"), ("罗", "雪柔"), - ("梁", "子豪"), ("宋", "依琳"), ("郑", "宇航"), ("谢", "诗涵"), - ("韩", "志强"), ("唐", "梦瑶"), ("冯", "文博"), ("于", "雅楠"), - ("董", "天宇"), ("萧", "欣妍"), ("程", "子睿"), ("曹", "静雯"), - ("袁", "俊豪"), ("邓", "雨欣"), ("许", "浩宇"), ("傅", "语嫣"), - ("沈", "子涵"), ("曾", "思雨"), ("彭", "博涛"), ("吕", "雅婷"), - ("苏", "天翔"), ("卢", "梦洁"), ("蒋", "文轩"), ("蔡", "雪梅"), - ("贾", "志远"), ("丁", "依娜"), ("魏", "子安"), ("薛", "静雅"), - ("叶", "俊峰"), ("阎", "雨婷"), ("余", "浩轩"), ("潘", "诗琪"), - ("杜", "子恒"), ("戴", "思雅"), ("夏", "博超"), ("钟", "雅琪"), - ("汪", "天瑞"), ("田", "梦菲"), ("任", "文昊"), ("姜", "雪莲"), - ("范", "志鹏"), ("方", "依婷"), ("石", "子健"), ("姚", "静淑"), - ("谭", "俊凯"), ("廖", "雨菲"), ("邹", "浩南"), ("熊", "诗瑶"), - ("金", "子宁"), ("陆", "思琪"), ("郝", "博远"), ("孔", "雅静"), - ("白", "天浩"), ("崔", "梦婷"), ("康", "文杰"), ("毛", "雪琪"), - ("邱", "志伟"), ("秦", "依静"), ("江", "子文"), ("史", "静怡"), - ("顾", "俊杰"), ("侯", "雨萱"), ("邵", "浩天"), ("孟", "诗涵"), - ("龙", "子辰"), ("万", "思妍"), ("段", "博文"), ("雷", "雅欣"), - ("钱", "天佑"), ("汤", "梦瑶"), ("尹", "文豪"), ("黎", "雪莹"), - ("易", "志强"), ("常", "依琳"), ("武", "子轩"), ("乔", "静雯"), - ("贺", "俊熙"), ("赖", "雨桐"), ("龚", "浩宇"), ("文", "诗琪"), -] - - -# ============ 文档生成 ============ - -# 中文编号 -CN_NUMBERS = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一"] - -def add_title(doc, text): - """添加文档标题:宋体、二号(22pt)、加粗、居中、无下划线""" - para = doc.add_paragraph() - para.alignment = WD_ALIGN_PARAGRAPH.CENTER - run = para.add_run(text) - run.font.name = "宋体" - run._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体") - run.font.size = Pt(22) - run.font.bold = True - run.font.underline = False - run.font.color.rgb = None # 确保黑色,无蓝色 - -def add_section_heading(doc, cn_num, title_text): - """添加小标题:仿宋、四号(14pt)、加粗、中文编号""" - para = doc.add_paragraph() - run = para.add_run(f"{cn_num}、{title_text}") - run.font.name = "仿宋" - run._element.rPr.rFonts.set(qn("w:eastAsia"), "仿宋") - run.font.size = Pt(14) - run.font.bold = True - -def add_numbered_paragraph(doc, index, text): - """添加编号段落:仿宋、四号(14pt)、不加粗、阿拉伯数字编号""" - para = doc.add_paragraph() - run = para.add_run(f"{index}.{text}") - run.font.name = "仿宋" - run._element.rPr.rFonts.set(qn("w:eastAsia"), "仿宋") - run.font.size = Pt(14) - run.font.bold = False - -def add_numbered_items(doc, items): - """批量添加编号段落""" - for i, text in enumerate(items, 1): - add_numbered_paragraph(doc, i, text) - -def add_name_table(doc, names): - """添加玩家随机名称表格:姓/名两列""" - table = doc.add_table(rows=1, cols=2, style="Table Grid") - table.alignment = WD_TABLE_ALIGNMENT.CENTER - # 表头 - for i, header in enumerate(["姓", "名"]): - cell = table.rows[0].cells[i] - cell.text = "" - para = cell.paragraphs[0] - para.alignment = WD_ALIGN_PARAGRAPH.CENTER - run = para.add_run(header) - run.font.name = "仿宋" - run._element.rPr.rFonts.set(qn("w:eastAsia"), "仿宋") - run.font.size = Pt(14) - run.font.bold = True - # 数据行 - for surname, given_name in names: - row = table.add_row() - for j, text in enumerate([surname, given_name]): - cell = row.cells[j] - cell.text = "" - para = cell.paragraphs[0] - para.alignment = WD_ALIGN_PARAGRAPH.CENTER - run = para.add_run(text) - run.font.name = "仿宋" - run._element.rPr.rFonts.set(qn("w:eastAsia"), "仿宋") - run.font.size = Pt(14) - run.font.bold = False - -def build_document(): - doc = Document() - - # 设置默认字体 - style = doc.styles["Normal"] - style.font.name = "仿宋" - style.font.size = Pt(14) - style._element.rPr.rFonts.set(qn("w:eastAsia"), "仿宋") - - # 标题:宋体、二号、加粗、无蓝色下划线 - add_title(doc, "《不朽封仙》文字脚本") - - # ======= 一、系统提示 ======= - add_section_heading(doc, CN_NUMBERS[0], "系统提示") - add_numbered_items(doc, get_system_prompts()) - - # ======= 二、任务文本 ======= - add_section_heading(doc, CN_NUMBERS[1], "任务文本") - add_numbered_items(doc, get_quest_texts()) - - # ======= 三、成就文本 ======= - add_section_heading(doc, CN_NUMBERS[2], "成就文本") - add_numbered_items(doc, get_achievement_texts()) - - # ======= 四、玩家随机名称文本 ======= - add_section_heading(doc, CN_NUMBERS[3], "玩家随机名称文本") - add_name_table(doc, PLAYER_NAMES) - - # ======= 五、新手指引文本 ======= - add_section_heading(doc, CN_NUMBERS[4], "新手指引文本") - add_numbered_items(doc, get_guide_texts()) - - # ======= 六、道具说明 ======= - add_section_heading(doc, CN_NUMBERS[5], "道具说明") - prop_data = get_prop_data() - add_table_with_icons(doc, ["道具名称", "图标", "道具描述"], prop_data, "product") - - # ======= 七、技能 ======= - add_section_heading(doc, CN_NUMBERS[6], "技能") - skill_data = get_skill_data() - add_table_with_icons(doc, ["技能名称", "技能图标", "技能描述"], skill_data, "skill") - - # ======= 八、灵石 ======= - add_section_heading(doc, CN_NUMBERS[7], "灵石") - rune_data = get_rune_data() - add_table_with_icons(doc, ["灵石名称", "灵石图标", "灵石介绍"], rune_data, "rune") - - # ======= 九、天赋 ======= - add_section_heading(doc, CN_NUMBERS[8], "天赋") - talent_data = get_talent_data() - add_table_with_icons(doc, ["天赋名称", "天赋图标", "天赋介绍"], talent_data, "talent") - - # ======= 十、装备 ======= - add_section_heading(doc, CN_NUMBERS[9], "装备") - equip_data = get_equip_data() - add_table_equip(doc, ["装备名称", "装备图标"], equip_data, "equip") - - # ======= 十一、关卡名称 ======= - add_section_heading(doc, CN_NUMBERS[10], "关卡名称") - add_numbered_items(doc, get_level_names()) - - # 保存文档 - doc.save(OUTPUT_PATH) - print(f"文档已生成: {OUTPUT_PATH}") - - -if __name__ == "__main__": - build_document() diff --git a/wenzijiaoben/py/universal_tool.py b/wenzijiaoben/py/universal_tool.py index 94d9609..5e68119 100644 --- a/wenzijiaoben/py/universal_tool.py +++ b/wenzijiaoben/py/universal_tool.py @@ -4,6 +4,7 @@ 功能:自动扫描项目目录,识别标准结构,生成游戏文字脚本Word文档。 无需手动配置路径,只需提供项目根目录即可自动完成全部流程。 +配置表直接读取 xlsx(优先)或 csv,无需预先转换。 标准目录结构约定: / @@ -11,7 +12,9 @@ ├── *_game/ ← 游戏客户端目录 │ └── unity/Assets/Resources/.../atlas_ui/ ← 图标资源 │ └── unity/Assets/Scripts/.../*Const*.cs ← 系统提示常量 - └── work/ ← 工作目录 (CSV缓存、输出等) + └── work/ ← 工作目录 (输出等) + +正文编号使用 Word 自动编号列表,在 Word 中增删条目后编号会自动重排。 用法: python universal_tool.py [项目路径] [--output 输出路径] @@ -23,18 +26,18 @@ import csv import os import re import sys -import glob import argparse from docx import Document from docx.shared import Pt, Cm from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.table import WD_TABLE_ALIGNMENT from docx.oxml.ns import qn +from docx.oxml import OxmlElement # ============ 常量 ============ CN_NUMBERS = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一"] -# 11章节数据源定义(字段名取自CSV英文头行第3行) +# 11章节数据源定义(字段名取自表格英文头行第3行;优先读 xlsx,其次 csv) SECTION_DEFS = { "quest_texts": {"csv": "quest", "field": "desc", "filter": {"category": ["1", "2"]}}, "achievements": {"csv": "quest", "field": "desc", "filter": {"category": ["9"]}}, @@ -86,7 +89,8 @@ class ProjectScanner: base = os.path.basename(self.config_dir) self.project_name = base.replace("_config", "") - # 3. CSV目录: work/cvs 优先,其次 config_dir 本身 + # 3. 数据表目录(csv_dir): work/cvs 优先(若存在),否则用 config_dir 本身(含xlsx) + # find_file 优先查找 .xlsx,其次 .csv,两种格式都直接读取 if self.work_dir: cvs = os.path.join(self.work_dir, "cvs") if os.path.isdir(cvs) and os.listdir(cvs): @@ -139,11 +143,11 @@ class ProjectScanner: return def find_file(self, name): - """在 csv_dir 或 config_dir 中查找文件(支持 .csv/.xlsx)""" + """在 csv_dir 或 config_dir 中查找文件(优先 .xlsx,其次 .csv)""" for d in [self.csv_dir, self.config_dir]: if not d: continue - for ext in [".csv", ".xlsx"]: + for ext in [".xlsx", ".csv"]: p = os.path.join(d, name + ext) if os.path.isfile(p): return p @@ -155,7 +159,7 @@ class ProjectScanner: f" 项目名称: {self.project_name}", f" 配置目录: {self.config_dir or '未找到'}", f" 游戏目录: {self.game_dir or '未找到'}", - f" CSV目录: {self.csv_dir or '未找到'}", + f" 数据表目录: {self.csv_dir or '未找到'}", f" 图标目录: {len(self.icon_dirs)}个 {list(self.icon_dirs.keys())}", f" 脚本目录: {self.scripts_dir or '未找到'}", f" 常量文件: {os.path.basename(self.game_text_const) if self.game_text_const else '未找到'}", @@ -164,16 +168,24 @@ class ProjectScanner: # ================================================================ -# 二、CSV 数据读取(自动编码 + 动态字段名解析) +# 二、表格数据读取(xlsx/csv 自动识别 + 动态字段名解析) # ================================================================ -def read_csv(filepath): +def read_table(filepath): """ - 读取CSV文件,自动检测编码 (utf-8-sig/gbk/gb2312/utf-8)。 - 返回 (headers, rows): + 统一表格读取(支持 .xlsx / .csv),返回 (headers, rows): headers: 第3行英文字段名列表 rows: 第4行起的数据行列表 (list of list) + xlsx 直接读取,无需先转 csv。 """ + ext = os.path.splitext(filepath)[1].lower() + if ext == ".xlsx": + return _read_xlsx(filepath) + return _read_csv(filepath) + + +def _read_csv(filepath): + """读取CSV文件,自动检测编码 (utf-8-sig/gbk/gb2312/utf-8)。""" for enc in ["utf-8-sig", "gbk", "gb2312", "utf-8"]: try: with open(filepath, "r", encoding=enc) as f: @@ -186,6 +198,20 @@ def read_csv(filepath): return [], [] +def _read_xlsx(filepath): + """直接读取 xlsx(无需先转csv),表头结构与csv一致:第3行字段名,第4行起数据。""" + from openpyxl import load_workbook + wb = load_workbook(filepath, read_only=True, data_only=True) + ws = wb.active + all_rows = [] + for row in ws.iter_rows(values_only=True): + all_rows.append([str(c) if c is not None else "" for c in row]) + wb.close() + if len(all_rows) < 4: + return all_rows[-1] if all_rows else [], [] + return all_rows[2], all_rows[3:] + + def field_map(headers): """将英文头行转为 {字段名: 列索引} 映射""" return {h.strip(): i for i, h in enumerate(headers) if h.strip()} @@ -199,39 +225,6 @@ def field_val(row, fm, name, default=""): return default -def xlsx_to_csv_batch(config_dir, output_dir): - """将 config_dir 中所有 xlsx 批量转为 csv""" - from openpyxl import load_workbook - os.makedirs(output_dir, exist_ok=True) - count = 0 - for xlsx in glob.glob(os.path.join(config_dir, "*.xlsx")): - fname = os.path.basename(xlsx) - if fname.startswith("~$"): - continue - csv_path = os.path.join(output_dir, os.path.splitext(fname)[0] + ".csv") - try: - wb = load_workbook(xlsx, read_only=True, data_only=True) - ws = wb.active - with open(csv_path, "w", newline="", encoding="utf-8-sig") as f: - writer = csv.writer(f) - for row in ws.iter_rows(values_only=True): - writer.writerow([c if c is not None else "" for c in row]) - wb.close() - count += 1 - except Exception: - pass - # 复制非xlsx文件 - import shutil - for entry in os.listdir(config_dir): - src = os.path.join(config_dir, entry) - if not os.path.isfile(src) or entry.endswith(".xlsx"): - continue - dst = os.path.join(output_dir, entry) - if not os.path.exists(dst): - shutil.copy2(src, dst) - return count - - # ================================================================ # 三、图片资源查找 # ================================================================ @@ -353,11 +346,106 @@ def add_section_heading(doc, cn_num, title): _run(p, f"{cn_num}、{title}", font="仿宋", size=14, bold=True) +# ---- Word 自动编号辅助 ---- +# 用 Word 的 numbering 机制生成 1. 2. 3. ... 列表, +# 这样在 Word 中增删条目后编号会自动重排,无需手改数字。 + +def _get_numbering_element(doc): + """获取文档的 根元素(默认模板已含 numbering part)。""" + try: + return doc.part.numbering_part.element + except KeyError: + raise RuntimeError("当前文档模板缺少 numbering part,无法使用自动编号。") + + +def _create_decimal_numbering(doc): + """ + 新建一个十进制自动编号定义 (1. 2. 3. ...),返回新的 numId。 + 每次调用返回独立的 numId,编号从 1 开始, + 因此每个章节调用一次即可获得独立计数。 + """ + numbering = _get_numbering_element(doc) + + abs_ids = [int(an.get(qn('w:abstractNumId'))) for an in numbering.findall(qn('w:abstractNum'))] + num_ids = [int(n.get(qn('w:numId'))) for n in numbering.findall(qn('w:num'))] + next_abs = (max(abs_ids) + 1) if abs_ids else 0 + next_num = (max(num_ids) + 1) if num_ids else 1 + + # + abstract_num = OxmlElement('w:abstractNum') + abstract_num.set(qn('w:abstractNumId'), str(next_abs)) + multi_level = OxmlElement('w:multiLevelType') + multi_level.set(qn('w:val'), 'singleLevel') + abstract_num.append(multi_level) + + lvl = OxmlElement('w:lvl') + lvl.set(qn('w:ilvl'), '0') + start = OxmlElement('w:start') + start.set(qn('w:val'), '1') + lvl.append(start) + num_fmt = OxmlElement('w:numFmt') + num_fmt.set(qn('w:val'), 'decimal') + lvl.append(num_fmt) + lvl_text = OxmlElement('w:lvlText') + lvl_text.set(qn('w:val'), '%1.') + lvl.append(lvl_text) + lvl_jc = OxmlElement('w:lvlJc') + lvl_jc.set(qn('w:val'), 'left') + lvl.append(lvl_jc) + p_pr = OxmlElement('w:pPr') + ind = OxmlElement('w:ind') + ind.set(qn('w:left'), '420') + ind.set(qn('w:hanging'), '420') + p_pr.append(ind) + lvl.append(p_pr) + abstract_num.append(lvl) + + # + num = OxmlElement('w:num') + num.set(qn('w:numId'), str(next_num)) + abs_ref = OxmlElement('w:abstractNumId') + abs_ref.set(qn('w:val'), str(next_abs)) + num.append(abs_ref) + + # abstractNum 必须排在所有 num 之前 + last_abs = numbering.findall(qn('w:abstractNum')) + if last_abs: + last_abs[-1].addnext(abstract_num) + else: + numbering.insert(0, abstract_num) + numbering.append(num) + + return next_num + + +def _apply_numbering(paragraph, num_id, ilvl=0): + """给段落应用自动编号 (设置 numPr)。""" + p_pr = paragraph._p.get_or_add_pPr() + for existing in p_pr.findall(qn('w:numPr')): + p_pr.remove(existing) + num_pr = OxmlElement('w:numPr') + ilvl_el = OxmlElement('w:ilvl') + ilvl_el.set(qn('w:val'), str(ilvl)) + num_pr.append(ilvl_el) + num_id_el = OxmlElement('w:numId') + num_id_el.set(qn('w:val'), str(num_id)) + num_pr.append(num_id_el) + p_pr.append(num_pr) + + def add_numbered_items(doc, items): - """编号列表:仿宋 四号 不加粗""" - for i, text in enumerate(items, 1): + """ + 编号列表:仿宋 四号 不加粗,使用 Word 自动编号。 + 每次调用创建独立编号序列(从 1 开始), + 在 Word 中增删条目后编号会自动重排。 + """ + if not items: + return + num_id = _create_decimal_numbering(doc) + for text in items: p = doc.add_paragraph() - _run(p, f"{i}.{text}", font="仿宋", size=14, bold=False) + _apply_numbering(p, num_id) + _run(p, str(text), font="仿宋", size=14, bold=False) def _set_cell(cell, text, bold=False, size=14): @@ -412,10 +500,10 @@ def add_name_table(doc, names): # ================================================================ def get_player_names(scanner): - """获取玩家随机名称(优先 name.csv,否则使用默认列表)""" + """获取玩家随机名称(优先 name.xlsx/csv,否则使用默认列表)""" fp = scanner.find_file("name") - if fp and fp.endswith(".csv"): - headers, rows = read_csv(fp) + if fp: + headers, rows = read_table(fp) fm = field_map(headers) if "first" in fm and "last" in fm: seen = set() @@ -461,11 +549,11 @@ def get_player_names(scanner): def get_csv_list_data(scanner, csv_name, field_name, filter_dict=None): - """通用:从CSV中按字段名提取去重文本列表""" + """通用:从表格(xlsx/csv)中按字段名提取去重文本列表""" fp = scanner.find_file(csv_name) - if not fp or not fp.endswith(".csv"): + if not fp: return [] - headers, rows = read_csv(fp) + headers, rows = read_table(fp) fm = field_map(headers) seen = set() result = [] @@ -487,11 +575,11 @@ def get_csv_list_data(scanner, csv_name, field_name, filter_dict=None): def get_csv_table_data(scanner, csv_name, field_names, dedup_field=None): - """通用:从CSV中提取多字段表格数据 [(f1, f2, f3, ...), ...]""" + """通用:从表格(xlsx/csv)中提取多字段表格数据 [(f1, f2, f3, ...), ...]""" fp = scanner.find_file(csv_name) - if not fp or not fp.endswith(".csv"): + if not fp: return [] - headers, rows = read_csv(fp) + headers, rows = read_table(fp) fm = field_map(headers) seen = set() result = [] @@ -516,14 +604,13 @@ def build_document(scanner, output_path): """根据扫描结果构建完整文档""" print("\n[1/4] 准备数据源...") - # 确保CSV可用 - if not scanner.csv_dir or not any(f.endswith(".csv") for f in os.listdir(scanner.csv_dir)): - if scanner.config_dir: - print(f" CSV目录为空,正在从 {os.path.basename(scanner.config_dir)} 转换xlsx...") - cvs_out = os.path.join(scanner.work_dir or scanner.root, "cvs") - n = xlsx_to_csv_batch(scanner.config_dir, cvs_out) - scanner.csv_dir = cvs_out - print(f" 已转换 {n} 个xlsx文件") + # 直接从 config_dir / csv_dir 读取 xlsx(优先) 或 csv,不再转换。 + data_dir = scanner.csv_dir or scanner.config_dir + if data_dir: + files = [f for f in os.listdir(data_dir) if not f.startswith("~$")] + xlsx_n = sum(1 for f in files if f.endswith(".xlsx")) + csv_n = sum(1 for f in files if f.endswith(".csv")) + print(f" 数据目录: {data_dir} (xlsx {xlsx_n} 个 / csv {csv_n} 个)") # 预加载数据 system_prompts = parse_game_text_const(scanner.game_text_const, scanner.scripts_dir) diff --git a/wenzijiaoben/py/xlsx_to_csv.py b/wenzijiaoben/py/xlsx_to_csv.py deleted file mode 100644 index 51b2083..0000000 --- a/wenzijiaoben/py/xlsx_to_csv.py +++ /dev/null @@ -1,104 +0,0 @@ -""" -xlsx转csv工具 -- 将指定目录下所有.xlsx文件转换为.csv格式 -- 输出路径:D:\15CVS -- 只处理每个xlsx文件的第一个工作表(主数据表) -- 忽略以~$开头的临时文件 -- CSV编码:UTF-8 with BOM -- 避免覆盖同名文件(如已存在则跳过并提示) -""" - -import os -import sys -import csv -import glob -from openpyxl import load_workbook - - -def xlsx_to_csv(src_dir, out_dir): - """将源目录下所有xlsx文件转换为csv格式""" - # 确保输出目录存在 - os.makedirs(out_dir, exist_ok=True) - - # 统计计数 - success_count = 0 - skip_count = 0 - fail_count = 0 - - # 遍历源目录下所有xlsx文件(不递归子目录) - xlsx_files = glob.glob(os.path.join(src_dir, "*.xlsx")) - - for xlsx_path in xlsx_files: - filename = os.path.basename(xlsx_path) - - # 跳过以~$开头的临时文件 - if filename.startswith("~$"): - print(f"[跳过] 临时文件: {filename}") - skip_count += 1 - continue - - csv_filename = os.path.splitext(filename)[0] + ".csv" - csv_path = os.path.join(out_dir, csv_filename) - - # 检查目标文件是否已存在 - if os.path.exists(csv_path): - print(f"[跳过] 文件已存在: {csv_filename}") - skip_count += 1 - continue - - try: - print(f"[转换] {filename} -> {csv_filename}", end=" ... ") - - # 读取xlsx文件(只读模式,提高性能) - wb = load_workbook(xlsx_path, read_only=True, data_only=True) - ws = wb.active # 获取第一个工作表 - - # 写入CSV文件 - with open(csv_path, "w", newline="", encoding="utf-8-sig") as f: - writer = csv.writer(f) - - for row in ws.iter_rows(values_only=True): - # 将None转为空字符串,其他值保持原样 - csv_row = [cell if cell is not None else "" for cell in row] - writer.writerow(csv_row) - - wb.close() - print("成功") - success_count += 1 - - except Exception as e: - print(f"失败") - print(f" [错误] {filename}: {e}") - fail_count += 1 - # 清理可能生成的不完整文件 - if os.path.exists(csv_path): - try: - os.remove(csv_path) - except Exception: - pass - - # 打印统计结果 - print("\n" + "=" * 50) - print(f"转换完成!") - print(f" 成功: {success_count} 个") - print(f" 跳过: {skip_count} 个") - print(f" 失败: {fail_count} 个") - print(f" 总计: {success_count + skip_count + fail_count} 个") - print("=" * 50) - - -if __name__ == "__main__": - src_dir = r"d:\A工作\P-L15_config" - out_dir = r"D:\15CVS" - - # 支持命令行参数修改源目录 - if len(sys.argv) > 1: - src_dir = sys.argv[1] - if len(sys.argv) > 2: - out_dir = sys.argv[2] - - print(f"源目录: {src_dir}") - print(f"输出目录: {out_dir}") - print("-" * 50) - - xlsx_to_csv(src_dir, out_dir) diff --git a/wenzijiaoben/使用请看.txt b/wenzijiaoben/使用请看.txt index 67337cc..885da93 100644 --- a/wenzijiaoben/使用请看.txt +++ b/wenzijiaoben/使用请看.txt @@ -1 +1,30 @@ -将PY放入项目文件中,运行universal_tool.py他会自动读取设定好的表格对应关系进行自动生成 \ No newline at end of file +通用游戏文字脚本文档生成工具 使用说明 +入口脚本: py/universal_tool.py + +一、用法 + python universal_tool.py <项目根目录> [--output 输出路径] + 不带参数则用当前目录作为项目根目录。 + +二、它会自动扫描项目目录结构: + <项目根目录>/ + ├── *_config/ 配置表目录 (优先读 .xlsx,其次 .csv,无需预先转换) + ├── *_game/ 游戏客户端目录 + │ └── .../atlas_ui/atlas_* 图标资源 + │ └── .../Scripts/*Const*.cs 系统提示常量 + └── work/ 输出目录 (文档默认生成到此) + +三、输出 + 默认: <项目根目录>/work/《<项目名>》游戏文字脚本.docx + 可用 --output 指定路径。 + +四、特性 + - 配置表直接读 xlsx,不再需要先转成 csv。 + - 正文条目使用 Word 自动编号,在 Word 中增删条目后编号会自动重排。 + - 依赖: pip install openpyxl python-docx + +五、示例 + python universal_tool.py d:\p-L12 + python universal_tool.py d:\p-L12 --output d:\out\脚本.docx + +注: memories/ 目录下的 json 仅为开发参考(数据源映射/格式规范/图标规则等), + 脚本运行时不读取它们,全部信息由自动扫描项目目录获得。