header.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. # Purpose: manage header section
  2. # Created: 12.03.2011
  3. # Copyright (c) 2011-2018, Manfred Moitzi
  4. # License: MIT License
  5. from typing import TYPE_CHECKING, Iterable, List, Tuple, KeysView, Any, Iterator
  6. from collections import OrderedDict
  7. from ezdxf.lldxf.types import strtag
  8. from ezdxf.lldxf.tags import group_tags, Tags, DXFTag
  9. from ezdxf.lldxf.const import DXFStructureError, DXFValueError, DXFKeyError
  10. from ezdxf.lldxf.validator import header_validator
  11. from ezdxf.legacy.headervars import VARMAP as VARMAP_R12
  12. from ezdxf.modern.headervars import VARMAP as VARMAP_R13
  13. if TYPE_CHECKING:
  14. from ezdxf.eztypes import TagWriter
  15. MIN_HEADER_TEXT = """ 0
  16. SECTION
  17. 2
  18. HEADER
  19. 9
  20. $ACADVER
  21. 1
  22. AC1009
  23. 9
  24. $DWGCODEPAGE
  25. 3
  26. ANSI_1252
  27. 9
  28. $HANDSEED
  29. 5
  30. FF
  31. """
  32. class CustomVars:
  33. """
  34. Custom Properties are stored as string tuples ('CustomTag', 'CustomValue') in a list object.
  35. Multiple occurrence of the same 'CustomTag' is allowed, but not well supported by the interface.
  36. """
  37. def __init__(self):
  38. self.properties = list() # type: List[Tuple[str, str]]
  39. def __len__(self) -> int:
  40. return len(self.properties)
  41. def __iter__(self) -> Iterable[Tuple[str, str]]:
  42. return iter(self.properties)
  43. def clear(self) -> None:
  44. """ Remove all custom properties.
  45. """
  46. self.properties.clear()
  47. def append(self, tag: str, value: str) -> None:
  48. # custom properties always stored as strings
  49. self.properties.append((tag, str(value)))
  50. def get(self, tag: str, default: str = None):
  51. """ Get value of first occurrence of 'tag'.
  52. """
  53. for key, value in self.properties:
  54. if key == tag:
  55. return value
  56. else:
  57. return default
  58. def has_tag(self, tag: str) -> bool:
  59. return self.get(tag) is not None
  60. def remove(self, tag: str, all: bool = False) -> None:
  61. """ Remove first occurrence of 'tag', removes all occurrences if param all is True.
  62. """
  63. found_tag = False
  64. for item in self.properties:
  65. if item[0] == tag:
  66. self.properties.remove(item)
  67. found_tag = True
  68. if not all:
  69. return
  70. if not found_tag:
  71. raise DXFValueError("Tag '%s' does not exist" % tag)
  72. def replace(self, tag: str, value: str) -> None:
  73. """ Replaces the value of the first custom property `tag` by a new `value`.
  74. """
  75. properties = self.properties
  76. for index in range(len(properties)):
  77. name = properties[index][0]
  78. if name == tag:
  79. properties[index] = (name, value)
  80. return
  81. raise DXFValueError("Tag '%s' does not exist" % tag)
  82. def write(self, tagwriter: 'TagWriter') -> None:
  83. for tag, value in self.properties:
  84. s = " 9\n$CUSTOMPROPERTYTAG\n 1\n{0}\n 9\n$CUSTOMPROPERTY\n 1\n{1}\n".format(tag, value)
  85. tagwriter.write_str(s)
  86. class HeaderSection:
  87. MIN_HEADER_TAGS = Tags.from_text(MIN_HEADER_TEXT)
  88. name = 'HEADER'
  89. def __init__(self, tags: Tags = None):
  90. tags = tags or self.MIN_HEADER_TAGS
  91. self.hdrvars = OrderedDict()
  92. self.custom_vars = CustomVars()
  93. self._build(iter(tags))
  94. self._varmap = self._get_varmap()
  95. def _get_varmap(self) -> dict:
  96. dxfversion = self.get('$ACADVER', 'AC1009')
  97. if dxfversion > 'AC1009':
  98. return dict(VARMAP_R13)
  99. else:
  100. return dict(VARMAP_R12)
  101. def _headervar_factory(self, key: str, value: Any) -> DXFTag:
  102. if key in self._varmap:
  103. factory = self._varmap[key]
  104. return factory(value)
  105. else:
  106. raise DXFKeyError('Invalid header variable {}.'.format(key))
  107. def __len__(self) -> int:
  108. return len(self.hdrvars)
  109. def __contains__(self, key) -> bool:
  110. return key in self.hdrvars
  111. def _build(self, tags: Iterator[DXFTag]) -> None:
  112. section_tag = next(tags)
  113. name_tag = next(tags)
  114. if section_tag != (0, 'SECTION') or name_tag != (2, 'HEADER'):
  115. raise DXFStructureError("Critical structure error in HEADER section.")
  116. groups = group_tags(header_validator(tags), splitcode=9)
  117. custom_property_stack = [] # collect $CUSTOMPROPERTY/TAG
  118. for group in groups:
  119. name = group[0].value
  120. value = group[1]
  121. if name in ('$CUSTOMPROPERTYTAG', '$CUSTOMPROPERTY'):
  122. custom_property_stack.append(value.value)
  123. else:
  124. self.hdrvars[name] = HeaderVar(value)
  125. custom_property_stack.reverse()
  126. while len(custom_property_stack):
  127. try:
  128. self.custom_vars.append(tag=custom_property_stack.pop(), value=custom_property_stack.pop())
  129. except IndexError: # internal exception
  130. break
  131. def varnames(self) -> KeysView:
  132. return self.hdrvars.keys()
  133. def write(self, tagwriter: 'TagWriter') -> None:
  134. def _write(name: str, value: Any) -> None:
  135. tagwriter.write_tag2(9, name)
  136. tagwriter.write_str(str(value))
  137. if self.get('$ACADVER', 'AC1009') == 'AC1009' and self.get('$HANDLING', 1) == 0:
  138. write_handles = False
  139. else:
  140. write_handles = True
  141. tagwriter.write_str(" 0\nSECTION\n 2\nHEADER\n")
  142. for name, value in self.hdrvars.items():
  143. if not write_handles and name == '$HANDSEED':
  144. continue # skip $HANDSEED
  145. _write(name, value)
  146. if name == "$LASTSAVEDBY": # ugly hack, but necessary for AutoCAD
  147. self.custom_vars.write(tagwriter)
  148. tagwriter.write_str(" 0\nENDSEC\n")
  149. def __getitem__(self, key: str) -> Any:
  150. try:
  151. return self.hdrvars[key].value
  152. except KeyError: # map exception
  153. raise DXFKeyError(str(key))
  154. def get(self, key: str, default: Any = None) -> Any:
  155. if key in self.hdrvars:
  156. return self.__getitem__(key)
  157. else:
  158. return default
  159. def __setitem__(self, key: str, value: Any) -> None:
  160. try:
  161. tags = self._headervar_factory(key, value)
  162. except (IndexError, ValueError):
  163. raise DXFValueError(str(value))
  164. self.hdrvars[key] = HeaderVar(tags)
  165. def __delitem__(self, key: str) -> None:
  166. try:
  167. del self.hdrvars[key]
  168. except KeyError: # map exception
  169. raise DXFKeyError(str(key))
  170. class HeaderVar:
  171. def __init__(self, tag: DXFTag):
  172. self.tag = tag
  173. @property
  174. def code(self) -> int:
  175. return self.tag[0]
  176. @property
  177. def value(self) -> Any:
  178. return self.tag[1]
  179. @property
  180. def ispoint(self) -> bool:
  181. return self.code == 10
  182. def __str__(self) -> str:
  183. if self.ispoint:
  184. code, value = self.tag
  185. s = []
  186. for coord in value:
  187. s.append(strtag((code, coord)))
  188. code += 10
  189. return "".join(s)
  190. else:
  191. return strtag(self.tag)