Skip to content

fix: reassemble fails loudly instead of writing incomplete XML - #219

Merged
bc-m merged 4 commits into
mainfrom
fix/reassemble-fail-loudly
Nov 2, 2026
Merged

bc-m merged 4 commits into
mainfrom
fix/reassemble-fail-loudly

Conversation

@bc-m

@bc-m bc-m commented Nov 2, 2026 •

Copy link
Copy Markdown
Owner

Follow-up to #218: reassemble should never write incomplete XML without saying so. It now either gives back the export or fails with an error that names the cause.

Reassembling

  • ReplaceAction and DeleteAction: their catalogs now keep their start tag and their children in the skeleton, like the catalogs of AddAction and ModifyAction. Before, they were reassembled without the catalog start tag, which gave malformed XML without an error.
  • Metadata and DDR_INFO: a missing or unreadable file is now an error. With --lossless, these files are also written for empty elements, so a missing file always means it was lost.
  • Catalogs that aren't exploded: if such a catalog has content, reassembling fails, because the skeleton keeps only its outline. Empty single-line catalogs are still fine. Explode and reassemble now use the same rule, CatalogType::is_exploded(ver), to decide which catalogs are exploded. Because of that, value list options in 2.2.3.4 and later also count as not exploded.
  • Several files for one id: if more than one file matches a placeholder with an id, reassembling fails and lists all those files. Before, the file name order decided which one was used. Only the start tag counts as the id, so items without an id can still share a key.
  • Unused item files: the "Not part of any skeleton" report covers every catalog folder of a database, including folders no skeleton refers to. Before, it only covered folders that were read. Only catalog folders are checked, so readable scripts written with --script-extension xml aren't reported. The exploder and reassemble now share constants for the names of the non-catalog folders (_, scripts_sanitized, custom_functions_sanitized). reassemble returns these files and main prints them.
  • Multi-line placeholders: a placeholder without its end tag is now an error. Before, it consumed the rest of the skeleton. The line state is also reset after such a placeholder, so the end tag that follows is recognized.

Exploding

  • Metadata of split exports is named after the catalog, like DDR_INFO, for example metadata_ScriptCatalog.xml. Before, all catalog files wrote the same metadata.xml.
  • Duplicate writes: if a run writes the same path a second time, OutputFiles::write keeps the content written first and warns if the new content differs. A hash of the content is stored per path, so the check doesn't read the file again and threads can't race on it.

Changes to output ⚠️

  • In exports split into catalogs, metadata.xml is now metadata_<catalog>.xml, with and without --lossless.
  • With --lossless, metadata*.xml and ddr_info*.xml are also written when the element is empty.

Tests

  • New round trips: an export split into two catalog files, where only one has metadata and DDR_INFO with content, and ReplaceAction/DeleteAction.
  • New error tests: a missing or unreadable metadata.xml, a catalog that isn't exploded, several files for one id, and a placeholder without its end tag.
  • New for unused files and duplicate keys: unused files in catalog folders that no skeleton refers to, and items without an id that share a key.
  • The fixtures' round-trip test now uses --script-extension xml and checks that no file is reported as unused.
  • An OutputFiles test checks that a second write keeps the first content.
  • The split, shared-key and missing-end-tag tests fail without the fixes.
  • Also checked with a large real-world export in both output trees, including --script-extension xml: there were no warnings and no unused files, and every file came back identical to the export (the UTF-16 files are identical in content, as documented).

Not included

  • Unused item files are reported on stderr, but the exit code stays 0.
  • Catalogs that aren't exploded are still not kept in full in the skeleton, so they can't be reassembled. Keeping their whole subtree would make lossless output complete; that is for a separate issue.
  • Items without an id that have identical keys are still matched in file name order.

🤖 Generated with Claude Code

bc-m and others added 4 commits November 2, 2026 22:32
- Catalogs of ReplaceAction and DeleteAction keep their start tag and
  their children in the skeleton, like those of AddAction and ModifyAction;
  they were reassembled without their catalog start tag
- Only a missing metadata or DDR_INFO file means an empty element; other
  read errors are reported instead of dropping the content
- Item files are reported as not part of any skeleton also in catalog
  folders no skeleton refers to, not only in folders that were read
- The state after a placeholder that spans several lines is reset, so the
  catalog end tag that follows is recognized
- Warn when the same output file is written more than once in a run,
  because the content written first is lost

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Only catalog folders are checked for unused item files, so readable
  scripts written with --script-extension xml aren't reported; the names of
  the non-catalog folders are shared with the exploder
- Folders already read for placeholders aren't listed a second time
- Lossless output writes metadata and DDR_INFO also when they are empty, so
  a missing file is an error when reassembling instead of an empty element
- Reassembling fails on catalogs that aren't exploded, because the skeleton
  keeps only their outline
- Reassembling fails when several files have the start tag of one id
- A file written twice in one run keeps the content written first, and the
  warning comes before anything is overwritten

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Metadata of a split export is named after the catalog like DDR_INFO,
  e.g. metadata_ScriptCatalog.xml, so the files don't overwrite each other
- A file written twice in one run warns only if the content differs; the
  hash of each written file is kept, so there is no race between threads
- Only the start tag decides whether an item has an id, so items without an
  id may still share their key
- Value list options of 2.2.3.4 and later count as not exploded when
  reassembling as well, with one rule shared by both sides
- A placeholder without its end tag is an error instead of consuming the
  rest of the skeleton
- Simpler catalog folder listing, and one helper for lines that hold a
  whole element
- Round trip of an export split into catalogs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bc-m
bc-m merged commit 782b9ef into main Nov 2, 2026
1 check passed
@bc-m
bc-m deleted the fix/reassemble-fail-loudly branch November 2, 2026 23:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant