source: orange/Orange/OrangeCanvas/scheme/readwrite.py @ 11112:ce1574cdd4fd

Revision 11112:ce1574cdd4fd, 9.7 KB checked in by Ales Erjavec <ales.erjavec@…>, 18 months ago (diff)

Fixed scheme annotation serialization, added tests.

Line 
1"""
2Scheme save/load routines.
3"""
4
5from xml.etree.ElementTree import TreeBuilder, Element, ElementTree
6from collections import defaultdict
7
8import cPickle
9
10try:
11    from ast import literal_eval
12except ImportError:
13    literal_eval = eval
14
15import logging
16
17from . import SchemeNode
18from . import SchemeLink
19from .annotations import SchemeTextAnnotation, SchemeArrowAnnotation
20
21from .. import registry
22
23log = logging.getLogger(__name__)
24
25
26def parse_scheme(scheme, stream):
27    """Parse saved scheme string
28    """
29    from xml.etree.ElementTree import parse
30    doc = parse(stream)
31    scheme_el = doc.getroot()
32    version = scheme_el.attrib.get("version", None)
33    if version is None:
34        if scheme_el.find("widgets") is not None:
35            version = "1.0"
36        else:
37            version = "2.0"
38
39    if version == "1.0":
40        parse_scheme_v_1_0(doc, scheme)
41        return scheme
42    else:
43        parse_scheme_v_2_0(doc, scheme)
44        return scheme
45
46
47def scheme_node_from_element(node_el, registry):
48    """Create a SchemeNode from an Element instance.
49    """
50    widget_desc = registry.widget(node_el.get("qualified_name"))
51    title = node_el.get("title")
52    pos = node_el.get("position")
53
54    if pos is not None:
55        pos = literal_eval(pos)
56
57    return SchemeNode(widget_desc, title=title, position=pos)
58
59
60def parse_scheme_v_2_0(etree, scheme, widget_registry=None):
61    """Parse an ElementTree Instance.
62    """
63    if widget_registry is None:
64        widget_registry = registry.global_registry()
65
66    nodes = []
67    links = []
68
69    id_to_node = {}
70
71    scheme_node = etree.getroot()
72    scheme.title = scheme_node.attrib.get("title", "")
73    scheme.description = scheme_node.attrib.get("description", "")
74
75    # Load and create scheme nodes.
76    for node_el in etree.findall("nodes/node"):
77        # TODO: handle errors.
78        try:
79            node = scheme_node_from_element(node_el, widget_registry)
80        except Exception:
81            raise
82
83        nodes.append(node)
84        id_to_node[node_el.get("id")] = node
85
86    # Load and create scheme links.
87    for link_el in etree.findall("links/link"):
88        source = id_to_node[link_el.get("source_node_id")]
89        sink = id_to_node[link_el.get("sink_node_id")]
90        source_channel = link_el.get("source_channel")
91        sink_channel = link_el.get("sink_channel")
92        enabled = link_el.get("enabled") == "true"
93        link = SchemeLink(source, source_channel, sink, sink_channel,
94                          enabled=enabled)
95        links.append(link)
96
97    # Load node properties
98    for property_el in etree.findall("node_properties/properties"):
99        print "Loading node properties", property_el.attrib, property_el.text
100        node_id = property_el.attrib.get("node_id")
101        node = id_to_node[node_id]
102        data = property_el.text
103        properties = None
104        try:
105            properties = cPickle.loads(data)
106        except Exception:
107            log.error("Could not load properties for %r.", node.title,
108                      exc_info=True)
109        if properties is not None:
110            node.properties = properties
111
112    annotations = []
113    for annot_el in etree.findall("annotations/*"):
114        if annot_el.tag == "text":
115            rect = annot_el.attrib.get("rect", "(0, 0, 20, 20)")
116            rect = literal_eval(rect)
117            annot = SchemeTextAnnotation(rect, annot_el.text or "")
118        elif annot_el.tag == "arrow":
119            start = annot_el.attrib.get("start", "(0, 0)")
120            end = annot_el.attrib.get("end", "(0, 0)")
121            start, end = map(literal_eval, (start, end))
122            annot = SchemeArrowAnnotation(start, end)
123        annotations.append(annot)
124
125    for node in nodes:
126        scheme.add_node(node)
127
128    for link in links:
129        scheme.add_link(link)
130
131    for annot in annotations:
132        scheme.add_annotation(annot)
133
134
135def parse_scheme_v_1_0(etree, scheme, widget_registry=None):
136    """ElementTree Instance of an old .ows scheme format.
137    """
138    if widget_registry is None:
139        widget_registry = registry.global_registry()
140
141    widgets = widget_registry.widgets()
142    widgets_by_name = [(d.qualified_name.rsplit(".", 1)[-1], d)
143                       for d in widgets]
144    widgets_by_name = dict(widgets_by_name)
145
146    nodes_by_caption = {}
147    nodes = []
148    links = []
149    for widget_el in etree.findall("widgets/widget"):
150        caption = widget_el.get("caption")
151        name = widget_el.get("widgetName")
152        x_pos = widget_el.get("xPos")
153        y_pos = widget_el.get("yPos")
154        desc = widgets_by_name[name]
155        node = SchemeNode(desc, title=caption,
156                          position=(int(x_pos), int(y_pos)))
157        nodes_by_caption[caption] = node
158        nodes.append(node)
159#        scheme.add_node(node)
160
161    for channel_el in etree.findall("channels/channel"):
162        in_caption = channel_el.get("inWidgetCaption")
163        out_caption = channel_el.get("outWidgetCaption")
164        source = nodes_by_caption[out_caption]
165        sink = nodes_by_caption[in_caption]
166        enabled = channel_el.get("enabled") == "1"
167        signals = eval(channel_el.get("signals"))
168        for source_channel, sink_channel in signals:
169            link = SchemeLink(source, source_channel, sink, sink_channel,
170                              enabled=enabled)
171            links.append(link)
172#            scheme.add_link(link)
173
174    settings = etree.find("settings")
175    properties = {}
176    if settings is not None:
177        data = settings.attrib.get("settingsDictionary", None)
178        if data:
179            try:
180                properties = literal_eval(data)
181            except Exception:
182                log.error("Could not load properties for the scheme.",
183                          exc_info=True)
184
185    for node in nodes:
186        if node.title in properties:
187            try:
188                node.properties = cPickle.loads(properties[node.title])
189            except Exception:
190                log.error("Could not unpickle properties for the node %r.",
191                          node.title, exc_info=True)
192
193        scheme.add_node(node)
194
195    for link in links:
196        scheme.add_link(link)
197
198
199def inf_range(start=0, step=1):
200    """Return an infinite range iterator.
201    """
202    while True:
203        yield start
204        start += step
205
206
207def scheme_to_ows_stream(scheme, stream):
208    """Write scheme to a a stream in Orange Scheme .ows format
209    """
210    builder = TreeBuilder(element_factory=Element)
211    builder.start("scheme", {"version": "2.0",
212                             "title": scheme.title or "",
213                             "description": scheme.description or ""})
214
215    ## Nodes
216    node_ids = defaultdict(inf_range().next)
217    builder.start("nodes", {})
218    for node in scheme.nodes:
219        desc = node.description
220        attrs = {"id": str(node_ids[node]),
221                 "name": desc.name,
222                 "qualified_name": desc.qualified_name,
223                 "project_name": desc.project_name or "",
224                 "version": desc.version or "",
225                 "title": node.title,
226                 }
227        if node.position is not None:
228            attrs["position"] = str(node.position)
229
230        if type(node) is not SchemeNode:
231            attrs["scheme_node_type"] = "%s.%s" % (type(node).__name__,
232                                                   type(node).__module__)
233        builder.start("node", attrs)
234        builder.end("node")
235
236    builder.end("nodes")
237
238    ## Links
239    link_ids = defaultdict(inf_range().next)
240    builder.start("links", {})
241    for link in scheme.links:
242        source = link.source_node
243        sink = link.sink_node
244        source_id = node_ids[source]
245        sink_id = node_ids[sink]
246        attrs = {"id": str(link_ids[link]),
247                 "source_node_id": str(source_id),
248                 "sink_node_id": str(sink_id),
249                 "source_channel": link.source_channel.name,
250                 "sink_channel": link.sink_channel.name,
251                 "enabled": "true" if link.enabled else "false",
252                 }
253        builder.start("link", attrs)
254        builder.end("link")
255
256    builder.end("links")
257
258    ## Annotations
259    annotation_ids = defaultdict(inf_range().next)
260    builder.start("annotations", {})
261    for annotation in scheme.annotations:
262        annot_id = annotation_ids[annotation]
263        attrs = {"id": str(annot_id)}
264        data = None
265        if isinstance(annotation, SchemeTextAnnotation):
266            tag = "text"
267            attrs.update({"rect": repr(annotation.rect)})
268            data = annotation.text
269
270        elif isinstance(annotation, SchemeArrowAnnotation):
271            tag = "arrow"
272            attrs.update({"start": repr(annotation.start_pos),
273                          "end": repr(annotation.end_pos)})
274            data = None
275        else:
276            log.warning("Can't save %r", annotation)
277            continue
278        builder.start(tag, attrs)
279        if data is not None:
280            builder.data(data)
281        builder.end(tag)
282
283    builder.end("annotations")
284
285    builder.start("thumbnail", {})
286    builder.end("thumbnail")
287
288    # Node properties/settings
289    builder.start("node_properties", {})
290    for node in scheme.nodes:
291        data = None
292        if node.properties:
293            try:
294                data = cPickle.dumps(node.properties)
295            except Exception:
296                log.error("Error serializing properties for node %r",
297                          node.title, exc_info=True)
298            if data is not None:
299                builder.start("properties",
300                              {"node_id": str(node_ids[node]),
301                               "format": "pickle"})
302                builder.data(data)
303                builder.end("properties")
304
305    builder.end("node_properties")
306    builder.end("scheme")
307    root = builder.close()
308    tree = ElementTree(root)
309
310    tree.write(stream, encoding="utf-8")
Note: See TracBrowser for help on using the repository browser.