ODS writer: drop duplicate XML declaration; harden sheet-name sanitiser.
LibreOffice rejected the generated `.ods` with `Format error at 2,39 in content.xml`. Root cause: `pugi::format_no_declaration` suppresses only the *implicit* declaration auto-added at save time — the explicit `node_declaration` I had appended to the document still got serialised, on top of a manual `<?xml…?>` string prepend in the output. Two declarations back-to-back, invalid XML. Fix: let pugixml emit the explicit declaration node, drop the manual prepend. Also harden the sheet-name sanitiser in the export action: ODS / Excel also forbid `< > &` in raw cell or table names, so the default connection name `bp/J20 <-> payload1/P0` made content.xml entity- escape `<` to `<`, which a few viewers handle but Excel rejects. Clip to 31 chars too (Excel's hard limit) so multi-name connections don't blow up the open. Verified by `soffice --headless --convert-to csv` round-tripping the output without errors. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -73,11 +73,14 @@ std::string build_content_xml(const std::vector<OdsSheet> &sheets) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
doc.save(oss, " ", pugi::format_no_declaration | pugi::format_raw);
|
// We added the declaration node explicitly above, so pugi will emit
|
||||||
// pugi's `format_no_declaration` drops the `<?xml…?>` line; we want
|
// it. `format_no_declaration` only suppresses *implicit* injection;
|
||||||
// it. Re-emit explicitly to control encoding.
|
// setting it here would leave the explicit node in place and silently
|
||||||
return std::string(R"(<?xml version="1.0" encoding="UTF-8"?>)" "\n")
|
// duplicate would-be auto-emission elsewhere. Keep format_raw to
|
||||||
+ oss.str();
|
// avoid pretty-print overhead — the file is read by software, not
|
||||||
|
// humans.
|
||||||
|
doc.save(oss, "", pugi::format_raw);
|
||||||
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minimum META-INF/manifest.xml that satisfies the spec.
|
// Minimum META-INF/manifest.xml that satisfies the spec.
|
||||||
|
|||||||
@@ -98,12 +98,18 @@ void Tui::RegisterExportCommands() {
|
|||||||
int total = 0;
|
int total = 0;
|
||||||
for (auto &ckv : *sys->connections()) {
|
for (auto &ckv : *sys->connections()) {
|
||||||
Connection *c = ckv.second;
|
Connection *c = ckv.second;
|
||||||
// Sheet names: Excel rejects /\?*:[] characters
|
// Sheet names: Excel rejects /\?*:[] characters,
|
||||||
// (LibreOffice tolerates them but stays portable).
|
// ODS forbids < > & in raw cell/table names
|
||||||
|
// (they'd need entity-escaping but a few viewers
|
||||||
|
// choke on them). Sanitise to underscores.
|
||||||
std::string sname = c->name;
|
std::string sname = c->name;
|
||||||
for (char &ch : sname)
|
for (char &ch : sname)
|
||||||
if (ch == '/' || ch == '\\' || ch == '?' || ch == '*'
|
if (ch == '/' || ch == '\\' || ch == '?' || ch == '*'
|
||||||
|| ch == ':' || ch == '[' || ch == ']') ch = '_';
|
|| ch == ':' || ch == '[' || ch == ']'
|
||||||
|
|| ch == '<' || ch == '>' || ch == '&') ch = '_';
|
||||||
|
// Sheet names also can't exceed 31 chars in
|
||||||
|
// Excel; clip aggressively to stay portable.
|
||||||
|
if (sname.size() > 31) sname = sname.substr(0, 31);
|
||||||
OdsSheet *s = w.add_sheet(sname);
|
OdsSheet *s = w.add_sheet(sname);
|
||||||
const char *hdr[] = {
|
const char *hdr[] = {
|
||||||
"transform",
|
"transform",
|
||||||
|
|||||||
Reference in New Issue
Block a user