# Purpose: manage header section # Created: 12.03.2011 # Copyright (c) 2011-2018, Manfred Moitzi # License: MIT License from typing import TYPE_CHECKING, Iterable, List, Tuple, KeysView, Any, Iterator from collections import OrderedDict from ezdxf.lldxf.types import strtag from ezdxf.lldxf.tags import group_tags, Tags, DXFTag from ezdxf.lldxf.const import DXFStructureError, DXFValueError, DXFKeyError from ezdxf.lldxf.validator import header_validator from ezdxf.legacy.headervars import VARMAP as VARMAP_R12 from ezdxf.modern.headervars import VARMAP as VARMAP_R13 if TYPE_CHECKING: from ezdxf.eztypes import TagWriter MIN_HEADER_TEXT = """ 0 SECTION 2 HEADER 9 $ACADVER 1 AC1009 9 $DWGCODEPAGE 3 ANSI_1252 9 $HANDSEED 5 FF """ class CustomVars: """ Custom Properties are stored as string tuples ('CustomTag', 'CustomValue') in a list object. Multiple occurrence of the same 'CustomTag' is allowed, but not well supported by the interface. """ def __init__(self): self.properties = list() # type: List[Tuple[str, str]] def __len__(self) -> int: return len(self.properties) def __iter__(self) -> Iterable[Tuple[str, str]]: return iter(self.properties) def clear(self) -> None: """ Remove all custom properties. """ self.properties.clear() def append(self, tag: str, value: str) -> None: # custom properties always stored as strings self.properties.append((tag, str(value))) def get(self, tag: str, default: str = None): """ Get value of first occurrence of 'tag'. """ for key, value in self.properties: if key == tag: return value else: return default def has_tag(self, tag: str) -> bool: return self.get(tag) is not None def remove(self, tag: str, all: bool = False) -> None: """ Remove first occurrence of 'tag', removes all occurrences if param all is True. """ found_tag = False for item in self.properties: if item[0] == tag: self.properties.remove(item) found_tag = True if not all: return if not found_tag: raise DXFValueError("Tag '%s' does not exist" % tag) def replace(self, tag: str, value: str) -> None: """ Replaces the value of the first custom property `tag` by a new `value`. """ properties = self.properties for index in range(len(properties)): name = properties[index][0] if name == tag: properties[index] = (name, value) return raise DXFValueError("Tag '%s' does not exist" % tag) def write(self, tagwriter: 'TagWriter') -> None: for tag, value in self.properties: s = " 9\n$CUSTOMPROPERTYTAG\n 1\n{0}\n 9\n$CUSTOMPROPERTY\n 1\n{1}\n".format(tag, value) tagwriter.write_str(s) class HeaderSection: MIN_HEADER_TAGS = Tags.from_text(MIN_HEADER_TEXT) name = 'HEADER' def __init__(self, tags: Tags = None): tags = tags or self.MIN_HEADER_TAGS self.hdrvars = OrderedDict() self.custom_vars = CustomVars() self._build(iter(tags)) self._varmap = self._get_varmap() def _get_varmap(self) -> dict: dxfversion = self.get('$ACADVER', 'AC1009') if dxfversion > 'AC1009': return dict(VARMAP_R13) else: return dict(VARMAP_R12) def _headervar_factory(self, key: str, value: Any) -> DXFTag: if key in self._varmap: factory = self._varmap[key] return factory(value) else: raise DXFKeyError('Invalid header variable {}.'.format(key)) def __len__(self) -> int: return len(self.hdrvars) def __contains__(self, key) -> bool: return key in self.hdrvars def _build(self, tags: Iterator[DXFTag]) -> None: section_tag = next(tags) name_tag = next(tags) if section_tag != (0, 'SECTION') or name_tag != (2, 'HEADER'): raise DXFStructureError("Critical structure error in HEADER section.") groups = group_tags(header_validator(tags), splitcode=9) custom_property_stack = [] # collect $CUSTOMPROPERTY/TAG for group in groups: name = group[0].value value = group[1] if name in ('$CUSTOMPROPERTYTAG', '$CUSTOMPROPERTY'): custom_property_stack.append(value.value) else: self.hdrvars[name] = HeaderVar(value) custom_property_stack.reverse() while len(custom_property_stack): try: self.custom_vars.append(tag=custom_property_stack.pop(), value=custom_property_stack.pop()) except IndexError: # internal exception break def varnames(self) -> KeysView: return self.hdrvars.keys() def write(self, tagwriter: 'TagWriter') -> None: def _write(name: str, value: Any) -> None: tagwriter.write_tag2(9, name) tagwriter.write_str(str(value)) if self.get('$ACADVER', 'AC1009') == 'AC1009' and self.get('$HANDLING', 1) == 0: write_handles = False else: write_handles = True tagwriter.write_str(" 0\nSECTION\n 2\nHEADER\n") for name, value in self.hdrvars.items(): if not write_handles and name == '$HANDSEED': continue # skip $HANDSEED _write(name, value) if name == "$LASTSAVEDBY": # ugly hack, but necessary for AutoCAD self.custom_vars.write(tagwriter) tagwriter.write_str(" 0\nENDSEC\n") def __getitem__(self, key: str) -> Any: try: return self.hdrvars[key].value except KeyError: # map exception raise DXFKeyError(str(key)) def get(self, key: str, default: Any = None) -> Any: if key in self.hdrvars: return self.__getitem__(key) else: return default def __setitem__(self, key: str, value: Any) -> None: try: tags = self._headervar_factory(key, value) except (IndexError, ValueError): raise DXFValueError(str(value)) self.hdrvars[key] = HeaderVar(tags) def __delitem__(self, key: str) -> None: try: del self.hdrvars[key] except KeyError: # map exception raise DXFKeyError(str(key)) class HeaderVar: def __init__(self, tag: DXFTag): self.tag = tag @property def code(self) -> int: return self.tag[0] @property def value(self) -> Any: return self.tag[1] @property def ispoint(self) -> bool: return self.code == 10 def __str__(self) -> str: if self.ispoint: code, value = self.tag s = [] for coord in value: s.append(strtag((code, coord))) code += 10 return "".join(s) else: return strtag(self.tag)