Commit Graph
6 Commits
Author SHA1 Message Date
Umut CANandClaude Opus 5 b464bcdaf5 fix(task-4): block_define dedupes requested layers case-insensitively so one layer in two spellings is created and reported once
DXF and ActiveX layer tables are case-insensitive, but block_define collected
the primitives' layers with a case-sensitive set. A request naming 'Alpha' and
'ALPHA' with create_layers=True made ezdxf add the first, then raise
DXFTableEntryError on the second after the write — layer left behind, no block,
surfaced as an internal error instead of a named refusal. COM ran Layers.Add
twice, got the existing layer back the second time, and reported two layers
created for one that appeared; the two engines diverged and the refusal message
without create_layers named the same layer twice.

referenced_layers() in backends/block_specs.py keys the set on the folded name
and keeps the first spelling; both engines consume it. _FakeLayers in the
hardening tests now matches Item/Add case-insensitively and returns the existing
record on a duplicate Add, as the ActiveX table does, so the COM path is pinned.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-18 00:23:16 +03:00
Umut CANandClaude Opus 5 375c9f4c70 fix(blocks): block_define refuses a non-finite base point and a missing layer unless create_layers
base_x/base_y were only float()-cast, so a NaN base point was written into
the BLOCK record; a primitive on a layer the drawing lacks was written as a
dangling reference doc.audit() never flags (and on COM would fail mid-loop
after Blocks.Add). validate_base_point names the key; the layer table is
probed before the first write and the missing names are refused, or created
and reported as layers_created when create_layers=true (new optional kwarg,
contract + both engines + tool).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-18 00:11:38 +03:00
Umut CANandClaude Opus 5 7f79497928 fix(blocks): attribute values are validated before any write; null/bool/objects refused by tag
block_insert and block_set_attributes applied str() to whatever arrived, so
a JSON null became the text None and a non-mapping failed after the INSERT
was written. validate_attribute_values (backends/block_specs.py) runs first
on both engines: strings pass the one-line rule, ints and finite floats are
written as plain digits, everything else is a TypeError naming the tag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-17 23:59:17 +03:00
Umut CANandClaude Opus 5 0a0be740bd fix(task-2): refuse multi-line / trailing-caret TEXT and ATTDEF strings before write
`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
2026-09-15 13:34:47 +03:00
Umut CANandClaude Opus 5 41585757c9 fix(task-2): close the trailing-newline tag hole and gate DXF-illegal layer names
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
2026-09-15 13:22:56 +03:00
Umut CANandClaude Opus 5 2cc4008fe8 feat(blocks): shared validation for block_define primitive and ATTDEF specs
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-15 13:10:43 +03:00