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