文字脚本自动生成
This commit is contained in:
108
wenzijiaoben/memories/data_source_mapping.json
Normal file
108
wenzijiaoben/memories/data_source_mapping.json
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
"description": "CSV数据源到文档章节的映射关系",
|
||||||
|
"csv_base_dir": "d:\\p-L12\\work\\cvs",
|
||||||
|
"mappings": {
|
||||||
|
"system_prompts": {
|
||||||
|
"source": "GameTextConst.cs (代码引用分析)",
|
||||||
|
"section": "一、系统提示",
|
||||||
|
"field": "常量值文本",
|
||||||
|
"filter": "仅保留在代码中被实际引用的常量(排除仅在注释中出现的)",
|
||||||
|
"dedup": "按文本值去重"
|
||||||
|
},
|
||||||
|
"quest_texts": {
|
||||||
|
"source": "quest.csv",
|
||||||
|
"section": "二、任务文本",
|
||||||
|
"field": "desc (第4列, index=3)",
|
||||||
|
"filter": "category == 1 或 category == 2",
|
||||||
|
"dedup": "按desc文本去重"
|
||||||
|
},
|
||||||
|
"achievement_texts": {
|
||||||
|
"source": "quest.csv",
|
||||||
|
"section": "三、成就文本",
|
||||||
|
"field": "desc (第4列, index=3)",
|
||||||
|
"filter": "category == 9",
|
||||||
|
"dedup": "按desc文本去重"
|
||||||
|
},
|
||||||
|
"player_names": {
|
||||||
|
"source": "原始文档固定数据 (100对姓名)",
|
||||||
|
"section": "四、玩家随机名称文本",
|
||||||
|
"format": "表格: 姓 | 名"
|
||||||
|
},
|
||||||
|
"guide_texts": {
|
||||||
|
"source": "guide.csv",
|
||||||
|
"section": "五、新手指引文本",
|
||||||
|
"field": "desc (第8列, index=7)",
|
||||||
|
"filter": "无",
|
||||||
|
"dedup": "按desc文本去重"
|
||||||
|
},
|
||||||
|
"props": {
|
||||||
|
"source": "prop.csv",
|
||||||
|
"section": "六、道具说明",
|
||||||
|
"fields": {
|
||||||
|
"name": "第3列 (index=2)",
|
||||||
|
"tips": "第7列 (index=6)",
|
||||||
|
"icon": "第8列 (index=7)"
|
||||||
|
},
|
||||||
|
"icon_dir": "atlas_product_icons",
|
||||||
|
"icon_naming": "icon_product_XX.png (XX为补零两位数, 如 icon_product_01)"
|
||||||
|
},
|
||||||
|
"skills": {
|
||||||
|
"source": "skill.csv",
|
||||||
|
"section": "七、技能",
|
||||||
|
"fields": {
|
||||||
|
"name": "第2列 (index=1)",
|
||||||
|
"icon": "第7列 (index=6)",
|
||||||
|
"desc": "第22列 (index=21)"
|
||||||
|
},
|
||||||
|
"icon_dir": "atlas_skill",
|
||||||
|
"icon_naming": "skillX.png (X为数字, 不补零, 如 skill1, skill10)"
|
||||||
|
},
|
||||||
|
"runes": {
|
||||||
|
"source": "rune.csv",
|
||||||
|
"section": "八、灵石",
|
||||||
|
"fields": {
|
||||||
|
"name": "第2列 (index=1)",
|
||||||
|
"icon": "第3列 (index=2)",
|
||||||
|
"desc": "第9列 (index=8)"
|
||||||
|
},
|
||||||
|
"icon_dir": "atlas_rune_icons",
|
||||||
|
"icon_naming": "rune_icon_X.png (X为数字, 不补零)"
|
||||||
|
},
|
||||||
|
"talents": {
|
||||||
|
"source": "playerlevel.csv",
|
||||||
|
"section": "九、天赋",
|
||||||
|
"fields": {
|
||||||
|
"name": "第14列 (index=13)",
|
||||||
|
"desc": "第15列 (index=14)",
|
||||||
|
"icon": "第16列 (index=15)"
|
||||||
|
},
|
||||||
|
"icon_dir": "atlas_talent_icons",
|
||||||
|
"icon_naming": "icon_talent_X.png (X为数字, 不补零)",
|
||||||
|
"dedup": "按name去重"
|
||||||
|
},
|
||||||
|
"equipment": {
|
||||||
|
"source": "equip.csv",
|
||||||
|
"section": "十、装备",
|
||||||
|
"fields": {
|
||||||
|
"name": "第2列 (index=1)",
|
||||||
|
"icon": "第6列 (index=5)"
|
||||||
|
},
|
||||||
|
"icon_dir": "atlas_equip_icons",
|
||||||
|
"icon_naming": "equip_icon_X.png (X为数字, 不补零)",
|
||||||
|
"dedup": "按name去重",
|
||||||
|
"note": "equip.csv无desc字段"
|
||||||
|
},
|
||||||
|
"level_names": {
|
||||||
|
"source": "fight_sample.csv",
|
||||||
|
"section": "十一、关卡名称",
|
||||||
|
"field": "name (第3列, index=2)",
|
||||||
|
"filter": "无",
|
||||||
|
"dedup": "按name去重"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"csv_format": {
|
||||||
|
"header_rows": 3,
|
||||||
|
"header_structure": "第1行:中文头, 第2行:类型定义, 第3行:英文字段名",
|
||||||
|
"encoding_note": "部分文件为GBK编码,需自动检测"
|
||||||
|
}
|
||||||
|
}
|
||||||
52
wenzijiaoben/memories/doc_format_spec.json
Normal file
52
wenzijiaoben/memories/doc_format_spec.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"document_title": "《不朽封仙》文字脚本",
|
||||||
|
"sections": [
|
||||||
|
{"number": "一", "title": "系统提示", "type": "numbered_list"},
|
||||||
|
{"number": "二", "title": "任务文本", "type": "numbered_list"},
|
||||||
|
{"number": "三", "title": "成就文本", "type": "numbered_list"},
|
||||||
|
{"number": "四", "title": "玩家随机名称文本", "type": "table", "columns": ["姓", "名"]},
|
||||||
|
{"number": "五", "title": "新手指引文本", "type": "numbered_list"},
|
||||||
|
{"number": "六", "title": "道具说明", "type": "table", "columns": ["道具名称", "图标", "道具描述"]},
|
||||||
|
{"number": "七", "title": "技能", "type": "table", "columns": ["技能名称", "技能图标", "技能描述"]},
|
||||||
|
{"number": "八", "title": "灵石", "type": "table", "columns": ["灵石名称", "灵石图标", "灵石介绍"]},
|
||||||
|
{"number": "九", "title": "天赋", "type": "table", "columns": ["天赋名称", "天赋图标", "天赋介绍"]},
|
||||||
|
{"number": "十", "title": "装备", "type": "table", "columns": ["装备名称", "装备图标"]},
|
||||||
|
{"number": "十一", "title": "关卡名称", "type": "numbered_list"}
|
||||||
|
],
|
||||||
|
"format_rules": {
|
||||||
|
"title": {
|
||||||
|
"font": "宋体",
|
||||||
|
"font_en": "SimSun",
|
||||||
|
"size": "二号 (22pt)",
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"color": "黑色 (无蓝色)",
|
||||||
|
"alignment": "居中"
|
||||||
|
},
|
||||||
|
"section_heading": {
|
||||||
|
"font": "仿宋",
|
||||||
|
"font_en": "FangSong",
|
||||||
|
"size": "四号 (14pt)",
|
||||||
|
"bold": true,
|
||||||
|
"numbering": "中文数字 (一、二、三...十一)"
|
||||||
|
},
|
||||||
|
"text_content": {
|
||||||
|
"font": "仿宋",
|
||||||
|
"font_en": "FangSong",
|
||||||
|
"size": "四号 (14pt)",
|
||||||
|
"bold": false,
|
||||||
|
"numbering": "阿拉伯数字 (1. 2. 3. ...)"
|
||||||
|
},
|
||||||
|
"table_images": {
|
||||||
|
"height": "3cm",
|
||||||
|
"aspect_ratio": "锁定纵横比",
|
||||||
|
"missing_image": "留空"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dedup_rules": {
|
||||||
|
"general": "所有文本和表格内容不得出现重复项",
|
||||||
|
"system_prompts": "GameTextConst中相同文本值的常量只保留第一个",
|
||||||
|
"talents": "天赋按name去重(同一天赋在不同等级重复出现)",
|
||||||
|
"equipment": "装备按name去重(同一装备不同品质重复出现)"
|
||||||
|
}
|
||||||
|
}
|
||||||
52
wenzijiaoben/memories/icon_resource_mapping.json
Normal file
52
wenzijiaoben/memories/icon_resource_mapping.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"description": "图标资源名称到图片文件的映射规则与目录结构",
|
||||||
|
"base_dir": "d:\\p-L12\\P-L12_game\\unity\\Assets\\Resources\\Resources_moved\\gui\\atlas_ui",
|
||||||
|
"directories": {
|
||||||
|
"atlas_product_icons": {
|
||||||
|
"section": "六、道具说明",
|
||||||
|
"csv_field": "prop.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,
|
||||||
|
"missing_icons": []
|
||||||
|
},
|
||||||
|
"atlas_skill": {
|
||||||
|
"section": "七、技能",
|
||||||
|
"csv_field": "skill.csv icon (index=6)",
|
||||||
|
"naming_pattern": "skillX.png",
|
||||||
|
"naming_note": "X为数字不补零 (1-99), 如 skill1, skill10, skill99",
|
||||||
|
"file_count": 99,
|
||||||
|
"missing_icons": []
|
||||||
|
},
|
||||||
|
"atlas_rune_icons": {
|
||||||
|
"section": "八、灵石",
|
||||||
|
"csv_field": "rune.csv icon (index=2)",
|
||||||
|
"naming_pattern": "rune_icon_X.png",
|
||||||
|
"naming_note": "X为数字不补零 (1-18)",
|
||||||
|
"file_count": 12,
|
||||||
|
"missing_icons": ["rune_icon_13", "rune_icon_14", "rune_icon_15", "rune_icon_16", "rune_icon_17", "rune_icon_18"]
|
||||||
|
},
|
||||||
|
"atlas_talent_icons": {
|
||||||
|
"section": "九、天赋",
|
||||||
|
"csv_field": "playerlevel.csv icon (index=15)",
|
||||||
|
"naming_pattern": "icon_talent_X.png",
|
||||||
|
"naming_note": "X为数字不补零 (1-6)",
|
||||||
|
"file_count": 6,
|
||||||
|
"missing_icons": []
|
||||||
|
},
|
||||||
|
"atlas_equip_icons": {
|
||||||
|
"section": "十、装备",
|
||||||
|
"csv_field": "equip.csv icon (index=5)",
|
||||||
|
"naming_pattern": "equip_icon_X.png",
|
||||||
|
"naming_note": "X为数字不补零 (1-6)",
|
||||||
|
"file_count": 6,
|
||||||
|
"missing_icons": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"image_lookup_strategy": "先直接匹配 icon_name.png,若失败则对末尾数字补零至2位或3位重试",
|
||||||
|
"image_insert_rules": {
|
||||||
|
"height": "3cm",
|
||||||
|
"aspect_ratio": "锁定纵横比",
|
||||||
|
"missing_image_action": "留空单元格"
|
||||||
|
}
|
||||||
|
}
|
||||||
33
wenzijiaoben/memories/project_config.json
Normal file
33
wenzijiaoben/memories/project_config.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"xlsx_to_csv": "d:\\p-L12\\work\\py\\xlsx_to_csv.py",
|
||||||
|
"generate_doc": "d:\\p-L12\\work\\py\\generate_doc.py"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"python_packages": ["openpyxl", "python-docx"],
|
||||||
|
"install_commands": [
|
||||||
|
"pip install openpyxl",
|
||||||
|
"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)"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"icon_resource_base": "d:\\p-L12\\P-L12_game\\unity\\Assets\\Resources\\Resources_moved\\gui\\atlas_ui"
|
||||||
|
}
|
||||||
104
wenzijiaoben/memories/system_prompts.json
Normal file
104
wenzijiaoben/memories/system_prompts.json
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
{
|
||||||
|
"description": "GameTextConst.cs 中被代码实际引用的系统提示常量",
|
||||||
|
"source_file": "d:\\p-L12\\P-L12_game\\unity\\Assets\\Scripts\\mgame\\error\\GameTextConst.cs",
|
||||||
|
"total_referenced": 85,
|
||||||
|
"total_unique_text": 83,
|
||||||
|
"note": "85个被引用常量中,有2对文本值重复('{0}不足。' 和 '保存失败。'),去重后为83条",
|
||||||
|
"referenced_constants": {
|
||||||
|
"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": "公会名称不能超过6个字符。",
|
||||||
|
"ErrorGuildSetSuccess": "设置公会成功。",
|
||||||
|
"ErrorHeroLevelUnlock": "修士{0}级解锁。",
|
||||||
|
"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": "通过{0}后解锁。",
|
||||||
|
"ErrorQuickSweepTimesNotEnough": "快速游历次数不足。",
|
||||||
|
"ErrorQuitGuildSuccess": "退出公会成功。",
|
||||||
|
"ErrorRefreshTimesNotEnough": "刷新次数不足。",
|
||||||
|
"ErrorRetreatNotNow": "现在还不能撤离。",
|
||||||
|
"ErrorRewardAlreadyClaimed": "您已领奖。",
|
||||||
|
"ErrorRunePutOnSuccess": "镶嵌成功。",
|
||||||
|
"ErrorRuneSlotFull": "铭文槽已满,请先卸下。",
|
||||||
|
"ErrorSaveHostage": "解救{0}个人质后解锁。",
|
||||||
|
"ErrorSaveSuccess": "保存成功。",
|
||||||
|
"ErrorScoreNotEnough": "积分不足。",
|
||||||
|
"ErrorSendGiftSuccess": "赠送成功",
|
||||||
|
"ErrorSetSuccess": "修改成功。",
|
||||||
|
"ErrorSomethingNotEnough": "{0}不足。",
|
||||||
|
"ErrorTakesuccess": "领取成功。",
|
||||||
|
"ErrorTargetNotEnough": "{0}不足。",
|
||||||
|
"ErrorTaskFailed": "领取失败。",
|
||||||
|
"ErrorTaskNotFinish": "任务未完成。",
|
||||||
|
"ErrorTaskRewardNotClaimed": "任务奖励未领取。",
|
||||||
|
"ErrorTimesNotEnough": "次数不足。",
|
||||||
|
"ErrorTitleNotUnlock": "称号未解锁。",
|
||||||
|
"ErrorUpLevelFirst": "请先升级。",
|
||||||
|
"ErrorUpLevelSuccess": "升级成功。",
|
||||||
|
"ErrorUpStage1First": "请先升品。",
|
||||||
|
"ErrorUpStageFirst": "请先升阶。",
|
||||||
|
"ErrorUpStageSuccess": "升阶成功。",
|
||||||
|
"ErrorUpStrengthFirst": "请先强化。",
|
||||||
|
"ErrorVipLevelNotEnough": "权益等级不足。",
|
||||||
|
"TipChallengeTimesConfirm": "\u3000是否消耗{0}进行挑战。\n\u3000今日剩余(<color=#19F45C>{1}</color>/{2})次。",
|
||||||
|
"TipFusionSuccess": "合成成功。"
|
||||||
|
},
|
||||||
|
"duplicates_removed": [
|
||||||
|
{"kept": "ErrorTargetNotEnough", "removed": "ErrorSomethingNotEnough", "text": "{0}不足。"},
|
||||||
|
{"kept": "ErrorBuyFailed", "removed": "ErrorSaveFailed", "text": "保存失败。"}
|
||||||
|
],
|
||||||
|
"unreferenced_constants": [
|
||||||
|
"ErrorUseFailed", "ErrorStrengthLimit", "ErrorRecruitTimesNotEnough",
|
||||||
|
"ErrorRecruitTicketNotEnough", "ErrorRecruitDiamondNotEnough",
|
||||||
|
"ErrorUpStarFirst", "ErrorUpStrengthFirst", "ErrorTujianProgressNotEnough",
|
||||||
|
"ErrorHeroMaxCountReached", "ErrorNoHeroToCombine", "ErrorStrengthSuccess",
|
||||||
|
"ErrorSaveFailed", "ErrorInvalidId", "ErrorReceiveConditionNotMet"
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
wenzijiaoben/py/__pycache__/generate_doc.cpython-311.pyc
Normal file
BIN
wenzijiaoben/py/__pycache__/generate_doc.cpython-311.pyc
Normal file
Binary file not shown.
568
wenzijiaoben/py/generate_doc.py
Normal file
568
wenzijiaoben/py/generate_doc.py
Normal file
@@ -0,0 +1,568 @@
|
|||||||
|
# -*- 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今日剩余(<color=#19F45C>{1}</color>/{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()
|
||||||
682
wenzijiaoben/py/universal_tool.py
Normal file
682
wenzijiaoben/py/universal_tool.py
Normal file
@@ -0,0 +1,682 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
通用游戏文字脚本文档生成工具
|
||||||
|
|
||||||
|
功能:自动扫描项目目录,识别标准结构,生成游戏文字脚本Word文档。
|
||||||
|
无需手动配置路径,只需提供项目根目录即可自动完成全部流程。
|
||||||
|
|
||||||
|
标准目录结构约定:
|
||||||
|
<project_root>/
|
||||||
|
├── *_config/ ← 配置表目录 (xlsx/csv)
|
||||||
|
├── *_game/ ← 游戏客户端目录
|
||||||
|
│ └── unity/Assets/Resources/.../atlas_ui/ ← 图标资源
|
||||||
|
│ └── unity/Assets/Scripts/.../*Const*.cs ← 系统提示常量
|
||||||
|
└── work/ ← 工作目录 (CSV缓存、输出等)
|
||||||
|
|
||||||
|
用法:
|
||||||
|
python universal_tool.py [项目路径] [--output 输出路径]
|
||||||
|
|
||||||
|
依赖:pip install openpyxl python-docx
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# ============ 常量 ============
|
||||||
|
CN_NUMBERS = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一"]
|
||||||
|
|
||||||
|
# 11章节数据源定义(字段名取自CSV英文头行第3行)
|
||||||
|
SECTION_DEFS = {
|
||||||
|
"quest_texts": {"csv": "quest", "field": "desc", "filter": {"category": ["1", "2"]}},
|
||||||
|
"achievements": {"csv": "quest", "field": "desc", "filter": {"category": ["9"]}},
|
||||||
|
"guide_texts": {"csv": "guide", "field": "desc"},
|
||||||
|
"props": {"csv": "prop", "fields": ["name", "icon", "tips"], "icon_key": "atlas_product_icons"},
|
||||||
|
"skills": {"csv": "skill", "fields": ["name", "icon", "desc"], "icon_key": "atlas_skill"},
|
||||||
|
"runes": {"csv": "rune", "fields": ["name", "icon", "desc"], "icon_key": "atlas_rune_icons"},
|
||||||
|
"talents": {"csv": "playerlevel", "fields": ["name", "icon", "desc"], "icon_key": "atlas_talent_icons", "dedup": "name"},
|
||||||
|
"equips": {"csv": "equip", "fields": ["name", "icon"], "icon_key": "atlas_equip_icons", "dedup": "name"},
|
||||||
|
"level_names": {"csv": "fight_sample", "field": "name"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# 一、项目结构自动扫描
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
class ProjectScanner:
|
||||||
|
"""自动扫描并识别项目目录结构"""
|
||||||
|
|
||||||
|
def __init__(self, project_root):
|
||||||
|
self.root = os.path.abspath(project_root)
|
||||||
|
self.config_dir = None
|
||||||
|
self.game_dir = None
|
||||||
|
self.csv_dir = None
|
||||||
|
self.work_dir = None
|
||||||
|
self.resources_dir = None
|
||||||
|
self.icon_dirs = {}
|
||||||
|
self.scripts_dir = None
|
||||||
|
self.game_text_const = None
|
||||||
|
self.project_name = ""
|
||||||
|
self._scan()
|
||||||
|
|
||||||
|
def _scan(self):
|
||||||
|
# 1. 查找 _config / _game / work 目录
|
||||||
|
for entry in os.listdir(self.root):
|
||||||
|
p = os.path.join(self.root, entry)
|
||||||
|
if not os.path.isdir(p):
|
||||||
|
continue
|
||||||
|
if entry.endswith("_config"):
|
||||||
|
self.config_dir = p
|
||||||
|
elif entry.endswith("_game"):
|
||||||
|
self.game_dir = p
|
||||||
|
elif entry == "work":
|
||||||
|
self.work_dir = p
|
||||||
|
|
||||||
|
# 2. 项目名
|
||||||
|
if self.config_dir:
|
||||||
|
base = os.path.basename(self.config_dir)
|
||||||
|
self.project_name = base.replace("_config", "")
|
||||||
|
|
||||||
|
# 3. CSV目录: work/cvs 优先,其次 config_dir 本身
|
||||||
|
if self.work_dir:
|
||||||
|
cvs = os.path.join(self.work_dir, "cvs")
|
||||||
|
if os.path.isdir(cvs) and os.listdir(cvs):
|
||||||
|
self.csv_dir = cvs
|
||||||
|
if not self.csv_dir:
|
||||||
|
self.csv_dir = self.config_dir
|
||||||
|
|
||||||
|
# 4. 搜索根目录:优先 Resources_moved,其次 Resources
|
||||||
|
if self.game_dir:
|
||||||
|
for root, dirs, _ in os.walk(self.game_dir):
|
||||||
|
for name in ["Resources_moved", "Resources"]:
|
||||||
|
if name in dirs:
|
||||||
|
self.resources_dir = os.path.join(root, name)
|
||||||
|
break
|
||||||
|
if self.resources_dir:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 5. 图标目录 (递归搜索 atlas_* 子目录,只保留包含图片的叶子目录)
|
||||||
|
if self.resources_dir:
|
||||||
|
for root, dirs, files in os.walk(self.resources_dir):
|
||||||
|
for d in dirs:
|
||||||
|
if d.startswith("atlas_"):
|
||||||
|
dp = os.path.join(root, d)
|
||||||
|
# 检查是否包含图片文件
|
||||||
|
has_img = any(f.lower().endswith((".png", ".jpg", ".jpeg")) for f in os.listdir(dp))
|
||||||
|
if has_img:
|
||||||
|
self.icon_dirs[d] = dp
|
||||||
|
|
||||||
|
# 6. Scripts 目录
|
||||||
|
if self.game_dir:
|
||||||
|
unity = os.path.join(self.game_dir, "unity")
|
||||||
|
assets = os.path.join(unity, "Assets") if os.path.isdir(unity) else None
|
||||||
|
if assets:
|
||||||
|
scripts = os.path.join(assets, "Scripts")
|
||||||
|
if os.path.isdir(scripts):
|
||||||
|
self.scripts_dir = scripts
|
||||||
|
|
||||||
|
# 7. GameTextConst.cs
|
||||||
|
if self.scripts_dir:
|
||||||
|
for root, _, files in os.walk(self.scripts_dir):
|
||||||
|
for f in files:
|
||||||
|
if f.lower().endswith(".cs") and "const" in f.lower() and ("text" in f.lower() or "game" in f.lower()):
|
||||||
|
self.game_text_const = os.path.join(root, f)
|
||||||
|
return
|
||||||
|
# 宽松匹配:任意 *Const*.cs
|
||||||
|
for root, _, files in os.walk(self.scripts_dir):
|
||||||
|
for f in files:
|
||||||
|
if "Const" in f and f.endswith(".cs"):
|
||||||
|
self.game_text_const = os.path.join(root, f)
|
||||||
|
return
|
||||||
|
|
||||||
|
def find_file(self, name):
|
||||||
|
"""在 csv_dir 或 config_dir 中查找文件(支持 .csv/.xlsx)"""
|
||||||
|
for d in [self.csv_dir, self.config_dir]:
|
||||||
|
if not d:
|
||||||
|
continue
|
||||||
|
for ext in [".csv", ".xlsx"]:
|
||||||
|
p = os.path.join(d, name + ext)
|
||||||
|
if os.path.isfile(p):
|
||||||
|
return p
|
||||||
|
return None
|
||||||
|
|
||||||
|
def summary(self):
|
||||||
|
"""打印扫描结果"""
|
||||||
|
lines = [
|
||||||
|
f" 项目名称: {self.project_name}",
|
||||||
|
f" 配置目录: {self.config_dir or '未找到'}",
|
||||||
|
f" 游戏目录: {self.game_dir or '未找到'}",
|
||||||
|
f" CSV目录: {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 '未找到'}",
|
||||||
|
]
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# 二、CSV 数据读取(自动编码 + 动态字段名解析)
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
def read_csv(filepath):
|
||||||
|
"""
|
||||||
|
读取CSV文件,自动检测编码 (utf-8-sig/gbk/gb2312/utf-8)。
|
||||||
|
返回 (headers, rows):
|
||||||
|
headers: 第3行英文字段名列表
|
||||||
|
rows: 第4行起的数据行列表 (list of list)
|
||||||
|
"""
|
||||||
|
for enc in ["utf-8-sig", "gbk", "gb2312", "utf-8"]:
|
||||||
|
try:
|
||||||
|
with open(filepath, "r", encoding=enc) as f:
|
||||||
|
all_rows = list(csv.reader(f))
|
||||||
|
if len(all_rows) < 4:
|
||||||
|
return all_rows[-1] if all_rows else [], []
|
||||||
|
return all_rows[2], all_rows[3:]
|
||||||
|
except (UnicodeDecodeError, UnicodeError):
|
||||||
|
continue
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
|
||||||
|
def field_map(headers):
|
||||||
|
"""将英文头行转为 {字段名: 列索引} 映射"""
|
||||||
|
return {h.strip(): i for i, h in enumerate(headers) if h.strip()}
|
||||||
|
|
||||||
|
|
||||||
|
def field_val(row, fm, name, default=""):
|
||||||
|
"""从数据行中按字段名取值"""
|
||||||
|
idx = fm.get(name)
|
||||||
|
if idx is not None and idx < len(row):
|
||||||
|
return row[idx].strip()
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# 三、图片资源查找
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
def find_image(icon_name, icon_dirs, icon_key):
|
||||||
|
"""根据 icon 名称在指定目录中查找图片文件"""
|
||||||
|
icon_dir = icon_dirs.get(icon_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)
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# 四、GameTextConst.cs 自动解析
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
def parse_game_text_const(cs_path, scripts_dir):
|
||||||
|
"""
|
||||||
|
自动解析 GameTextConst.cs 中所有 public const string 定义,
|
||||||
|
扫描项目中其他 .cs 文件确认哪些常量被引用,
|
||||||
|
返回去重后的文本列表。
|
||||||
|
"""
|
||||||
|
if not cs_path or not os.path.isfile(cs_path):
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 读取文件
|
||||||
|
content = None
|
||||||
|
for enc in ["utf-8-sig", "utf-8", "gbk", "gb2312"]:
|
||||||
|
try:
|
||||||
|
with open(cs_path, "r", encoding=enc) as f:
|
||||||
|
content = f.read()
|
||||||
|
break
|
||||||
|
except (UnicodeDecodeError, UnicodeError):
|
||||||
|
continue
|
||||||
|
if content is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 提取常量:public const string Name = "value";
|
||||||
|
pattern = r'public\s+const\s+string\s+(\w+)\s*=\s*"((?:[^"\\]|\\.)*)"\s*;'
|
||||||
|
all_consts = {}
|
||||||
|
for m in re.finditer(pattern, content):
|
||||||
|
name, value = m.group(1), m.group(2)
|
||||||
|
# 还原转义
|
||||||
|
value = value.replace("\\n", "\n").replace("\\t", "\t").replace('\\"', '"')
|
||||||
|
all_consts[name] = value
|
||||||
|
|
||||||
|
if not all_consts:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 扫描引用
|
||||||
|
const_names = set(all_consts.keys())
|
||||||
|
referenced = set()
|
||||||
|
cs_file = os.path.basename(cs_path)
|
||||||
|
if scripts_dir and os.path.isdir(scripts_dir):
|
||||||
|
for root, _, files in os.walk(scripts_dir):
|
||||||
|
for fname in files:
|
||||||
|
if not fname.endswith(".cs") or fname == cs_file:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
with open(os.path.join(root, fname), "r", encoding="utf-8", errors="ignore") as f:
|
||||||
|
src = f.read()
|
||||||
|
for cn in const_names:
|
||||||
|
if cn in src:
|
||||||
|
referenced.add(cn)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 去重文本值
|
||||||
|
seen = set()
|
||||||
|
result = []
|
||||||
|
for name, text in all_consts.items():
|
||||||
|
if name in referenced and text not in seen:
|
||||||
|
seen.add(text)
|
||||||
|
result.append(text)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# 五、文档格式化辅助
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
def _run(para, text, font="仿宋", size=14, bold=False, underline=None):
|
||||||
|
"""创建带格式的 run"""
|
||||||
|
r = para.add_run(text)
|
||||||
|
r.font.name = font
|
||||||
|
r._element.rPr.rFonts.set(qn("w:eastAsia"), font)
|
||||||
|
r.font.size = Pt(size)
|
||||||
|
r.font.bold = bold
|
||||||
|
if underline is not None:
|
||||||
|
r.font.underline = underline
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def add_title(doc, text):
|
||||||
|
"""标题:宋体 二号(22pt) 加粗 居中"""
|
||||||
|
p = doc.add_paragraph()
|
||||||
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
_run(p, text, font="宋体", size=22, bold=True, underline=False)
|
||||||
|
|
||||||
|
|
||||||
|
def add_section_heading(doc, cn_num, title):
|
||||||
|
"""小标题:仿宋 四号(14pt) 加粗 中文编号"""
|
||||||
|
p = doc.add_paragraph()
|
||||||
|
_run(p, f"{cn_num}、{title}", font="仿宋", size=14, bold=True)
|
||||||
|
|
||||||
|
|
||||||
|
def add_numbered_items(doc, items):
|
||||||
|
"""编号列表:仿宋 四号 不加粗"""
|
||||||
|
for i, text in enumerate(items, 1):
|
||||||
|
p = doc.add_paragraph()
|
||||||
|
_run(p, f"{i}.{text}", font="仿宋", size=14, bold=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _set_cell(cell, text, bold=False, size=14):
|
||||||
|
"""设置表格单元格文本"""
|
||||||
|
cell.text = ""
|
||||||
|
p = cell.paragraphs[0]
|
||||||
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
_run(p, str(text), font="仿宋", size=size, bold=bold)
|
||||||
|
|
||||||
|
|
||||||
|
def _insert_img(cell, img_path):
|
||||||
|
"""在单元格中插入图片(高度3cm 锁定纵横比)"""
|
||||||
|
p = cell.paragraphs[0]
|
||||||
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
p.add_run().add_picture(img_path, height=Cm(3))
|
||||||
|
|
||||||
|
|
||||||
|
def add_data_table(doc, headers, rows, icon_dirs=None, icon_key=None):
|
||||||
|
"""通用数据表格(支持图片列)"""
|
||||||
|
table = doc.add_table(rows=1, cols=len(headers), style="Table Grid")
|
||||||
|
table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||||
|
for i, h in enumerate(headers):
|
||||||
|
_set_cell(table.rows[0].cells[i], h, bold=True, size=10)
|
||||||
|
for rd in rows:
|
||||||
|
row = table.add_row()
|
||||||
|
_set_cell(row.cells[0], rd[0])
|
||||||
|
if len(rd) > 1:
|
||||||
|
icon_name = rd[1]
|
||||||
|
img = find_image(icon_name, icon_dirs, icon_key) if icon_name and icon_dirs else None
|
||||||
|
if img:
|
||||||
|
_insert_img(row.cells[1], img)
|
||||||
|
else:
|
||||||
|
_set_cell(row.cells[1], "", size=10)
|
||||||
|
if len(rd) > 2:
|
||||||
|
_set_cell(row.cells[2], rd[2], size=10)
|
||||||
|
|
||||||
|
|
||||||
|
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, h in enumerate(["姓", "名"]):
|
||||||
|
_set_cell(table.rows[0].cells[i], h, bold=True)
|
||||||
|
for surname, given in names:
|
||||||
|
row = table.add_row()
|
||||||
|
_set_cell(row.cells[0], surname)
|
||||||
|
_set_cell(row.cells[1], given)
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# 六、数据提取函数
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
def get_player_names(scanner):
|
||||||
|
"""获取玩家随机名称(优先 name.csv,否则使用默认列表)"""
|
||||||
|
fp = scanner.find_file("name")
|
||||||
|
if fp and fp.endswith(".csv"):
|
||||||
|
headers, rows = read_csv(fp)
|
||||||
|
fm = field_map(headers)
|
||||||
|
if "first" in fm and "last" in fm:
|
||||||
|
seen = set()
|
||||||
|
result = []
|
||||||
|
for r in rows:
|
||||||
|
first = field_val(r, fm, "first")
|
||||||
|
last = field_val(r, fm, "last")
|
||||||
|
if first and last:
|
||||||
|
key = (first, last)
|
||||||
|
if key not in seen:
|
||||||
|
seen.add(key)
|
||||||
|
result.append(key)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
# 默认名称(100对)
|
||||||
|
return [
|
||||||
|
("李", "子轩"), ("王", "梓涵"), ("张", "浩然"), ("刘", "欣怡"),
|
||||||
|
("陈", "宇轩"), ("杨", "晨曦"), ("赵", "俊杰"), ("黄", "雅静"),
|
||||||
|
("周", "天佑"), ("吴", "梦琪"), ("徐", "博文"), ("孙", "思彤"),
|
||||||
|
("胡", "睿智"), ("朱", "静怡"), ("高", "昊天"), ("林", "雨桐"),
|
||||||
|
("何", "泽宇"), ("郭", "婉清"), ("马", "俊熙"), ("罗", "雪柔"),
|
||||||
|
("梁", "子豪"), ("宋", "依琳"), ("郑", "宇航"), ("谢", "诗涵"),
|
||||||
|
("韩", "志强"), ("唐", "梦瑶"), ("冯", "文博"), ("于", "雅楠"),
|
||||||
|
("董", "天宇"), ("萧", "欣妍"), ("程", "子睿"), ("曹", "静雯"),
|
||||||
|
("袁", "俊豪"), ("邓", "雨欣"), ("许", "浩宇"), ("傅", "语嫣"),
|
||||||
|
("沈", "子涵"), ("曾", "思雨"), ("彭", "博涛"), ("吕", "雅婷"),
|
||||||
|
("苏", "天翔"), ("卢", "梦洁"), ("蒋", "文轩"), ("蔡", "雪梅"),
|
||||||
|
("贾", "志远"), ("丁", "依娜"), ("魏", "子安"), ("薛", "静雅"),
|
||||||
|
("叶", "俊峰"), ("阎", "雨婷"), ("余", "浩轩"), ("潘", "诗琪"),
|
||||||
|
("杜", "子恒"), ("戴", "思雅"), ("夏", "博超"), ("钟", "雅琪"),
|
||||||
|
("汪", "天瑞"), ("田", "梦菲"), ("任", "文昊"), ("姜", "雪莲"),
|
||||||
|
("范", "志鹏"), ("方", "依婷"), ("石", "子健"), ("姚", "静淑"),
|
||||||
|
("谭", "俊凯"), ("廖", "雨菲"), ("邹", "浩南"), ("熊", "诗瑶"),
|
||||||
|
("金", "子宁"), ("陆", "思琪"), ("郝", "博远"), ("孔", "雅静"),
|
||||||
|
("白", "天浩"), ("崔", "梦婷"), ("康", "文杰"), ("毛", "雪琪"),
|
||||||
|
("邱", "志伟"), ("秦", "依静"), ("江", "子文"), ("史", "静怡"),
|
||||||
|
("顾", "俊杰"), ("侯", "雨萱"), ("邵", "浩天"), ("孟", "诗涵"),
|
||||||
|
("龙", "子辰"), ("万", "思妍"), ("段", "博文"), ("雷", "雅欣"),
|
||||||
|
("钱", "天佑"), ("汤", "梦瑶"), ("尹", "文豪"), ("黎", "雪莹"),
|
||||||
|
("易", "志强"), ("常", "依琳"), ("武", "子轩"), ("乔", "静雯"),
|
||||||
|
("贺", "俊熙"), ("赖", "雨桐"), ("龚", "浩宇"), ("文", "诗琪"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_csv_list_data(scanner, csv_name, field_name, filter_dict=None):
|
||||||
|
"""通用:从CSV中按字段名提取去重文本列表"""
|
||||||
|
fp = scanner.find_file(csv_name)
|
||||||
|
if not fp or not fp.endswith(".csv"):
|
||||||
|
return []
|
||||||
|
headers, rows = read_csv(fp)
|
||||||
|
fm = field_map(headers)
|
||||||
|
seen = set()
|
||||||
|
result = []
|
||||||
|
for r in rows:
|
||||||
|
# 过滤条件
|
||||||
|
if filter_dict:
|
||||||
|
ok = True
|
||||||
|
for fk, fvs in filter_dict.items():
|
||||||
|
if field_val(r, fm, fk) not in fvs:
|
||||||
|
ok = False
|
||||||
|
break
|
||||||
|
if not ok:
|
||||||
|
continue
|
||||||
|
val = field_val(r, fm, field_name)
|
||||||
|
if val and val not in seen:
|
||||||
|
seen.add(val)
|
||||||
|
result.append(val)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_csv_table_data(scanner, csv_name, field_names, dedup_field=None):
|
||||||
|
"""通用:从CSV中提取多字段表格数据 [(f1, f2, f3, ...), ...]"""
|
||||||
|
fp = scanner.find_file(csv_name)
|
||||||
|
if not fp or not fp.endswith(".csv"):
|
||||||
|
return []
|
||||||
|
headers, rows = read_csv(fp)
|
||||||
|
fm = field_map(headers)
|
||||||
|
seen = set()
|
||||||
|
result = []
|
||||||
|
for r in rows:
|
||||||
|
vals = tuple(field_val(r, fm, fn) for fn in field_names)
|
||||||
|
if not vals[0]:
|
||||||
|
continue
|
||||||
|
if dedup_field:
|
||||||
|
key = vals[0]
|
||||||
|
if key in seen:
|
||||||
|
continue
|
||||||
|
seen.add(key)
|
||||||
|
result.append(vals)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# 七、文档构建主流程
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
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文件")
|
||||||
|
|
||||||
|
# 预加载数据
|
||||||
|
system_prompts = parse_game_text_const(scanner.game_text_const, scanner.scripts_dir)
|
||||||
|
player_names = get_player_names(scanner)
|
||||||
|
|
||||||
|
print(f" 系统提示: {len(system_prompts)}条")
|
||||||
|
print(f" 玩家名称: {len(player_names)}对")
|
||||||
|
|
||||||
|
print("\n[2/4] 创建文档...")
|
||||||
|
doc = Document()
|
||||||
|
style = doc.styles["Normal"]
|
||||||
|
style.font.name = "仿宋"
|
||||||
|
style.font.size = Pt(14)
|
||||||
|
style._element.rPr.rFonts.set(qn("w:eastAsia"), "仿宋")
|
||||||
|
|
||||||
|
doc_title = f"《{scanner.project_name}》文字脚本" if scanner.project_name else "游戏文字脚本"
|
||||||
|
add_title(doc, doc_title)
|
||||||
|
|
||||||
|
print("\n[3/4] 写入章节...")
|
||||||
|
icon_dirs = scanner.icon_dirs
|
||||||
|
sections_done = 0
|
||||||
|
|
||||||
|
# --- 一、系统提示 ---
|
||||||
|
add_section_heading(doc, CN_NUMBERS[0], "系统提示")
|
||||||
|
add_numbered_items(doc, system_prompts)
|
||||||
|
print(f" 一、系统提示: {len(system_prompts)}条")
|
||||||
|
sections_done += 1
|
||||||
|
|
||||||
|
# --- 二、任务文本 ---
|
||||||
|
quest_def = SECTION_DEFS["quest_texts"]
|
||||||
|
quest_texts = get_csv_list_data(scanner, quest_def["csv"], quest_def["field"], quest_def.get("filter"))
|
||||||
|
add_section_heading(doc, CN_NUMBERS[1], "任务文本")
|
||||||
|
add_numbered_items(doc, quest_texts)
|
||||||
|
print(f" 二、任务文本: {len(quest_texts)}条")
|
||||||
|
sections_done += 1
|
||||||
|
|
||||||
|
# --- 三、成就文本 ---
|
||||||
|
ach_def = SECTION_DEFS["achievements"]
|
||||||
|
ach_texts = get_csv_list_data(scanner, ach_def["csv"], ach_def["field"], ach_def.get("filter"))
|
||||||
|
add_section_heading(doc, CN_NUMBERS[2], "成就文本")
|
||||||
|
add_numbered_items(doc, ach_texts)
|
||||||
|
print(f" 三、成就文本: {len(ach_texts)}条")
|
||||||
|
sections_done += 1
|
||||||
|
|
||||||
|
# --- 四、玩家随机名称文本 ---
|
||||||
|
add_section_heading(doc, CN_NUMBERS[3], "玩家随机名称文本")
|
||||||
|
add_name_table(doc, player_names)
|
||||||
|
print(f" 四、玩家随机名称文本: {len(player_names)}对")
|
||||||
|
sections_done += 1
|
||||||
|
|
||||||
|
# --- 五、新手指引文本 ---
|
||||||
|
guide_def = SECTION_DEFS["guide_texts"]
|
||||||
|
guide_texts = get_csv_list_data(scanner, guide_def["csv"], guide_def["field"])
|
||||||
|
add_section_heading(doc, CN_NUMBERS[4], "新手指引文本")
|
||||||
|
add_numbered_items(doc, guide_texts)
|
||||||
|
print(f" 五、新手指引文本: {len(guide_texts)}条")
|
||||||
|
sections_done += 1
|
||||||
|
|
||||||
|
# --- 六、道具说明 ---
|
||||||
|
prop_def = SECTION_DEFS["props"]
|
||||||
|
prop_data = get_csv_table_data(scanner, prop_def["csv"], prop_def["fields"])
|
||||||
|
add_section_heading(doc, CN_NUMBERS[5], "道具说明")
|
||||||
|
add_data_table(doc, ["道具名称", "图标", "道具描述"], prop_data, icon_dirs, prop_def["icon_key"])
|
||||||
|
print(f" 六、道具说明: {len(prop_data)}条")
|
||||||
|
sections_done += 1
|
||||||
|
|
||||||
|
# --- 七、技能 ---
|
||||||
|
skill_def = SECTION_DEFS["skills"]
|
||||||
|
skill_data = get_csv_table_data(scanner, skill_def["csv"], skill_def["fields"])
|
||||||
|
add_section_heading(doc, CN_NUMBERS[6], "技能")
|
||||||
|
add_data_table(doc, ["技能名称", "技能图标", "技能描述"], skill_data, icon_dirs, skill_def["icon_key"])
|
||||||
|
print(f" 七、技能: {len(skill_data)}条")
|
||||||
|
sections_done += 1
|
||||||
|
|
||||||
|
# --- 八、灵石 ---
|
||||||
|
rune_def = SECTION_DEFS["runes"]
|
||||||
|
rune_data = get_csv_table_data(scanner, rune_def["csv"], rune_def["fields"])
|
||||||
|
add_section_heading(doc, CN_NUMBERS[7], "灵石")
|
||||||
|
add_data_table(doc, ["灵石名称", "灵石图标", "灵石介绍"], rune_data, icon_dirs, rune_def["icon_key"])
|
||||||
|
print(f" 八、灵石: {len(rune_data)}条")
|
||||||
|
sections_done += 1
|
||||||
|
|
||||||
|
# --- 九、天赋 ---
|
||||||
|
talent_def = SECTION_DEFS["talents"]
|
||||||
|
talent_data = get_csv_table_data(scanner, talent_def["csv"], talent_def["fields"], talent_def.get("dedup"))
|
||||||
|
add_section_heading(doc, CN_NUMBERS[8], "天赋")
|
||||||
|
add_data_table(doc, ["天赋名称", "天赋图标", "天赋介绍"], talent_data, icon_dirs, talent_def["icon_key"])
|
||||||
|
print(f" 九、天赋: {len(talent_data)}条")
|
||||||
|
sections_done += 1
|
||||||
|
|
||||||
|
# --- 十、装备 ---
|
||||||
|
equip_def = SECTION_DEFS["equips"]
|
||||||
|
equip_data = get_csv_table_data(scanner, equip_def["csv"], equip_def["fields"], equip_def.get("dedup"))
|
||||||
|
add_section_heading(doc, CN_NUMBERS[9], "装备")
|
||||||
|
add_data_table(doc, ["装备名称", "装备图标"], equip_data, icon_dirs, equip_def["icon_key"])
|
||||||
|
print(f" 十、装备: {len(equip_data)}条")
|
||||||
|
sections_done += 1
|
||||||
|
|
||||||
|
# --- 十一、关卡名称 ---
|
||||||
|
level_def = SECTION_DEFS["level_names"]
|
||||||
|
level_names = get_csv_list_data(scanner, level_def["csv"], level_def["field"])
|
||||||
|
add_section_heading(doc, CN_NUMBERS[10], "关卡名称")
|
||||||
|
add_numbered_items(doc, level_names)
|
||||||
|
print(f" 十一、关卡名称: {len(level_names)}条")
|
||||||
|
sections_done += 1
|
||||||
|
|
||||||
|
print(f"\n[4/4] 保存文档...")
|
||||||
|
doc.save(output_path)
|
||||||
|
print(f" 文档已生成: {output_path}")
|
||||||
|
print(f" 共 {sections_done} 个章节")
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# 主入口
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="通用游戏文字脚本文档生成工具")
|
||||||
|
parser.add_argument("project_path", nargs="?", help="项目根目录路径 (默认: 当前目录)")
|
||||||
|
parser.add_argument("--output", "-o", help="输出文档路径 (默认: 自动生成)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
project_path = args.project_path or os.getcwd()
|
||||||
|
if not os.path.isdir(project_path):
|
||||||
|
print(f"错误: 目录不存在 - {project_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"{'='*50}")
|
||||||
|
print(f" 通用游戏文字脚本生成工具")
|
||||||
|
print(f"{'='*50}")
|
||||||
|
print(f"\n扫描项目: {project_path}")
|
||||||
|
|
||||||
|
scanner = ProjectScanner(project_path)
|
||||||
|
print(f"\n{scanner.summary()}")
|
||||||
|
|
||||||
|
if not scanner.csv_dir and not scanner.config_dir:
|
||||||
|
print("\n错误: 未找到配置目录 (*_config) 或CSV目录")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if args.output:
|
||||||
|
output_path = args.output
|
||||||
|
else:
|
||||||
|
work_dir = scanner.work_dir or os.path.join(project_path, "work")
|
||||||
|
os.makedirs(work_dir, exist_ok=True)
|
||||||
|
title = scanner.project_name or "游戏"
|
||||||
|
output_path = os.path.join(work_dir, f"《{title}》游戏文字脚本.docx")
|
||||||
|
|
||||||
|
build_document(scanner, output_path)
|
||||||
|
|
||||||
|
print(f"\n{'='*50}")
|
||||||
|
print(f" 全部完成!")
|
||||||
|
print(f"{'='*50}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
104
wenzijiaoben/py/xlsx_to_csv.py
Normal file
104
wenzijiaoben/py/xlsx_to_csv.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
"""
|
||||||
|
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)
|
||||||
1
wenzijiaoben/使用请看.txt
Normal file
1
wenzijiaoben/使用请看.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
将PY放入项目文件中,运行universal_tool.py他会自动读取设定好的表格对应关系进行自动生成
|
||||||
Reference in New Issue
Block a user