fix: escape newlines in WireManager.add_text
The KiCad s-expression parser rejects raw newline and carriage-return characters inside quoted string literals — a multi-line text annotation written through `add_text` produced a `.kicad_sch` file that eeschema silently tolerated but `kicad-cli sch ...` refused with "Failed to load schematic." The escape pass only handled backslashes and double quotes. Add `\\n` → `\\\\n` and `\\r` → `\\\\r` to the same escape chain. Order matters: backslashes are escaped first so we don't double-escape our own escapes. A new regression test (`test_escapes_newlines_in_multiline_text`) checks both that the resulting quoted string literal contains no raw newline characters and that the file round-trips cleanly through the sexpdata parser. End-to-end smoke: a 4-line annotation written through the patched add_text now passes `kicad-cli sch erc` (exit 0) where the previous behaviour failed parse. Note: the same escape gap exists in `_make_hierarchical_label_text` and `_make_sheet_pin_text` for unescaped quotes/newlines in the user- supplied text. Not fixed here to keep this PR scoped to the documented add_text bug; happy to fold it in if a reviewer prefers.
This commit is contained in:
@@ -136,6 +136,39 @@ class TestWireManagerAddText:
|
||||
content = sch.read_text(encoding="utf-8")
|
||||
assert r"He said \"hello\"" in content
|
||||
|
||||
def test_escapes_newlines_in_multiline_text(self, tmp_path):
|
||||
"""Raw newlines in quoted string literals break kicad-cli's parser
|
||||
(silently in eeschema, but kicad-cli sch reports 'Failed to load
|
||||
schematic'). They must be escaped to the two-character \\n sequence."""
|
||||
from commands.wire_manager import WireManager
|
||||
|
||||
sch = tmp_path / "test.kicad_sch"
|
||||
sch.write_text(_MINIMAL_SCH, encoding="utf-8")
|
||||
|
||||
WireManager.add_text(sch, "line one\nline two\r\nline three", [10.0, 10.0])
|
||||
|
||||
content = sch.read_text(encoding="utf-8")
|
||||
# The text S-expression's quoted argument must not contain a literal
|
||||
# newline. Find the (text "...") and check its first quoted arg.
|
||||
text_start = content.index('(text "') + len('(text "')
|
||||
# Find matching close-quote, respecting backslash escapes.
|
||||
i = text_start
|
||||
while i < len(content):
|
||||
if content[i] == "\\":
|
||||
i += 2
|
||||
continue
|
||||
if content[i] == '"':
|
||||
break
|
||||
i += 1
|
||||
quoted = content[text_start:i]
|
||||
assert (
|
||||
"\n" not in quoted and "\r" not in quoted
|
||||
), f"Quoted text still contains a raw newline: {quoted!r}"
|
||||
assert "\\n" in quoted, "Newline should be escaped to \\n"
|
||||
|
||||
# And the file must round-trip through the s-expression parser cleanly.
|
||||
sexpdata.loads(content)
|
||||
|
||||
def test_result_is_valid_sexp(self, tmp_path):
|
||||
from commands.wire_manager import WireManager
|
||||
|
||||
|
||||
Reference in New Issue
Block a user