XML上传必须前端用DOMParser校验格式,否则后端解析易崩溃;Retool需开启文件内容访问并base64解码,Appsmith需用arrayBuffer+TextDecoder读取;校验失败须反馈具体行列号;后端接收时应统一UTF-8编码解析。

XML 文件上传必须校验格式,否则后端解析会直接崩溃
Retool 和 Appsmith 都不内置 XML 解析器,上传后若不做预检,parseFromString 或后端 xml.etree.ElementTree.fromstring() 很可能抛出 xml.etree.ElementTree.ParseError。别指望用户传的文件一定合法——哪怕只是多了一个空格、少了一个闭合标签,或用了不带声明的 UTF-8 BOM,都会卡死流程。
- 前端校验用浏览器原生
DOMParser最轻量,无需额外依赖 - Appsmith 的
runJavascript节点或 Retool 的JS Query都能调用它,但注意:Appsmith v1.25+ 才支持返回 Promise 的 JS Query;Retool 中需手动return结果,否则变量取不到值 - 校验失败时,立刻用
showAlert(Retool)或storeValue+Toast(Appsmith)反馈具体错误位置(error.lineNumber/error.columnNumber)
Retool 里用 FilePicker + JS Query 实现零配置上传链路
Retool 的 FilePicker 组件默认只输出 file.name 和 file.url,但 XML 上传需要原始二进制内容。必须开启 Allow file content access,并用 JS Query 读取 file.content(Base64 字符串)再转为文本。
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(file.content, "text/xml");
if (xmlDoc.querySelector("parsererror")) {
throw new Error(`XML parse error at line ${xmlDoc.querySelector("parsererror").textContent.split("line ")[1].split(" ")[0]}`);
}
return { valid: true, xmlText: atob(file.content) };
-
atob(file.content)是关键:Retool 的file.content是 Base64,不解码就传给后端会导致乱码或解析失败 - 别用
fetch直接发file.content到后端——Base64 体积膨胀 33%,且多数后端 API 接收的是 raw body 或 multipart/form-data - 建议后续用
Query发送POST /api/upload-xml,body 设为{"xml": {{jsquery1.data.xmlText}}}
Appsmith 的 FilePicker 默认不暴露文件内容,得靠 runJavascript 拿 ArrayBuffer
Appsmith 的 FilePicker 组件在 v1.24 前完全不提供文件内容访问权限,v1.25+ 才通过 runJavascript 支持读取 files[0].arrayBuffer()。绕过这个限制的唯一办法是:把 FilePicker 的 onFilesSelected 绑定到一个 runJavascript 查询,手动读取并校验。
const file = files[0];
const arrayBuffer = await file.arrayBuffer();
const text = new TextDecoder().decode(arrayBuffer);
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(text, "text/xml");
if (xmlDoc.querySelector("parsererror")) {
throw new Error("Invalid XML: " + xmlDoc.querySelector("parsererror").textContent);
}
storeValue("validXml", text);
- 必须用
arrayBuffer()+TextDecoder,不能用file.text()——Appsmith 当前版本对大文件(>5MB)调用text()会内存溢出 -
storeValue("validXml", text)后,后续查询才能用{{validXml}}引用校验后的 XML 字符串 - 如果后端要求 multipart 上传,需用
FormData构造体:const fd = new FormData(); fd.append("file", new Blob([text], {type: "application/xml"}), "data.xml");
后端接收时别忽略 Content-Type 和编码细节
即使前端校验通过,后端仍可能因请求头或编码问题报错。常见坑点:
- Retool/Appsmith 发送纯文本 XML 时,默认
Content-Type: application/json,后端 Flask/FastAPI 若用request.body读取,需显式指定encoding="utf-8",否则中文字段变\ufffd - 若用 multipart 方式上传,
Content-Type必须是multipart/form-data,且服务端要正确解析file字段名(如 Express 需multer,FastAPI 需UploadFile) - XML 声明里的编码(如
<?xml version="1.0" encoding="GBK"?>)必须与实际字节流一致,否则 PythonElementTree会抛UnicodeDecodeError
最稳妥的做法是:前端统一用 UTF-8 编码发送,后端强制按 UTF-8 解析,忽略 XML 声明中的 encoding 属性——只要用户没刻意传 GBK 文件,这条路径最稳定。










