`text`, ATTDEF `prompt` and `default` were only isinstance(str)-checked, so
'A\nB' and '10^' passed validation and ezdxf's DXFAttr fixer
(`fix_one_line_text`) rewrote them on `add_text` / `add_attdef` at DEBUG
log level — the declared ATTRIB value and the one the P&ID reader gets back
differed silently, and COM (raw string) diverged from ezdxf. Same hole the
tag check closed in 4158575, left open on its three siblings.
A `_one_line(value, where)` helper now refuses '\n', '\r', any other C0
control, and a trailing '^' (ezdxf's own `is_valid_one_line_text` rule),
naming `entities[i]: 'text'` / `attdefs[i]: 'prompt'|'default'`. Tests cover
the refusals, the strings ezdxf keeps verbatim ('10^2', 'Ø20 H7', '%%C20'),
and an ezdxf round-trip proving accepted values are stored unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
Two ways a malformed block_define request slipped past the pre-write
validator and reached the engine:
* `ATTDEF_TAG_RE.match` with a `$` anchor accepts 'TAG\n' because `$`
matches before a trailing newline. ezdxf strips the newline on write, so
'TAG\n' + 'TAG' landed as two ATTDEFs with one tag, defeating the
duplicate-tag refusal, and the tag-keyed attribute readers then dropped
one silently. Now `fullmatch`.
* `_layer` only required a non-empty string, so a DXF-illegal name such as
'BAD<LAYER' passed and ezdxf raised DXFValueError mid-loop, after the
block existed and earlier primitives were written (with overwrite=True,
after the old definition was already wiped). The gate now refuses on the
same forbidden-character rule `sanitize_symbol_name` uses, exposed as
`security.illegal_symbol_name_chars`, with a TypeError naming
`entities[i]` and 'layer'.
Tests cover both repros and fail against the previous code.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd