def __init__(self): self.doc: Optional["Drawing"] = None self.entries: Dict[str, T] = OrderedDict() self._head = TableHead()
def _set_head(self, name: str, handle: str = None) -> None: self._head = TableHead.new(handle, owner='0', dxfattribs={'name': name}, doc=self.doc)
class Table(Generic[T]): TABLE_TYPE = "UNKNOWN" def __init__(self): self.doc: Optional["Drawing"] = None self.entries: Dict[str, T] = OrderedDict() self._head = TableHead() def load(self, doc: "Drawing", entities: Iterator["DXFEntity"]) -> None: """Loading interface. (internal API)""" self.doc = doc table_head = next(entities) if isinstance(table_head, TableHead): self._head = table_head else: raise const.DXFStructureError( "Critical structure error in TABLES section." ) expected_entry_dxftype = self.TABLE_TYPE for table_entry in entities: if table_entry.dxftype() == expected_entry_dxftype: self._append(cast(T, table_entry)) else: logger.warning( f"Ignored invalid DXF entity type '{table_entry.dxftype()}'" f" in {self.TABLE_TYPE} table." ) def reset(self, doc: "Drawing", handle: str) -> None: """Reset table. (internal API)""" self.doc = doc self._set_head(self.TABLE_TYPE, handle) self.entries.clear() def _set_head(self, name: str, handle: str = None) -> None: self._head = TableHead.new( handle, owner="0", dxfattribs={"name": name}, doc=self.doc ) @property def head(self): """Returns table head entry.""" return self._head @property def name(self) -> str: return self.TABLE_TYPE @staticmethod def key(name: str) -> str: """Unified table entry key.""" return validator.make_table_key(name) def has_entry(self, name: str) -> bool: """Returns ``True`` if an table entry `name` exist.""" return self.key(name) in self.entries __contains__ = has_entry def __len__(self) -> int: """Count of table entries.""" return len(self.entries) def __iter__(self) -> Iterator[T]: """Iterable of all table entries.""" for e in self.entries.values(): if e.is_alive: yield e def new(self, name: str, dxfattribs: dict = None) -> T: """Create a new table entry `name`. Args: name: name of table entry, case insensitive dxfattribs: additional DXF attributes for table entry """ if self.has_entry(name): raise const.DXFTableEntryError( f"{self.TABLE_TYPE} '{name}' already exists!" ) dxfattribs = dxfattribs or {} dxfattribs["name"] = name dxfattribs["owner"] = self._head.dxf.handle return self.new_entry(dxfattribs) def get(self, name: str) -> T: """Get table entry `name` (case insensitive). Raises :class:`DXFValueError` if table entry does not exist. """ key = self.key(name) entry = self.entries.get(key, None) if entry: return entry else: raise const.DXFTableEntryError(name) def remove(self, name: str) -> None: """Removes table entry `name`. Raises :class:`DXFValueError` if table-entry does not exist. """ key = self.key(name) entry = self.get(name) self.entitydb.delete_entity(entry) self.discard(key) def duplicate_entry(self, name: str, new_name: str) -> T: """Returns a new table entry `new_name` as copy of `name`, replaces entry `new_name` if already exist. Raises: DXFValueError: `name` does not exist """ entry = self.get(name) entitydb = self.entitydb if entitydb: new_entry = entitydb.duplicate_entity(entry) else: # only for testing! new_entry = entry.copy() new_entry.dxf.name = new_name entry = cast(T, new_entry) self._append(entry) return entry def discard(self, name: str) -> None: """Remove table entry without destroying object. (internal API)""" del self.entries[self.key(name)] def replace(self, name: str, entry: T) -> None: """Replace table entry `name` by new `entry`. (internal API)""" self.discard(name) self._append(entry) @property def entitydb(self) -> "EntityDB": return self.doc.entitydb # type: ignore def new_entry(self, dxfattribs: dict) -> T: """Create and add new table-entry of type 'self.entry_dxftype'. Does not check if an entry dxfattribs['name'] already exists! Duplicate entries are possible for Viewports. """ assert self.doc is not None, "valid DXF document expected" entry = cast( T, factory.create_db_entry(self.TABLE_TYPE, dxfattribs, self.doc) ) self._append(entry) return entry def _append(self, entry: T) -> None: """Add a table entry, replaces existing entries with same name. (internal API). """ assert entry.dxftype() == self.TABLE_TYPE self.entries[self.key(entry.dxf.name)] = entry def add_entry(self, entry: T) -> None: """Add a table `entry`, created by other object than this table. (internal API) """ if entry.dxftype() != self.TABLE_TYPE: raise const.DXFTypeError( f"Invalid table entry type {entry.dxftype()} " f"for table {self.TABLE_TYPE}" ) name = entry.dxf.name if self.has_entry(name): raise const.DXFTableEntryError( f"{self._head.dxf.name} {name} already exists!" ) entry.doc = self.doc entry.dxf.owner = self._head.dxf.handle self._append(entry) def export_dxf(self, tagwriter: "TagWriter") -> None: """Export DXF representation. (internal API)""" def prologue(): self.update_owner_handles() # The table head itself has no owner and is therefore always '0': self._head.dxf.owner = "0" self._head.dxf.count = len(self) self._head.export_dxf(tagwriter) def content(): for entry in self.entries.values(): # VPORT if isinstance(entry, list): for e in entry: e.export_dxf(tagwriter) else: entry.export_dxf(tagwriter) def epilogue(): tagwriter.write_tag2(0, "ENDTAB") prologue() content() epilogue() def update_owner_handles(self) -> None: owner_handle = self._head.dxf.handle for entry in self.entries.values(): entry.dxf.owner = owner_handle def set_handle(self, handle: str): """Set new `handle` for table, updates also :attr:`owner` tag of table entries. (internal API) """ if self._head.dxf.handle is None: self._head.dxf.handle = handle self.update_owner_handles() def audit(self, auditor: Auditor): # The table entries are stored in the entity database and are already # audited! self._fix_table_head(auditor) self._fix_entry_handles(auditor) def _fix_entry_handles(self, auditor: Auditor): # Why: see duplicate handle issue #604 entitydb = self.entitydb for entry in self: entity = entitydb.get(entry.dxf.handle) if entity is not entry: # duplicate handle usage # This can break entities referring to this entity, but at # least the DXF readable entry.dxf.handle = entitydb.next_handle() self.entitydb.add(entry) auditor.fixed_error( code=AuditError.INVALID_TABLE_HANDLE, message=f"Fixed invalid table entry handle in {entry}", ) def _fix_table_head(self, auditor: Auditor): def fix_head(): head.dxf.handle = entitydb.next_handle() entitydb.add(head) if log: auditor.fixed_error( code=AuditError.INVALID_TABLE_HANDLE, message=f"Fixed invalid table head handle in table {self.name}", ) # fix silently for older DXF versions log = auditor.doc.dxfversion > const.DXF12 head = self.head # Another exception for an invalid owner tag, but this usage is # covered in Auditor.check_owner_exist(): head.dxf.owner = "0" handle = head.dxf.handle entitydb = self.entitydb if handle is None or handle == "0": # Entity database does not assign new handle: fix_head() else: # Why: see duplicate handle issue #604 entry = self.entitydb.get(handle) if entry is not head: # another entity has the same handle! fix_head() # Just to be sure owner handle is valid in every circumstance: self.update_owner_handles()