1# Copyright (c) 2020, Manfred Moitzi 2# License: MIT License 3from typing import Tuple, List, Iterable 4from collections import namedtuple 5from .const import DXFStructureError 6from ezdxf.tools.codepage import toencoding 7 8IndexEntry = namedtuple('IndexEntry', field_names='code value location line') 9 10 11class FileStructure: 12 """ 13 DXF file structure representation stored as file locations. 14 15 Store all DXF structure tags and some other tags as :class:`IndexEntry` tuples: 16 17 - code: group code 18 - value: tag value as string 19 - location: file location as int 20 - line: line number as int 21 22 Indexed tags: 23 24 - structure tags, every tag with group code 0 25 - section names, (2, name) tag following a (0, SECTION) tag 26 - entity handle tags with group code 5, the DIMSTYLE handle group code 105 27 is also stored as group code 5 28 29 """ 30 31 def __init__(self, filename: str): 32 # stores the file system name of the DXF document. 33 self.filename = filename 34 # DXF version if header variable $ACADVER is present, default is DXFR12 35 self.version = 'AC1009' 36 # Python encoding required to read the DXF document as text file. 37 self.encoding = 'cp1252' 38 self.index: List[IndexEntry] = [] 39 40 def print(self): 41 print(f'Filename: {self.filename}') 42 print(f'DXF Version: {self.version}') 43 print(f'encoding: {self.encoding}') 44 for entry in self.index: 45 print(f'Line: {entry.line} - ({entry.code}, {entry.value})') 46 47 def get(self, code: int, value: str, start: int = 0) -> int: 48 """ Returns index of first entry matching `code` and `value`. """ 49 self_index = self.index 50 index = start 51 count = len(self_index) 52 while index < count: 53 entry = self_index[index] 54 if entry.code == code and entry.value == value: 55 return index 56 index += 1 57 raise ValueError(f'No entry for tag ({code}, {value}) found.') 58 59 def fetchall(self, code: int, value: str, start: int = 0) -> Iterable[IndexEntry]: 60 """ Iterate over all specified entities. 61 62 e.g. fetchall(0, 'LINE') returns an iterator for all LINE entities. 63 64 """ 65 for entry in self.index[start:]: 66 if entry.code == code and entry.value == value: 67 yield entry 68 69 70def load(filename: str) -> FileStructure: 71 """ 72 Load DXF file structure for file `filename`, the file has to be seekable. 73 74 Args: 75 filename: file system file name 76 77 Raises: 78 DXFStructureError: Invalid or incomplete DXF file. 79 80 """ 81 file_structure = FileStructure(filename) 82 file = open(filename, mode='rb') 83 line: int = 1 84 eof = False 85 header = False 86 index: List[IndexEntry] = [] 87 prev_code: int = -1 88 prev_value: bytes = b'' 89 structure = None # the actual structure tag: 'SECTION', 'LINE', ... 90 91 def load_tag() -> Tuple[int, bytes]: 92 nonlocal line 93 try: 94 code = int(file.readline()) 95 except ValueError: 96 raise DXFStructureError(f'Invalid group code in line {line}') 97 98 if code < 0 or code > 1071: 99 raise DXFStructureError(f'Invalid group code {code} in line {line}') 100 value = file.readline().rstrip(b'\r\n') 101 line += 2 102 return code, value 103 104 def load_header_var() -> str: 105 _, value = load_tag() 106 return value.decode() 107 108 while not eof: 109 location = file.tell() 110 tag_line = line 111 try: 112 code, value = load_tag() 113 if header and code == 9: 114 if value == b'$ACADVER': 115 file_structure.version = load_header_var() 116 elif value == b'$DWGCODEPAGE': 117 file_structure.encoding = toencoding(load_header_var()) 118 continue 119 except IOError: 120 break 121 122 if code == 0: 123 # All structure tags have group code == 0, store file location 124 structure = value 125 index.append(IndexEntry(0, value.decode(), location, tag_line)) 126 eof = (value == b'EOF') 127 128 elif code == 2 and prev_code == 0 and prev_value == b'SECTION': 129 # Section name is the tag (2, name) following the (0, SECTION) tag. 130 header = (value == b'HEADER') 131 index.append(IndexEntry(2, value.decode(), location, tag_line)) 132 133 elif code == 5 and structure != b'DIMSTYLE': 134 # Entity handles have always group code 5. 135 index.append(IndexEntry(5, value.decode(), location, tag_line)) 136 137 elif code == 105 and structure == b'DIMSTYLE': 138 # Except the DIMSTYLE table entry has group code 105. 139 index.append(IndexEntry(5, value.decode(), location, tag_line)) 140 141 prev_code = code 142 prev_value = value 143 144 file.close() 145 if not eof: 146 raise DXFStructureError(f'Unexpected end of file.') 147 148 if file_structure.version >= 'AC1021': # R2007 and later 149 file_structure.encoding = 'utf-8' 150 file_structure.index = index 151 return file_structure 152