DSN reader: only the declared string_quote character opens a string
KiCad declares " and writes a keycap part number as a bare ', which the tokenizer took for an unterminated string (PCBench f.60_keyboard).
This commit is contained in:
@@ -9,6 +9,8 @@ tolerant ``skip_scope`` behaviour.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from .model import (
|
from .model import (
|
||||||
ClearanceRule,
|
ClearanceRule,
|
||||||
ComponentPlace,
|
ComponentPlace,
|
||||||
@@ -481,12 +483,26 @@ def _read_network(node: SExp, board: DsnBoard) -> None:
|
|||||||
# --- top level ---------------------------------------------------------------
|
# --- top level ---------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
_STRING_QUOTE = re.compile(r"\(\s*string_quote\s+(\S)\s*\)")
|
||||||
|
|
||||||
|
|
||||||
|
def _declared_quote(text: str) -> str:
|
||||||
|
"""The quote character the file declares in ``(string_quote X)``.
|
||||||
|
|
||||||
|
Only that character opens a string. Without a declaration both kinds are
|
||||||
|
accepted, as before: KiCad declares ``"`` and then writes a bare ``'``
|
||||||
|
where a value is a single quote mark, e.g. a keycap part number.
|
||||||
|
"""
|
||||||
|
found = _STRING_QUOTE.search(text[:4096])
|
||||||
|
return found.group(1) if found else "\"'"
|
||||||
|
|
||||||
|
|
||||||
def parse_dsn(text: str) -> DsnBoard:
|
def parse_dsn(text: str) -> DsnBoard:
|
||||||
"""Parse DSN ``text`` into a :class:`DsnBoard`.
|
"""Parse DSN ``text`` into a :class:`DsnBoard`.
|
||||||
|
|
||||||
Raises :class:`DsnParseError` if the top-level scope is not ``(pcb ...)``.
|
Raises :class:`DsnParseError` if the top-level scope is not ``(pcb ...)``.
|
||||||
"""
|
"""
|
||||||
top = parse(text)
|
top = parse(text, _declared_quote(text))
|
||||||
if top.head != "pcb":
|
if top.head != "pcb":
|
||||||
raise DsnParseError(
|
raise DsnParseError(
|
||||||
f"not a Specctra DSN file: expected top-level '(pcb ...)', got '({top.head} ...)'"
|
f"not a Specctra DSN file: expected top-level '(pcb ...)', got '({top.head} ...)'"
|
||||||
|
|||||||
@@ -156,3 +156,26 @@ def test_unplaced_component_has_no_coords():
|
|||||||
place = board.placements[0].places[0]
|
place = board.placements[0].places[0]
|
||||||
assert place.name == "U9"
|
assert place.name == "U9"
|
||||||
assert place.x is None and place.y is None
|
assert place.x is None and place.y is None
|
||||||
|
|
||||||
|
|
||||||
|
# --- string_quote ----------------------------------------------------------
|
||||||
|
|
||||||
|
_QUOTED = """(pcb q.dsn
|
||||||
|
(parser (string_quote {q}) (space_in_quoted_tokens on))
|
||||||
|
(resolution um 10) (unit um)
|
||||||
|
(structure (layer F.Cu (type signal)) (boundary (path pcb 0 0 0 1000 0 1000 1000 0 1000 0 0)))
|
||||||
|
(placement (component "SW1" (place SW1 100 100 front 0 (PN {pn}))))
|
||||||
|
(library)
|
||||||
|
(network)
|
||||||
|
)"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_bare_apostrophe_is_an_atom_when_the_file_quotes_with_double_quotes():
|
||||||
|
# KiCad declares " and writes a keycap's part number as a bare '
|
||||||
|
board = parse_dsn(_QUOTED.format(q='"', pn="'"))
|
||||||
|
assert board.parser.string_quote == '"'
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_declared_quote_character_is_the_one_that_opens_strings():
|
||||||
|
board = parse_dsn(_QUOTED.format(q="'", pn="'10k 1%'").replace('"SW1"', "'SW1'"))
|
||||||
|
assert board.parser.string_quote == "'"
|
||||||
|
|||||||
Reference in New Issue
Block a user