159 lines
5.1 KiB
Python
159 lines
5.1 KiB
Python
# Copyright (c) 2020-2022, Manfred Moitzi
|
|
# License: MIT License
|
|
from __future__ import annotations
|
|
from typing import Iterable, NamedTuple, BinaryIO
|
|
|
|
from .const import DXFStructureError
|
|
from ezdxf.tools.codepage import toencoding
|
|
|
|
|
|
class IndexEntry(NamedTuple):
|
|
code: int
|
|
value: str
|
|
location: int
|
|
line: int
|
|
|
|
|
|
class FileStructure:
|
|
"""DXF file structure representation stored as file locations.
|
|
|
|
Store all DXF structure tags and some other tags as :class:`IndexEntry`
|
|
tuples:
|
|
|
|
- code: group code
|
|
- value: tag value as string
|
|
- location: file location as int
|
|
- line: line number as int
|
|
|
|
Indexed tags:
|
|
|
|
- structure tags, every tag with group code 0
|
|
- section names, (2, name) tag following a (0, SECTION) tag
|
|
- entity handle tags with group code 5, the DIMSTYLE handle group code
|
|
105 is also stored as group code 5
|
|
|
|
"""
|
|
|
|
def __init__(self, filename: str):
|
|
# stores the file system name of the DXF document.
|
|
self.filename: str = filename
|
|
# DXF version if header variable $ACADVER is present, default is DXFR12
|
|
self.version: str = "AC1009"
|
|
# Python encoding required to read the DXF document as text file.
|
|
self.encoding: str = "cp1252"
|
|
self.index: list[IndexEntry] = []
|
|
|
|
def print(self) -> None:
|
|
print(f"Filename: {self.filename}")
|
|
print(f"DXF Version: {self.version}")
|
|
print(f"encoding: {self.encoding}")
|
|
for entry in self.index:
|
|
print(f"Line: {entry.line} - ({entry.code}, {entry.value})")
|
|
|
|
def get(self, code: int, value: str, start: int = 0) -> int:
|
|
"""Returns index of first entry matching `code` and `value`."""
|
|
self_index = self.index
|
|
index: int = start
|
|
count: int = len(self_index)
|
|
while index < count:
|
|
entry = self_index[index]
|
|
if entry.code == code and entry.value == value:
|
|
return index
|
|
index += 1
|
|
raise ValueError(f"No entry for tag ({code}, {value}) found.")
|
|
|
|
def fetchall(
|
|
self, code: int, value: str, start: int = 0
|
|
) -> Iterable[IndexEntry]:
|
|
"""Iterate over all specified entities.
|
|
|
|
e.g. fetchall(0, 'LINE') returns an iterator for all LINE entities.
|
|
|
|
"""
|
|
for entry in self.index[start:]:
|
|
if entry.code == code and entry.value == value:
|
|
yield entry
|
|
|
|
|
|
def load(filename: str) -> FileStructure:
|
|
"""Load DXF file structure for file `filename`, the file has to be seekable.
|
|
|
|
Args:
|
|
filename: file system file name
|
|
|
|
Raises:
|
|
DXFStructureError: Invalid or incomplete DXF file.
|
|
|
|
"""
|
|
file_structure = FileStructure(filename)
|
|
file: BinaryIO = open(filename, mode="rb")
|
|
line: int = 1
|
|
eof: bool = False
|
|
header: bool = False
|
|
index: list[IndexEntry] = []
|
|
prev_code: int = -1
|
|
prev_value: bytes = b""
|
|
structure = None # the current structure tag: 'SECTION', 'LINE', ...
|
|
|
|
def load_tag() -> tuple[int, bytes]:
|
|
nonlocal line
|
|
try:
|
|
code = int(file.readline())
|
|
except ValueError:
|
|
raise DXFStructureError(f"Invalid group code in line {line}")
|
|
|
|
if code < 0 or code > 1071:
|
|
raise DXFStructureError(f"Invalid group code {code} in line {line}")
|
|
value = file.readline().rstrip(b"\r\n")
|
|
line += 2
|
|
return code, value
|
|
|
|
def load_header_var() -> str:
|
|
_, value = load_tag()
|
|
return value.decode()
|
|
|
|
while not eof:
|
|
location = file.tell()
|
|
tag_line = line
|
|
try:
|
|
code, value = load_tag()
|
|
if header and code == 9:
|
|
if value == b"$ACADVER":
|
|
file_structure.version = load_header_var()
|
|
elif value == b"$DWGCODEPAGE":
|
|
file_structure.encoding = toencoding(load_header_var())
|
|
continue
|
|
except IOError:
|
|
break
|
|
|
|
if code == 0:
|
|
# All structure tags have group code == 0, store file location
|
|
structure = value
|
|
index.append(IndexEntry(0, value.decode(), location, tag_line))
|
|
eof = value == b"EOF"
|
|
|
|
elif code == 2 and prev_code == 0 and prev_value == b"SECTION":
|
|
# Section name is the tag (2, name) following the (0, SECTION) tag.
|
|
header = value == b"HEADER"
|
|
index.append(IndexEntry(2, value.decode(), location, tag_line))
|
|
|
|
elif code == 5 and structure != b"DIMSTYLE":
|
|
# Entity handles have always group code 5.
|
|
index.append(IndexEntry(5, value.decode(), location, tag_line))
|
|
|
|
elif code == 105 and structure == b"DIMSTYLE":
|
|
# Except the DIMSTYLE table entry has group code 105.
|
|
index.append(IndexEntry(5, value.decode(), location, tag_line))
|
|
|
|
prev_code = code
|
|
prev_value = value
|
|
|
|
file.close()
|
|
if not eof:
|
|
raise DXFStructureError(f"Unexpected end of file.")
|
|
|
|
if file_structure.version >= "AC1021": # R2007 and later
|
|
file_structure.encoding = "utf-8"
|
|
file_structure.index = index
|
|
return file_structure
|