source: orange/Orange/OrangeCanvas/scheme/readwrite.py @ 11210:bb3860029714

Revision 11210:bb3860029714, 11.7 KB checked in by Ales Erjavec <ales.erjavec@…>, 17 months ago (diff)

Fixed some errors caught by failing uniitests.

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