feat(scripts): readLayerXml - UTF-8 解码 + Layer/Spirit 字段提取

This commit is contained in:
2026-09-02 07:52:11 +08:00
parent 9a695878ba
commit cfca336e98
2 changed files with 94 additions and 0 deletions
@@ -0,0 +1,39 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readLayerXml } from '../../scripts/legacy-layer-to-cocos-prefab.mjs';
test('readLayerXml 解码 UTF-8 并提取 Layer00002 字段', () => {
const layer = readLayerXml('framework-tests/legacy-layer-migration/fixtures/Layer00002.xml');
assert.equal(layer.layerId, '2');
assert.equal(layer.layerName, 'Login_Layer');
assert.equal(layer.spirits.length, 18, 'Login_Layer 有 18 个 Spirit');
});
test('Spirit 字段完整提取(含中文名)', () => {
const layer = readLayerXml('framework-tests/legacy-layer-migration/fixtures/Layer00002.xml');
const spirit405 = layer.spirits.find(s => s.id === '405');
assert.ok(spirit405, 'Spirit id=405 应存在');
assert.equal(spirit405.type, '0'); // Sprite
assert.equal(spirit405.x, '734');
assert.equal(spirit405.y, '143');
assert.equal(spirit405.w, '59');
assert.equal(spirit405.h, '148');
assert.equal(spirit405.group, '2');
// 验证 UTF-8 中文名解码: spirit id=2 在 fixture 中实际 Name="微信登录背景".
// 注: brief 原版用 spirit id=405 + /渠道/ 验证中文, 但 fixture 里 405 的 Name 是
// "Spirit405" (无中文), 故改用含中文的 spirit id=2 验证 UTF-8 解码.
const spirit2 = layer.spirits.find(s => s.id === '2');
assert.ok(spirit2, 'Spirit id=2 应存在');
assert.match(spirit2.name, /微信/); // UTF-8 解码后中文名应正常
});
test('BelongGroupID 提取(group=0 节点也存在)', () => {
const layer = readLayerXml('framework-tests/legacy-layer-migration/fixtures/Layer00002.xml');
for (const s of layer.spirits) {
assert.ok(s.group !== undefined, `Spirit ${s.id} (${s.name}) 应有 group 字段`);
}
const g0 = layer.spirits.filter(s => s.group === '0');
assert.ok(g0.length >= 0); // Login_Layer 没有 group=0,但提取逻辑应支持
});
@@ -0,0 +1,55 @@
import { readFileSync } from 'node:fs';
import { DOMParser } from '@xmldom/xmldom';
/**
* 读取 Game_Surface_3 风格的 Layer XML (UTF-8 编码), 解析 Layer + Spirit 字段.
*
* 协议来源: docs/protocol/ (服务器零改动). XML 结构与 save/Layer*.xml 一致:
* <Layer>
* <PropertyList><Property Name="ID" Value="2"/><Property Name="Name" Value="..."/></PropertyList>
* <SpiritList>
* <Spirit>
* <PropertyList><Property Name="SpiritType" Value="0"/>...</PropertyList>
* </Spirit>
* </SpiritList>
* </Layer>
*
* 数据源权威: 字段缺失/不存在一律返回 undefined (不上游兜底, 由下游显式判错).
*
* @param {string} path - 文件路径 (相对 cwd 或绝对)
* @returns {{ layerId: string|undefined, layerName: string|undefined, spirits: Array<{ id, name, type, imgResID, x, y, w, h, group, indexOfLayer }> }}
*/
export function readLayerXml(path) {
const text = readFileSync(path, 'utf8');
const doc = new DOMParser().parseFromString(text, 'text/xml');
const getProp = (el, name) => {
const prop = Array.from(el.getElementsByTagName('Property'))
.find(p => p.getAttribute('Name') === name);
return prop?.getAttribute('Value');
};
const layerEl = doc.getElementsByTagName('Layer')[0];
const layerPropList = layerEl.getElementsByTagName('PropertyList')[0];
const layerId = getProp(layerPropList, 'ID');
const layerName = getProp(layerPropList, 'Name');
const spiritEls = doc.getElementsByTagName('Spirit');
const spirits = Array.from(spiritEls).map(sp => {
const propList = sp.getElementsByTagName('PropertyList')[0];
return {
id: getProp(propList, 'ID'),
name: getProp(propList, 'Name'),
type: getProp(propList, 'SpiritType'),
imgResID: getProp(propList, 'ImgResID'),
x: getProp(propList, 'X'),
y: getProp(propList, 'Y'),
w: getProp(propList, 'Width'),
h: getProp(propList, 'Height'),
group: getProp(propList, 'BelongGroupID'),
indexOfLayer: getProp(propList, 'IndexOfLayer'),
};
});
return { layerId, layerName, spirits };
}