1# Copyright (c) 2019-2020 Manfred Moitzi 2# License: MIT License 3from typing import TYPE_CHECKING, List, Iterable, Tuple 4from collections import OrderedDict 5from ezdxf.lldxf.types import dxftag 6from ezdxf.lldxf.tags import Tags 7from ezdxf.lldxf.const import XDATA_MARKER, DXFValueError 8from ezdxf.lldxf.tags import ( 9 xdata_list, remove_named_list_from_xdata, get_named_list_from_xdata, 10 NotFoundException, 11) 12from ezdxf import options 13from ezdxf.lldxf.repair import filter_invalid_xdata_group_codes 14import logging 15 16logger = logging.getLogger('ezdxf') 17 18if TYPE_CHECKING: 19 from ezdxf.eztypes import TagWriter 20 21__all__ = ['XData', 'EmbeddedObjects'] 22 23 24class XData: 25 def __init__(self, xdata: List[Tags] = None): 26 self.data = OrderedDict() 27 for data in (xdata or []): 28 self._add(data) 29 30 def __len__(self): 31 return len(self.data) 32 33 def __contains__(self, appid: str) -> bool: 34 return appid in self.data 35 36 def _add(self, tags: Tags) -> None: 37 tags = Tags(tags) 38 if len(tags): 39 appid = tags[0].value 40 if appid in self.data: 41 logger.info(f'Duplicate XDATA appid {appid} in one entity') 42 self.data[appid] = tags 43 44 def add(self, appid: str, tags: Iterable) -> None: 45 data = Tags(dxftag(code, value) for code, value in tags) 46 if len(data) == 0 or data[0] != (XDATA_MARKER, appid): 47 data.insert(0, dxftag(XDATA_MARKER, appid)) 48 self._add(data) 49 50 def get(self, appid: str) -> Tags: 51 if appid in self.data: 52 return self.data[appid] 53 else: 54 raise DXFValueError(appid) 55 56 def discard(self, appid): 57 if appid in self.data: 58 del self.data[appid] 59 60 def export_dxf(self, tagwriter: 'TagWriter') -> None: 61 for appid, tags in self.data.items(): 62 if options.filter_invalid_xdata_group_codes: 63 tags = list(filter_invalid_xdata_group_codes(tags)) 64 tagwriter.write_tags(tags) 65 66 def has_xlist(self, appid: str, name: str) -> bool: 67 """ Returns True if list `name` from XDATA `appid` exists. 68 69 Args: 70 appid: APPID 71 name: list name 72 73 """ 74 try: 75 self.get_xlist(appid, name) 76 except DXFValueError: 77 return False 78 else: 79 return True 80 81 def get_xlist(self, appid: str, name: str) -> List[Tuple]: 82 """ Get list `name` from XDATA `appid`. 83 84 Args: 85 appid: APPID 86 name: list name 87 88 Returns: list of DXFTags including list name and curly braces '{' '}' tags 89 90 Raises: 91 DXFKeyError: XDATA `appid` does not exist 92 DXFValueError: list `name` does not exist 93 94 """ 95 xdata = self.get(appid) 96 try: 97 return get_named_list_from_xdata(name, xdata) 98 except NotFoundException: 99 raise DXFValueError( 100 f'No data list "{name}" not found for APPID "{appid}"') 101 102 def set_xlist(self, appid: str, name: str, tags: Iterable) -> None: 103 """ Create new list `name` of XDATA `appid` with `xdata_tags` and 104 replaces list `name` if already exists. 105 106 Args: 107 appid: APPID 108 name: list name 109 tags: list content as DXFTags or (code, value) tuples, list name and 110 curly braces '{' '}' tags will be added 111 """ 112 if appid not in self.data: 113 data = [(XDATA_MARKER, appid)] 114 data.extend(xdata_list(name, tags)) 115 self.add(appid, data) 116 else: 117 self.replace_xlist(appid, name, tags) 118 119 def discard_xlist(self, appid: str, name: str) -> None: 120 """ Deletes list `name` from XDATA `appid`. Ignores silently if XDATA 121 `appid` or list `name` not exist. 122 123 Args: 124 appid: APPID 125 name: list name 126 127 """ 128 try: 129 xdata = self.get(appid) 130 except DXFValueError: 131 pass 132 else: 133 try: 134 tags = remove_named_list_from_xdata(name, xdata) 135 except NotFoundException: 136 pass 137 else: 138 self.add(appid, tags) 139 140 def replace_xlist(self, appid: str, name: str, tags: Iterable) -> None: 141 """ Replaces list `name` of existing XDATA `appid` by `tags`. Appends 142 new list if list `name` do not exist, but raises `DXFValueError` if 143 XDATA `appid` do not exist. 144 145 Low level interface, if not sure use `set_xdata_list()` instead. 146 147 Args: 148 appid: APPID 149 name: list name 150 tags: list content as DXFTags or (code, value) tuples, list name and 151 curly braces '{' '}' tags will be added 152 Raises: 153 DXFValueError: XDATA `appid` do not exist 154 155 """ 156 xdata = self.get(appid) 157 try: 158 data = remove_named_list_from_xdata(name, xdata) 159 except NotFoundException: 160 data = xdata 161 xlist = xdata_list(name, tags) 162 data.extend(xlist) 163 self.add(appid, data) 164 165 166class EmbeddedObjects: # TODO: remove 167 """ Introduced in DXF R2018. """ 168 169 def __init__(self, embedded_objects: List[Tags]): 170 self.embedded_objects = embedded_objects 171 172 def export_dxf(self, tagwriter: 'TagWriter') -> None: 173 for tags in self.embedded_objects: 174 tagwriter.write_tags(tags) 175