Пример #1
0
 def __init__(self):
     self.doc: Optional["Drawing"] = None
     self.entries: Dict[str, T] = OrderedDict()
     self._head = TableHead()
Пример #2
0
 def _set_head(self, name: str, handle: str = None) -> None:
     self._head = TableHead.new(handle,
                                owner='0',
                                dxfattribs={'name': name},
                                doc=self.doc)
Пример #3
0
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()