Preserve original formatting in Unmarshaler by using raw byte ranges

Instead of reconstructing key-value lines from parsed components, now
uses the original raw bytes from the document. This preserves:
- Whitespace around '=' (e.g., "key   =   value")
- String quoting style (basic vs literal)
- Number formats (hex, octal, binary)
- Inline table formatting

Changes:
- Add Raw range tracking to KeyValue expressions in parseKeyval
- Update handleKeyValuesUnmarshaler to use expr.Raw directly
- Remove keyNeedsQuoting helper (no longer needed)
- Add TestIssue873_FormattingPreservation test
- Update expected output in ExampleParser_comments
This commit is contained in:
Claude
2026-01-17 12:41:38 +00:00
parent 6c995ec13e
commit 8df3b65280
4 changed files with 74 additions and 55 deletions
+6 -49
View File
@@ -690,8 +690,8 @@ func (d *decoder) handleKeyValues(v reflect.Value) (reflect.Value, error) {
// and passes them to the Unmarshaler as raw TOML bytes.
func (d *decoder) handleKeyValuesUnmarshaler(u unstable.Unmarshaler) (reflect.Value, error) {
// Collect raw bytes from all key-value expressions for this table.
// We build a valid TOML document by reconstructing each key-value line
// from the key names and the value's raw bytes.
// We use the Raw field on each KeyValue expression to preserve the
// original formatting (whitespace, quoting style, etc.) from the document.
var buf []byte
for d.nextExpr() {
@@ -706,38 +706,10 @@ func (d *decoder) handleKeyValuesUnmarshaler(u unstable.Unmarshaler) (reflect.Va
return reflect.Value{}, err
}
// Reconstruct the key-value line from the key(s) and value
keyIt := expr.Key()
first := true
for keyIt.Next() {
if !first {
buf = append(buf, '.')
}
keyNode := keyIt.Node()
// Check if key needs quoting
if keyNeedsQuoting(keyNode.Data) {
buf = append(buf, '"')
buf = append(buf, keyNode.Data...)
buf = append(buf, '"')
} else {
buf = append(buf, keyNode.Data...)
}
first = false
}
buf = append(buf, " = "...)
// Get the raw value bytes
value := expr.Value()
if value != nil {
if value.Raw.Length > 0 {
// Use raw bytes from the original document
raw := d.p.Raw(value.Raw)
buf = append(buf, raw...)
} else {
// Some value types (like Bool) don't have Raw set,
// use Data which contains the value representation
buf = append(buf, value.Data...)
}
// Use the raw bytes from the original document to preserve formatting
if expr.Raw.Length > 0 {
raw := d.p.Raw(expr.Raw)
buf = append(buf, raw...)
}
buf = append(buf, '\n')
}
@@ -749,21 +721,6 @@ func (d *decoder) handleKeyValuesUnmarshaler(u unstable.Unmarshaler) (reflect.Va
return reflect.Value{}, nil
}
// keyNeedsQuoting returns true if the key needs to be quoted in TOML.
func keyNeedsQuoting(key []byte) bool {
if len(key) == 0 {
return true
}
for _, b := range key {
// Bare keys can only contain A-Za-z0-9_-
if (b < 'A' || b > 'Z') && (b < 'a' || b > 'z') &&
(b < '0' || b > '9') && b != '_' && b != '-' {
return true
}
}
return false
}
type (
handlerFn func(key unstable.Iterator, v reflect.Value) (reflect.Value, error)
valueMakerFn func() reflect.Value