Fix APT CLI roundtrip visual and script preservation
Preserve edittext channels, alpha/color transforms, inactive placement fields, zero ratio/clipping and null ClipAction presence. Keep unlabelled flag bits, quoted/escaped script strings, export declarations and correctly typed CONST and pushdata values. Validate signed integers and write byte-aligned UTF-8. Add 23 public CLI regressions and record the native 174-resource batch plus bounded retests: four discovered pages are repaired, while the sound placeholder and native float formatting remain explicit accuracy limits. Rebuild the 1.0.1 Windows executable and leave unrelated SDK changes untouched.
This commit is contained in:
+12
-44
@@ -26,7 +26,7 @@ function expand(input, output, opts) {
|
||||
const rawXml = U.readRawXml(tmpdir, baseName);
|
||||
|
||||
// 4. Parse manifest if available (for texture name resolution)
|
||||
const manifest = manifestData ? cheerio.load(manifestData, { xmlMode: true, decodeEntities: false }) : null;
|
||||
const manifest = manifestData ? cheerio.load(manifestData, { xmlMode: true, decodeEntities: true }) : null;
|
||||
|
||||
// 5. Parse .dat if available
|
||||
const dats = parseDat(datData);
|
||||
@@ -69,20 +69,11 @@ function parseDat(datData) {
|
||||
function xmladdhint(rawXml, baseName, aptName, manifest, dats, filedir, oldStyle) {
|
||||
U.resetLabels();
|
||||
|
||||
const $ = cheerio.load(rawXml, { xmlMode: true, decodeEntities: false });
|
||||
const $ = cheerio.load(rawXml, { xmlMode: true, decodeEntities: true });
|
||||
const ActionNames = oldStyle ? C.ActionNames2 : C.ActionNames;
|
||||
const xmldoc = $;
|
||||
|
||||
// Remove duplicated exports
|
||||
const exp = {};
|
||||
$('export').each(function () {
|
||||
const nm = $(this).attr('name');
|
||||
if (exp[nm]) {
|
||||
$(this).remove();
|
||||
} else {
|
||||
exp[nm] = true;
|
||||
}
|
||||
});
|
||||
// Preserve export order and repeated names, including Object prototype names.
|
||||
|
||||
// Calculate aligned positions for each action container
|
||||
const actionContainers = $('initaction, buttonaction, action, clipaction');
|
||||
@@ -179,8 +170,8 @@ function xmladdhint(rawXml, baseName, aptName, manifest, dats, filedir, oldStyle
|
||||
}
|
||||
});
|
||||
astr += U.indentstr(v);
|
||||
// Replace CDATA with text node (entity-encoded)
|
||||
v.empty().text(astr);
|
||||
// Parse an XML CDATA node so serialization does not entity-encode assembly text.
|
||||
v.empty().append('<![CDATA[' + astr.replace(/\]\]>/g, ']]]]><![CDATA[>') + ']]>');
|
||||
});
|
||||
|
||||
// Resolve flags to symbolic names
|
||||
@@ -268,10 +259,10 @@ function xmladdhint(rawXml, baseName, aptName, manifest, dats, filedir, oldStyle
|
||||
$v.removeAttr('character');
|
||||
}
|
||||
if (flags.indexOf('HasMatrix') < 0) {
|
||||
$v.removeAttr('matrix');
|
||||
$v.removeAttr('matrix rotm00 rotm01 rotm10 rotm11 tx ty');
|
||||
}
|
||||
if (flags.indexOf('HasColorTransform') < 0) {
|
||||
$v.removeAttr('rgbamul rgbaadd');
|
||||
$v.removeAttr('rgbamul rgbaadd red green blue alpha unknown');
|
||||
}
|
||||
if (flags.indexOf('HasRatio') < 0) {
|
||||
$v.removeAttr('ratio');
|
||||
@@ -287,12 +278,7 @@ function xmladdhint(rawXml, baseName, aptName, manifest, dats, filedir, oldStyle
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up: remove extra attributes from the final output
|
||||
$('edittext').each(function () {
|
||||
const $v = $(this);
|
||||
// Override for xmladdhint: store original color info
|
||||
// (the original code swaps red/blue for edittext display - preserve that)
|
||||
});
|
||||
// Keep edittext channels in cc3tools' order; build uses the same order.
|
||||
|
||||
// Clean internal attributes
|
||||
$('*').each(function () {
|
||||
@@ -302,29 +288,11 @@ function xmladdhint(rawXml, baseName, aptName, manifest, dats, filedir, oldStyle
|
||||
|
||||
// Serialize
|
||||
let output = $.xml();
|
||||
// Wrap action container text content in CDATA sections
|
||||
// This prevents cheerio from entity-encoding & in the action assembly text
|
||||
output = output.replace(
|
||||
/<(initaction|buttonaction|action|clipaction)(\s[^>]*)?>([\s\S]*?)<\/\1>/g,
|
||||
(match, tag, attrs, content) => {
|
||||
const trimmed = content.trim();
|
||||
if (!trimmed) return match;
|
||||
return '<' + tag + (attrs || '') + '><![CDATA[' + content + ']]></' + tag + '>';
|
||||
}
|
||||
);
|
||||
// Clean up excessive whitespace
|
||||
output = output.replace(/^[\t]+$/gm, '').replace(/[\r\n]+/g, '\n');
|
||||
return output;
|
||||
}
|
||||
|
||||
// ─── Decode XML entities in attribute values ───
|
||||
// With decodeEntities: false in cheerio load, attribute values preserve XML entities
|
||||
// (e.g. & stays as &). We need to decode them for human-readable output.
|
||||
function decodeAttrEntities(s) {
|
||||
if (typeof s !== 'string') return s;
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'");
|
||||
}
|
||||
|
||||
// ─── actiontostring(): XML action node → text assembly ───
|
||||
function actiontostring(a, ActionNames, $) {
|
||||
const action = parseInt(a.attr('action'), 10);
|
||||
@@ -347,7 +315,7 @@ function actiontostring(a, ActionNames, $) {
|
||||
action === C.EA_CALLNAMEDFUNCTION || action === C.EA_CALLNAMEDMETHODPOP ||
|
||||
action === C.EA_CALLNAMEDMETHOD || action === C.EA_PUSHVALUEOFVAR ||
|
||||
action === C.EA_GETNAMEDMEMBER || action === C.EA_PUSHWORDCONSTANT) {
|
||||
return aname + ' ' + (a.attr('str') !== undefined ? U.mkstr(decodeAttrEntities(a.attr('str'))) : a.attr('num'));
|
||||
return aname + ' ' + (a.attr('str') !== undefined ? U.mkstr(a.attr('str')) : a.attr('num'));
|
||||
}
|
||||
|
||||
// goto: ActionGotoFrame pos
|
||||
@@ -357,7 +325,7 @@ function actiontostring(a, ActionNames, $) {
|
||||
|
||||
// geturl
|
||||
if (a[0].tagName === 'geturl') {
|
||||
return aname + ' ' + U.mkstr(decodeAttrEntities(a.attr('str1'))) + ', ' + U.mkstr(decodeAttrEntities(a.attr('str2')));
|
||||
return aname + ' ' + U.mkstr(a.attr('str1')) + ', ' + U.mkstr(a.attr('str2'));
|
||||
}
|
||||
|
||||
// definefunction
|
||||
@@ -408,7 +376,7 @@ function actiontostring(a, ActionNames, $) {
|
||||
|
||||
// string instruction: pushstr "hello"
|
||||
if (a[0].tagName === 'string') {
|
||||
return aname + ' ' + U.mkstr(decodeAttrEntities(a.attr('str')));
|
||||
return aname + ' ' + U.mkstr(a.attr('str'));
|
||||
}
|
||||
|
||||
// pushdata: pushdata val1, val2, ...
|
||||
@@ -416,7 +384,7 @@ function actiontostring(a, ActionNames, $) {
|
||||
const vals = [];
|
||||
a.children().each(function () {
|
||||
const v = $(this);
|
||||
vals.push(v.attr('str') !== undefined ? U.mkstr(decodeAttrEntities(v.attr('str'))) : v.attr('num'));
|
||||
vals.push(v.attr('str') !== undefined ? U.mkstr(v.attr('str')) : v.attr('num'));
|
||||
});
|
||||
return aname + ' ' + vals.join(', ');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user