fix(config_edit): byte-clean writes matching Loxone's format - #13
Conversation
Every write re-serialized the whole file, so a raw `diff` on a `.Loxone` reported ~11.5k changed lines for a single added block — real changes drowned in formatting noise, making review impossible. The churn was purely emitter formatting that differs from Loxone's own output. Match it so a round-trip touches only what actually changed: - `pad_self_closing(false)` — Loxone writes `<C/>`, xml-rs padded to `<C />` (this alone caused ~99.5% of the diff). - Expand attribute-less empty tags: Loxone writes `<IoData></IoData>`, never `<IoData/>` (attributed empties like `<Co K="I" U="…"/>` stay self-closed). Verified: the config has 45 `<IoData></IoData>` and zero attribute-less `<X/>`. - Un-escape `
` → literal newline in attribute values (multi-line PicoC code, notification texts). Loxone keeps literal newlines and never emits `
`, so this only reverses xml-rs's own escaping. - Restore the trailing newline. Result: adding one block now produces a diff of exactly that block (+6/-0) instead of 11.5k lines. All 78 config_edit tests pass; adds a formatting round-trip test. Fixes eisber#7 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1TT6BSmf3uXakmtfexfDt
eisber
left a comment
There was a problem hiding this comment.
The raw string post-processing can alter unchanged comment and CDATA payloads, so this is not limited to element syntax or attribute values. I reproduced it through lox config room add with <!-- preserve <IoData/> and 
 literally --> and matching CDATA: the output changed <IoData/> to <IoData></IoData> and 
 to a physical newline in both nodes. Please scope both rewrites to actual element/attribute serialization (or otherwise skip comments/CDATA) and add a regression test proving their contents round-trip unchanged.
| // Loxone keeps literal newlines inside attribute values (e.g. multi-line PicoC | ||
| // code, notification texts); xml-rs escapes them to `
`. Un-escape to match. | ||
| // Loxone never emits `
`, so this only reverses xml-rs's own escaping. | ||
| s = s.replace("
", "\n"); |
There was a problem hiding this comment.
This global replacement also applies inside serialized comments and CDATA, not only attribute values. Together with expand_attrless_empty_tags, it silently changes unchanged node content. Please make the post-processing XML-context-aware and cover comments/CDATA in a regression test.
Fixes #7.
Problem
Every write re-serialized the whole file. A raw
diffon the resulting.Loxonereported ~11,500 changed lines for a single added block — the real change was drowned in formatting noise, so changes couldn't be reviewed before uploading to a live house.The churn was purely emitter formatting that differs from Loxone's own output — the content was identical, just re-emitted differently.
Fix
Make
to_bytesmatch Loxone's on-disk conventions so a round-trip touches only what actually changed:pad_self_closing(false)— Loxone writes<C/>, xml-rs padded to<C />. This alone was ~99.5% of the diff (every self-closing element).<IoData></IoData>, never<IoData/>; attributed empties like<Co K="I" U="…"/>stay self-closed. (In the test config: 45<IoData></IoData>, zero attribute-less<X/>— a consistent rule.)
→ literal newline in attribute values (multi-line PicoC code, notification/alarm texts). Loxone keeps literal newlines and never emits
, so this only reverses xml-rs's own escaping.Result
A diff of exactly the added block (+6/-0) — raw
.Loxonediffs are now reviewable.config_edittests pass; adds a formatting round-trip test.🤖 Generated with Claude Code