source: orange/Orange/OrangeCanvas/scheme/readwrite.py @ 11202:dba4e6f2678e

Revision 11202:dba4e6f2678e, 10.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 17 months ago (diff)

Added annotation font and color serialization.

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