NBA2kOL3 Table Editor
术语说明
| 用户说法 | 实际含义 |
|---|---|
| 本地化sheet / 本地化Sheet | Excel 中名为 Local 的 sheet |
当用户说"添加本地化sheet"、"本地化sheet"等,均指在 Excel 文件中创建或操作名为 Local 的 sheet。
🚨 CRITICAL: MANDATORY EXECUTION PROTOCOL
BEFORE STARTING ANY TASK, YOU MUST:
Read the relevant documentation file FIRST
- For MacroString tasks: Read
references/macrostring.mdIMMEDIATELY - When checking / adding
<indexer>inconvert_list_server.xml, or verifying server primary key config: Readreferences/indexer-type-check.mdIMMEDIATELY - For other tasks: Read the corresponding references file
- DO NOT skip this step - the documentation contains the complete workflow
- For MacroString tasks: Read
Create a complete TodoWrite checklist based on the documentation
- Include EVERY step from the documentation
- Do NOT improvise or skip steps based on assumptions
- Mark each step as you complete it
Execute ALL steps in the documentation
- Complete each step in order
- Do NOT declare the task complete until ALL steps are done
- Verify each step before moving to the next
FAILURE TO FOLLOW THIS PROTOCOL WILL RESULT IN INCOMPLETE WORK.
写引擎优先级(Write Engine Priority)
对 Excel 文件执行写操作时,按以下优先级选择方案:
| 优先级 | 方案 | 触发条件 |
|---|---|---|
| 🥇 优先 | table_tool CLI / XlsxWriter(minimax-xlsx) |
目标操作已在 CLI 或 XlsxWriter 中支持 |
| 🥈 次选 | 直接调用 XlsxWriter(minimax-xlsx XML 操作) |
CLI 不支持,但 XlsxWriter 方法可覆盖 |
| 🥉 兜底 | xlwings(需 Excel.exe 进程) |
以上两种均不支持时 |
判断逻辑:
该操作是否有对应的 table_tool CLI 命令?
├── 是 → 用 table_tool CLI
└── 否 → XlsxWriter 能否完成(add_sheet / update_cell / delete_rows / write_rows 等)?
├── 是 → 直接用 XlsxWriter
└── 否 → 用 xlwings(并在任务结束时报告"未支持场景")
目前 XlsxWriter 支持的操作(勿再用 xlwings):
XlsxWriter位于本仓库table_tool/table_toolkit/core/xlsx_writer.py,通过from table_tool.table_toolkit.core.xlsx_writer import XlsxWriter导入。
- 新建 sheet:
writer.add_sheet(name) - 写入行数据:
writer.insert_row_content/writer.write_rows(仅追加空位;在已有数据中间插入必须先shift_rows(at=row_num, delta=1)推移后续行,否则产生重复行号导致数据丢失) - 更新单个单元格:
writer.update_cell(sheet_name, row, col_letter, cell_type, value)(col_letter必须是U这类列字母,不是数字列号;字符串通常用inlineStr) - 删除行并重排行号:
writer.delete_rows - 追加 sharedStrings:
writer.append_shared_strings - 查找 sharedStrings 索引:
writer.find_shared_string_index - 获取 sheet 列表 / 最后数据行:
writer.get_sheet_names/writer.get_last_data_row
⚠️
get_last_data_row返回的是最后一个含实际值(<v>)的行号,排除了 Excel 保存时可能产生的样式空行(有<row>标签但无<v>)。追加行后应验证新行行号是否紧邻已有数据(见下方"验证写入正确性")。
⚠️ 不要在上述操作上继续使用 xlwings — 它们都已在 XlsxWriter 中实现且无进程残留风险。
XlsxWriter 标准调用模板(必须 unpack → 操作 → pack):
import sys
sys.path.insert(0, 'table_tool')
from table_toolkit.core.xlsx_writer import XlsxWriter
writer = XlsxWriter('excel/YourFile.xlsx')
writer.unpack() # ← 必须先 unpack,否则操作会 FileNotFoundError
# 执行一个或多个操作
writer.delete_rows('Local', rows_to_delete)
writer.update_cell('Main', 5, 'B', 'inlineStr', 'new_value')
writer.update_cell('Main', 6, 'C', 'n', 123)
# writer.add_sheet('NewSheet') ...
writer.pack() # ← 就地覆盖原文件(不传参数);传路径则写到指定位置
⚠️ LESSON LEARNED(2026-05-16): 直接调用
writer.delete_rows(...)而不先unpack()会立即抛出FileNotFoundError,因为 XlsxWriter 操作的是解包后的临时目录,未解包时目录为空。⚠️ LESSON LEARNED(2026-06-11): 用
update_cell()在工作表最右侧新增字段 / 新列时,单元格本身可能已写入成功,但依赖 OOXML<dimension>的工具(如 ugit 外部预览、部分只读解析器)仍可能看不到新列。原因是当前pack()的 dimension 修复更偏向修正末行,未必会把末列从T扩到U。因此,新增最右列后必须额外验证<dimension ref="...">是否覆盖到新列;不要只看openpyxl普通模式,因为它可能重算维度、掩盖底层 XML 仍然过窄的问题。
读操作优先级(Read Query Priority)
对 Excel 文件执行只读查询 / 条件筛选 / 聚合统计时,按以下优先级:
| 优先级 | 方案 | 适用场景 |
|---|---|---|
| 🥇 优先 | pandas.read_excel() |
条件筛选、列查找、多列组合查询、聚合统计 |
| 🥈 次选 | openpyxl(正常模式) |
只需读表头/结构、或需要精确访问单元格格式 |
pandas 典型用法:
import pandas as pd
# header=0 以 Row1 为列名,skiprows=[1,2,3] 跳过 Row2-4(类型/注释/导出目标)
df = pd.read_excel('excel/Foo.xlsx', sheet_name='Main', header=0, skiprows=[1, 2, 3])
# 向量化筛选,秒级完成
type_cols = [c for c in df.columns if 'EventAttribute' in c and '.type' in c]
mask = df[type_cols].isin(['TARGET_ENUM_VALUE']).any(axis=1)
print(df.loc[mask, 'Id'].tolist())
openpyxl 读取注意事项:
- 使用正常模式(不加
read_only=True)处理大表随机访问,避免 XML 重复解析导致超时 read_only=True仅适合顺序iter_rows()遍历,不适合ws.cell(r, c)随机访问
⚠️
pandas.to_excel()禁止用于写操作(破坏公式/格式),但read_excel()读取无此限制,放心使用。
🎯 INTERACTION PROTOCOL FOR TEXT MIGRATION
When migrating localization text to Local sheet, ALWAYS follow this CLI-first workflow:
Check proto field type first
grep "FieldName" converter/resource/desc/client/res_client_struct.proto- If
string→ no proto change needed - If
uint32/int32→ must update proto after migration (note this)
- If
Preview with dry-run using absolute path
python table_tool/table_tool.py migrate-text G:/Git/.../excel/YourModule.xlsx --field FieldName --dry-run- Always use absolute paths on Windows to avoid Git Bash encoding issues with Chinese filenames
展示 Loc key 样例并请用户确认
- 根据文件名、Sheet 名和字段名,计算出将要生成的 Loc key 格式,展示给用户确认:
将生成的 Loc key 格式(ENHANCED): Loc_{ModuleName}_{SheetName}_{ColumnHeader}_{RecordId} 示例: Loc_TacticsSettings_GameTacticsOption_TacticsOptionsTextual_1 Loc_TacticsSettings_GameTacticsOption_TacticsOptionsTextual_2 ... 共 N 条记录 - ModuleName 规则:取文件名主干,去掉非 ASCII 前缀(如"S赛事模式_"),只保留纯英文部分
- SheetName 规则:直接使用
--sheet参数指定的 Sheet 名(默认为Main) - ColumnHeader 规则:直接使用
--field参数指定的字段列名 - 使用
AskUserQuestion请用户确认 key 格式是否合适,再执行迁移 - 若用户希望调整(如使用
--basic格式或自定义前缀),按需修改命令
- 根据文件名、Sheet 名和字段名,计算出将要生成的 Loc key 格式,展示给用户确认:
Run the actual migration
python table_tool/table_tool.py migrate-text G:/Git/.../excel/YourModule.xlsx --field FieldName- The CLI handles Phase 0–5 automatically (proto check, ID collection, Local sheet create/update, source update, ID2StringMapTable cleanup)
- After writing to Local sheet, the CLI automatically validates key uniqueness
- If duplicates are detected → CLI raises
ValueErrorwith duplicate key details → migration aborted, file NOT saved - If all keys are unique → prints "Key uniqueness check passed: N unique key(s) in Local sheet"
- If duplicates are detected → CLI raises
Verify XML config was updated (CLI auto-does this as Phase 6; check if it worked)
grep "YourModule.xlsx|Local" converter/convert_list.xml- If found → done
- If missing → add
<scheme name="DataSource" ...>../../excel/YourModule.xlsx|Local|5,1</scheme>toClient_Key2StringMapTablemanually
验证写入正确性(必须执行)
- 验证目标列值已正确更新为 Loc keys(检查数据起始行的前几个值)
- 验证相邻列未被污染(检查是否有不该出现的 Loc_ 前缀)
- 验证 Local sheet 新增行紧邻已有数据(防止样式空行导致间隙)
- 验证
<dimension>覆盖新数据范围(防止 read_only 工具看不到新行)
python -c " import openpyxl wb = openpyxl.load_workbook('excel/YourModule.xlsx', data_only=True) sheet = wb['Local'] # 打印所有非空行的 (Id, 行号),确认末尾新增行紧邻最后一条旧数据 rows_with_data = [(sheet.cell(r, 1).value, r) for r in range(5, sheet.max_row+1) if sheet.cell(r, 1).value is not None] print('Local 数据行:', rows_with_data[-5:]) # 查看最后5行 # 如果出现 Id=None 的间隔行,说明有样式空行问题 "python -c " import openpyxl # 用 read_only=True 验证 dimension 已正确覆盖新数据(最严格的模式) wb = openpyxl.load_workbook('excel/YourModule.xlsx', read_only=True, data_only=True) sheet = wb['Local'] rows = [r[0].value for r in sheet.iter_rows(min_row=5) if r[0].value is not None] print(f'read_only 模式下 Local sheet 记录数: {len(rows)}') wb.close() # 若此数 < 预期总记录数,说明 dimension 未更新(用当前版本 XlsxWriter 不应出现) "python -c " import openpyxl wb = openpyxl.load_workbook('excel/YourModule.xlsx', data_only=True) sheet = wb['YourSheet'] # Check target column and adjacent column for row in range(5, 10): target = sheet.cell(row, TARGET_COL).value adjacent = sheet.cell(row, TARGET_COL + 1).value print(f'Row {row}: target={target}, adjacent={adjacent}') if isinstance(adjacent, str) and adjacent.startswith('Loc_'): print(f'ERROR: Row {row} adjacent column polluted!') "Update proto if needed (only when proto type was NOT string)
- Edit
converter/resource/desc/client/res_client_struct.proto - Change
uint32 FieldName→string FieldName
- Edit
If the CLI command hangs, exits silently with no output, or produces unexpected results: See the Troubleshooting section. Silent exit (no output, exit code 0) is NOT a success indicator — always verify actual data changes. Fall back to manual scripting only as a last resort.
NEVER write manual Python scripts for migration unless the CLI command fails.
🎯 INTERACTION PROTOCOL FOR FORMAT CONVERSION
When performing format conversion, ALWAYS follow this interactive workflow:
First: Preview with dry-run
- Execute
python table_tool/table_tool.py convert ... --dry-runFIRST - Capture the output to detect enum field suggestions
- Execute
If enum fields detected: Ask the user
- Parse output for lines like "检测到 X 个枚举字段建议添加@符号"
- Extract the specific field rename suggestions (e.g.,
BadgeType -> BadgeType@BadgeType) - Use
AskUserQuestiontool with clear options:- Question: "检测到以下枚举字段可以添加 @ 符号以明确类型,是否自动重命名?\n\n重命名建议:\n{list of suggestions}"
- Option 1: "Yes, auto-rename enum fields (推荐)" → Add
--auto-rename-enumflag - Option 2: "No, keep current field names" → Proceed without flag
Execute with user's choice
- Run the actual conversion command with or without
--auto-rename-enumbased on user response
- Run the actual conversion command with or without
NEVER skip the user question when enum fields are detected.
NBA2kOL3 Table Editor
Quick Start
Add a new configuration table:
# 1. Create Excel file in excel/ directory
# 2. Follow 4-row header format (data starts at row 5)
# 3. Generate proto definitions
python table_tool/table_tool.py generate excel/NewTable.xlsx --target server
# 4. Update XML configs if needed
# 5. Test conversion
cd converter
convert.bat # Windows
Migrate localization text to Local sheet:
# Migrate text from ID2StringMapTable to module's Local sheet
python table_tool/table_tool.py migrate-text excel/YourModule.xlsx --field DescriptionId
# Preview changes without modifying files
python table_tool/table_tool.py migrate-text excel/YourModule.xlsx --field DescriptionId --dry-run
# Use basic naming format (Loc_Module_Id instead of Loc_Module_Field_Id)
python table_tool/table_tool.py migrate-text excel/YourModule.xlsx --field DescriptionId --basic
# Export to both client and server
python table_tool/table_tool.py migrate-text excel/YourModule.xlsx --field DescriptionId --export-target both
Command Reference
migrate-text - Text Migration Command
Migrate module-specific localization text from global ID2StringMapTable to the module's own Local sheet with proper Loc key naming.
Basic Syntax:
python table_tool/table_tool.py migrate-text <excel_file> --field <field_name> [options]
Required Arguments:
excel_file- Path to Excel file (e.g.,excel/YourModule.xlsx)--field- Field column name to migrate (e.g.,DescriptionId)
Options:
--sheet- Source sheet name (default:Main)--basic- Use basic naming formatLoc_Module_Sheet_Idinstead ofLoc_Module_Sheet_Field_Id(default: enhanced)--export-target- Export target for Local sheet:client(default),server, orboth--dry-run- Preview changes without modifying files
Examples:
# Standard migration (enhanced naming format, client-only export)
python table_tool/table_tool.py migrate-text excel/S赛事模式_TacticsSettings.xlsx --field DefaultTacticsTextual
# Preview what will change
python table_tool/table_tool.py migrate-text excel/S赛事模式_TacticsSettings.xlsx --field DefaultTacticsTextual --dry-run
# Basic naming format for simpler Loc keys
python table_tool/table_tool.py migrate-text excel/PlayerAbility.xlsx --field AbilityDescription --basic
# Export text to both client and server
python table_tool/table_tool.py migrate-text excel/SharedConfig.xlsx --field SystemMessage --export-target both
Naming Formats:
- Enhanced (default):
Loc_Module_Sheet_Field_Id- Provides full namespace, prevents cross-sheet collisions- Example:
Loc_TacticsSettings_Main_DefaultTacticsTextual_1
- Example:
- Basic:
Loc_Module_Sheet_Id- Simpler when field context is obvious- Example:
Loc_TacticsSettings_Main_1
- Example:
Workflow:
- Phase 0: Check proto field type (warns if not 'string')
- Phase 1: Collect referenced text IDs from source sheet
- Phase 2: Extract matching text from ID2StringMapTable
- Phase 3: Create or update Local sheet with new Loc keys
- Phase 4: Update source field to use Loc keys
- Phase 5: Clean up migrated entries from ID2StringMapTable
Table Format
NBA2kOL3 Standard (4-Row Header)
| Row | Purpose |
|---|---|
| 1 | Field names (match proto field names) |
| 2 | Field types (uint32, string, etc.) |
| 3 | Comments |
| 4 | Export targets |
| 5+ | Data |
Export Targets (Row 4)
PrimaryKey- Primary key, exported to client + serverClient- Client onlyServer- Server only- Empty - Both client + server
Skip- Not exported
Capabilities
Adding a New Sheet to Existing Excel File
Add a new configuration sheet to an existing Excel file with complete proto and XML configuration setup.
Documentation: references/add-new-sheet.md
Format Conversion
Convert Legacy (2-row header) Excel files to NBA2kOL3 standard (4-row header) format.
Documentation: references/format-conversion.md
Proto Generation
Generate Protocol Buffer message definitions based on Excel table structure.
Documentation: references/proto-generation.md
Visualization
Generate DOT graphs to visualize table structure and relationships between tables.
Documentation: references/visualization.md
Enum Management
Manage and synchronize enum definitions across game configuration tables.
Documentation: references/enum-management.md
Table Validation
Validate Excel table structure and cross-table field references.
Documentation: references/validation.md
Adding MacroString
Add new macrostring text configurations for localized game UI strings.
⚠️ 流程已变更:MacroString 现在通过独立技能
nba2k-macrostring处理,只需修改MacroTable.conf,不再操作 Excel 或 proto 文件。遇到 MacroString 需求时,请直接使用/nba2k-macrostring技能。
Text Migration
Migrate module-specific texts from global ID2StringMapTable to module's Local sheet with proper localization key naming.
Always follow the CLI-first workflow in the INTERACTION PROTOCOL section above before reading this reference.
Documentation: references/text-migration.md
Indexer Type & Primary Key Check
验证 convert_list*.xml 中 <indexer> 配置的类型与 proto 一致,并检查 server 端非 Id 主键是否已在 proto message 头部声明 option (primary_key_field)。
触发时机:
- 新增或修改
convert_list_server.xml中的<indexer>配置时 - 检查 server 端表的主键配置是否符合要求时
- server 转表报
Type mismatch/Invalid key type错误时
Documentation: references/indexer-type-check.md
Local Sheet Cleanup
扫描 Main sheet 中实际引用的 Loc_ keys,删除 Local sheet 中未被引用的孤立记录,保持 Local sheet 与 Main sheet 的数据一致性。
操作步骤:
- 用
openpyxl读取 Main sheet,收集所有Loc_开头的值 - 用
openpyxl读取 Local sheet,记录所有 key → 行号的映射 - 计算差集,找出未被引用的行号列表
- 用
XlsxWriter.delete_rows()删除(unpack → delete_rows → pack) - 用
openpyxl验证:Local 行数 = Main 引用数,无缺失无多余
Project Structure
Table/
├── excel/ # Source Excel files
├── converter/
│ ├── convert.bat # Client conversion (Windows)
│ ├── convert.sh # Server conversion (Linux)
│ ├── convert_list.xml # Client config
│ ├── convert_list_server.xml # Server config
│ └── resource/
│ ├── desc/ # Proto definitions
│ │ ├── client/
│ │ └── server/
│ └── bin/ # Binary output (generated)
└── doc/
└── table_tool.py # Table Toolkit CLI
Adding a New Table
- Create Excel in
excel/following 4-row format - Run proto generation
- Update
convert_list.xmland/orconvert_list_server.xml - Add proto message to
resource/desc/client/res_client_struct.protoorresource/desc/server/res_server.proto - Test conversion with
convert.bat
Type Inference
Field types determined by:
- Excel Row 2 (explicit type, highest priority)
- Proto definition
- Field name pattern (e.g.,
Ranking*→ uint32) - Default: string
Supported types: uint32, int32, uint64, int64, float, double, bool, string
For enum types: use FieldName@EnumType syntax in Row 1, specify enum in Row 2.
Important Notes
- 写操作优先使用 table_tool / XlsxWriter(无 Excel 进程),参见上方"写引擎优先级"
- xlwings 仅在 XlsxWriter 无法覆盖时使用(兜底),使用后需报告"未支持场景"(见下方)
- Generated files are in .gitignore
- Excel temp files (
~$*.xlsx) should not be committed - Legacy format uses
DataSource="...|3,1", NBA2kOL3 format usesDataSource="...|5,1" - The
visualizecommand preserves existing table structures and reference edges by default
xlwings 兜底使用规范(仅当 XlsxWriter 不支持时)
本节仅适用于 XlsxWriter 尚未支持的场景(如复杂公式保留、条件格式、数据验证等)。
对于 add_sheet / delete_rows / write_rows / update_cell 等,请直接用 XlsxWriter,不要走这里。⚠️ 以下所有注意事项(Excel 进程检查、写入验证、1D list 方向)均只在使用 xlwings 时才需要关注。
使用 XlsxWriter / table_tool CLI 时完全不需要,因为它们是纯 Python XML 操作,不启动任何 Excel.exe 进程。
① 操作前:确认用户已关闭 Excel(xlwings 专属)
LESSON LEARNED(2026-03-25): 用户忘记关闭 Excel 时,wb.save() 不报错但数据写入无效。xlwings 在文件被 Excel 占用时以共享模式打开,save() 写入的是临时副本,不覆盖原文件。
执行 xlwings 写操作前必须检查:
tasklist | findstr /i EXCEL # 检查是否有 Excel 进程
dir excel | findstr "~$" # 检查是否有文件锁
如果发现 Excel 进程,先提醒用户关闭 Excel,再执行操作。不要直接 taskkill(会丢失用户未保存的工作)。
② 验证写入:必须用全新的 app 实例(xlwings 专属)
LESSON LEARNED(2026-03-25): 同一个 xlwings app 实例重新打开同一文件,读到的是内存缓存,不是磁盘数据,会误判"写入失败"。
# ❌ 错误:同一 app 重新 open,读到的是缓存
wb2 = app.books.open('file.xlsx')
print(wb2.sheets['Sheet1'].range('A1').value) # 可能还是旧值!
# ✅ 正确方式一:退出 app 后重新创建
app.quit()
app2 = xw.App(visible=False)
wb2 = app2.books.open('file.xlsx')
print(wb2.sheets['Sheet1'].range('A1').value)
app2.quit()
# ✅ 正确方式二:用 openpyxl 验证(直接读磁盘,无缓存)
import openpyxl
wb_check = openpyxl.load_workbook('file.xlsx', data_only=True)
print(wb_check['Sheet1']['A1'].value)
兜底手段:用 os.path.getmtime() 确认文件 mtime 变化:
import os
mtime_before = os.path.getmtime('excel/YourFile.xlsx')
# ... 执行写入 ...
print('文件已修改:', os.path.getmtime('excel/YourFile.xlsx') != mtime_before)
③ 单列批量写入:必须用 transpose=True(xlwings 专属)
⚠️ 已发生事故(2026-03-11): sheet.range('B5:B16').value = list 写入 1D list 导致值横向溢出污染整行,需要 git history 恢复。xlwings 1D list 默认横向填充,即使目标是列范围。
# ❌ 错误(1D list 横向溢出)
sheet.range('B5:B16').value = ['Loc_1', 'Loc_2', ..., 'Loc_12']
# 结果: B5='Loc_1', C5='Loc_2', D5='Loc_3', ... (全行污染!)
# ✅ 正确(transpose=True 纵向写入)
sheet.range('B5').options(transpose=True).value = ['Loc_1', 'Loc_2', ..., 'Loc_12']
# 结果: B5='Loc_1', B6='Loc_2', B7='Loc_3', ...
写入后验证相邻列未被污染:
for i in range(len(new_values)):
adj = sheet.range(start_row + i, target_col + 1).value
if isinstance(adj, str) and adj.startswith('Loc_'):
raise ValueError(f'横向溢出!Row {start_row + i} 相邻列被污染: {adj}')
④ 代码模板(xlwings 兜底时使用)
Always use context managers:
import xlwings as xw
with xw.App(visible=False) as app:
app.display_alerts = False
app.screen_updating = False
wb = app.books.open('excel/YourFile.xlsx')
sheet = wb.sheets['Sheet1']
sheet.range('A1').value = 'test'
wb.save()
# Cleanup automatic on exit
If inline scripts are necessary:
app = None
try:
app = xw.App(visible=False)
app.display_alerts = False
# ... code ...
finally:
if app:
app.quit()
Check for residual instances:
import xlwings as xw
print(f"Active Excel instances: {len(xw.apps)}")
CRITICAL: 临时文件管理规范
任务执行过程中有时需要写中间数据文件(如 JSON、txt)。这类文件如果清理不干净会遗留在仓库里,造成困惑。
两条强制规则:
1. 统一命名 + 固定写入项目根目录(绝对路径)
写临时文件时,必须用绝对路径,写到项目根目录,文件名统一加 _claude_temp_ 前缀:
import os
# 先确定项目根目录(converter/ 的上级)
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
# 或者直接硬编码(更可靠)
project_root = 'G:/Git/table_side'
# ✅ 正确:绝对路径 + 前缀
with open(f'{project_root}/_claude_temp_row_data.json', 'w', encoding='utf-8') as f:
json.dump(data, f)
# ❌ 错误:相对路径,实际落点依赖 working directory
with open('temp_row_data.json', 'w') as f: # 落在哪取决于 cwd,清理时容易漏
json.dump(data, f)
为什么这样做:shell 的 working directory 在任务过程中可能因 cd 而改变(例如 cd converter 后再写文件)。使用绝对路径可以保证写入和清理时路径完全一致,不会因为 cwd 变化而找不到文件。
2. 清理后必须验证文件确实不存在
清理完毕后,必须执行一次文件存在性检查,不能只跑 rm 就当清理完成:
# PowerShell 清理 + 验证(推荐)
$tmpFiles = Get-ChildItem "G:\Git\table_side" -Filter "_claude_temp_*" -File
$tmpFiles | Remove-Item -Force
$remaining = Get-ChildItem "G:\Git\table_side" -Filter "_claude_temp_*" -File
if ($remaining) {
Write-Host "WARNING: 以下临时文件未能清理: $($remaining.FullName)"
} else {
Write-Host "临时文件清理完毕"
}
# Bash 版本
rm -f /g/Git/table_side/_claude_temp_*
remaining=$(ls /g/Git/table_side/_claude_temp_* 2>/dev/null)
if [ -n "$remaining" ]; then echo "WARNING: 未清理: $remaining"; else echo "临时文件清理完毕"; fi
在任务收尾的 TodoWrite checklist 中,必须包含这一条,并且在执行后确认输出为"临时文件清理完毕"才能标记完成。
CRITICAL: 格式转换后必须验证第一行数据未落入注释行
LESSON LEARNED(2026-04-28): table_tool 将 OLDEST 格式(1行表头)误判为 LEGACY(2行表头)时,第一条数据会被放入 Row3(注释行位置),导致该行被 xresloader 静默跳过,转表不报错但数据丢失。
触发条件:OLDEST 格式(原始1行表头),且 Row2(第一条数据行)含有字符串值(如枚举值 KEYBOARD_BUTTON_NONE、中文说明 未设置 等),工具误判该行为注释,从而将其保留在 Row3。
每次执行 format conversion 后,必须执行此检查:
import openpyxl
wb = openpyxl.load_workbook('excel/YourFile.xlsx', data_only=True)
ws = wb['YourSheet']
row3 = [ws.cell(3, c).value for c in range(1, ws.max_column + 1)]
# ✅ 正常:Row3 全为 None(空注释行)
# ❌ 异常:Row3 第一列是数字(如 1),说明 ID=1 数据行错误落入注释行!
print('Row3:', row3)
发现异常时的修复方法见 references/format-conversion.md 的 Step 5: Post-Conversion Check — OLDEST 格式误判 章节。
CRITICAL: 在最右侧新增字段后,必须验证 <dimension> 已扩到新列
LESSON LEARNED(2026-06-11): 给 Excel 最右侧新增字段时,update_cell() 可能已经把 U1/U2/U3/U4 等单元格写进 XML,但 worksheet 的 <dimension ref="A1:T39"> 仍停留在旧范围。此时:
- Excel /
openpyxl普通模式可能看起来一切正常 - ugit 外部预览、部分只读解析器、依赖
<dimension>的工具会看不到新列
触发场景:
- 在现有最后一列右边新增字段(如
T后新增U) - 用
XlsxWriter.update_cell()逐个写入表头,但没有再做额外结构校验
新增字段后必须执行这组校验:
- 校验新列表头 4 行是否正确(字段名 / 类型 / 注释 / 导出目标)
- 校验左侧相邻旧列未被污染
- 校验 worksheet XML 的
<dimension ref="...">已覆盖到新列字母 - 若
<dimension>仍是旧列范围,需显式修复后再结束任务
推荐校验命令(权威,以压缩包内 XML 为准;按 sheet 名定位,不要硬编码 sheet1.xml):
python -c "
import zipfile, xml.etree.ElementTree as ET
sheet_name = 'Main'
ns_main = {'a': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
ns_rel = {'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}
ns_pkg = {'p': 'http://schemas.openxmlformats.org/package/2006/relationships'}
with zipfile.ZipFile('excel/YourFile.xlsx', 'r') as z:
workbook = ET.parse(z.open('xl/workbook.xml')).getroot()
rels = ET.parse(z.open('xl/_rels/workbook.xml.rels')).getroot()
rid = None
for sheet in workbook.findall('a:sheets/a:sheet', ns_main):
if sheet.get('name') == sheet_name:
rid = sheet.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id')
break
target = None
for rel in rels.findall('p:Relationship', ns_pkg):
if rel.get('Id') == rid:
target = rel.get('Target')
break
with z.open('xl/' + target.lstrip('/')) as f:
root = ET.parse(f).getroot()
dim = root.find('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}dimension')
print(sheet_name, 'dimension ref:', dim.get('ref'))
"
判定标准:
- ✅ 预期新增到
U列时,应看到A1:U... - ❌ 若仍是
A1:T...,说明外部预览工具仍可能看不到新字段
注意:
- 不要只用
openpyxl.load_workbook(..., data_only=True)的普通模式做验证;它可能重算维度,导致你误以为底层 XML 已修好 - 这个检查对“新增行”与“新增列”都重要,但新增最右列时最容易漏掉
故障排查 (Troubleshooting)
问题 1: 命令行无输出静默退出 / 挂起
两种失败模式(需区分诊断):
| 模式 | 症状 | 含义 |
|---|---|---|
| 静默退出 | 命令立即退出,无任何输出,exit code 0 | CLI 内部提前 return,未实际执行迁移 |
| 挂起 | 命令长时间无响应,进程不退出 | Excel COM 接口阻塞 |
⚠️ 静默退出 ≠ 成功。 应通过验证实际数据来确认迁移是否生效,而不是只看命令是否正常退出。
通用诊断步骤:
# 0. 首先检查:是否缺少 --sheet 参数?
# CLI 默认操作 Main sheet;如果目标字段在非 Main sheet,CLI 可能静默退出(无报错)
# 解决方法: 明确指定 --sheet 参数
# ❌ 可能静默退出(找不到非 Main sheet 的字段)
python table_tool/table_tool.py migrate-text excel/File.xlsx --field ThirdTabTextual
# ✅ 明确指定 sheet
python table_tool/table_tool.py migrate-text excel/File.xlsx --sheet GameTacticsOption --field ThirdTabTextual
# 1. 检查是否有残留 Excel 进程(两种模式的共同原因)
python -c "import xlwings as xw; print(f'Active apps: {len(xw.apps)}')"
tasklist | findstr /i EXCEL
# 2. 清理残留进程
taskkill /F /IM EXCEL.EXE
# 3. 清理残留锁定文件
dir excel | findstr "~$"
# 4. 使用绝对路径重试(避免中文路径编码问题)
python table_tool/table_tool.py migrate-text G:/Git/.../excel/File.xlsx --field FieldName
# 5. 如果仍失败 → 改用手动回退脚本
问题 2: ImportError / AttributeError
症状: 导入或调用方法时报错,例如:
ImportError: cannot import name 'XmlConfigUpdater'
AttributeError: 'XMLConfigUpdater' object has no attribute '_get_data_start_row_from_xml'
原因: 代码实现中的类名拼写错误或调用了不存在的方法
解决方法: 检查 table_tool/table_toolkit/operations/*.py 文件中的类名和方法定义,确保导入和调用正确。
常见类名:
XMLConfigUpdater(不是XmlConfigUpdater)TextMigration,FormatConverter,ProtoGenerator
问题 3: Windows 中文路径编码问题
症状: 在 Git Bash 中运行时出现编码错误,例如:
FileNotFoundError: [Errno 2]: No such file or directory: '...\\excel\\S赛事模式_TacticsSettings.xlsx'
原因: Git Bash 对中文路径的处理与 Python 内部编码不一致
解决方法:
# 使用 Python 原生绝对路径(正斜杠而非反斜杠)
python -c "import openpyxl; wb = openpyxl.load_workbook('G:/Git/.../excel/文件.xlsx')"
# 或使用 Python 内联命令而非 Bash 调用
python table_tool/table_tool.py migrate-text G:/Git/.../excel/文件.xlsx --field FieldName
🔔 任务结束时:报告 xlwings 兜底场景
每次任务完成后,如果本次操作中有任何步骤回退到了 xlwings(而非 XlsxWriter / table_tool),必须在任务总结末尾附上:
⚠️ XlsxWriter 未支持场景(供后续迭代参考):
- 场景:{具体操作描述,例如 "autofit 列宽" / "写入数据验证规则" / "复制 sheet"}
- 位置:{文件名 + 方法名 / 步骤}
- 当前方案:xlwings {具体 API}
- 建议:可考虑在 XlsxWriter 中新增 {方法名} 方法
如果本次操作全程使用了 table_tool 或 XlsxWriter,则无需附加此说明(静默即可)。