Changeset 11683:7b9dcf8abcc4 in orange


Ignore:
Timestamp:
09/03/13 14:53:58 (8 months ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
Message:

Refactored scheme parsing.

Using widget description's "replaces" list to resolve widgets.

Location:
Orange/OrangeCanvas
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • Orange/OrangeCanvas/application/canvasmain.py

    r11598 r11683  
    5151 
    5252from ..scheme import widgetsscheme 
    53 from ..scheme.readwrite import parse_scheme, sniff_version 
     53from ..scheme.readwrite import scheme_load, sniff_version 
    5454 
    5555from . import welcomedialog 
     
    952952        errors = [] 
    953953        try: 
    954             parse_scheme(new_scheme, open(filename, "rb"), 
    955                          error_handler=errors.append, 
    956                          allow_pickle_data=True) 
     954            scheme_load(new_scheme, open(filename, "rb"), 
     955                        error_handler=errors.append) 
     956 
    957957        except Exception: 
    958958            message_critical( 
  • Orange/OrangeCanvas/preview/scanner.py

    r11303 r11683  
    88from xml.sax import make_parser, handler, saxutils, SAXParseException 
    99 
    10 from ..scheme.readwrite import parse_scheme 
     10from ..scheme.readwrite import scheme_load 
    1111log = logging.getLogger(__name__) 
    1212 
     
    7979    scheme = scheme.Scheme() 
    8080    errors = [] 
    81     parse_scheme(scheme, scheme_file, error_handler=errors.append) 
     81    scheme_load(scheme, scheme_file, error_handler=errors.append) 
    8282 
    8383    tmp_scene = scene.CanvasScene() 
  • Orange/OrangeCanvas/scheme/readwrite.py

    r11391 r11683  
    44""" 
    55import sys 
     6import warnings 
    67 
    78from xml.etree.ElementTree import TreeBuilder, Element, ElementTree, parse 
    89 
    9 from collections import defaultdict 
    10 from itertools import chain 
     10from collections import defaultdict, namedtuple 
     11from itertools import chain, count 
    1112 
    1213import cPickle as pickle 
     
    2324from .errors import IncompatibleChannelTypeError 
    2425 
    25 from .. import registry 
     26from ..registry import global_registry 
    2627 
    2728log = logging.getLogger(__name__) 
     
    139140 
    140141    """ 
     142    warnings.warn("Use 'scheme_load' instead", DeprecationWarning, 
     143                  stacklevel=2) 
     144 
    141145    doc = parse(stream) 
    142146    scheme_el = doc.getroot() 
     
    187191    """ 
    188192    if widget_registry is None: 
    189         widget_registry = registry.global_registry() 
     193        widget_registry = global_registry() 
    190194 
    191195    nodes_not_found = [] 
     
    311315    """ 
    312316    if widget_registry is None: 
    313         widget_registry = registry.global_registry() 
     317        widget_registry = global_registry() 
    314318 
    315319    widgets_not_found = [] 
     
    386390    for link in links: 
    387391        scheme.add_link(link) 
     392 
     393 
     394# Intermediate scheme representation 
     395_scheme = namedtuple( 
     396    "_scheme", 
     397    ["title", "version", "description", "nodes", "links", "annotations"]) 
     398 
     399_node = namedtuple( 
     400    "_node", 
     401    ["id", "title", "name", "position", "project_name", "qualified_name", 
     402     "version", "data"]) 
     403 
     404_data = namedtuple( 
     405    "_data", 
     406    ["format", "data"]) 
     407 
     408_link = namedtuple( 
     409    "_link", 
     410    ["id", "source_node_id", "sink_node_id", "source_channel", "sink_channel", 
     411     "enabled"]) 
     412 
     413_annotation = namedtuple( 
     414    "_annotation", 
     415    ["id", "type", "params"]) 
     416 
     417_text_params = namedtuple( 
     418    "_text_params", 
     419    ["geometry", "text", "font"]) 
     420 
     421_arrow_params = namedtuple( 
     422    "_arrow_params", 
     423    ["geometry", "color"]) 
     424 
     425 
     426def parse_ows_etree_v_2_0(tree): 
     427    scheme = tree.getroot() 
     428    nodes, links, annotations = [], [], [] 
     429 
     430    # First collect all properties 
     431    properties = {} 
     432    for property in tree.findall("node_properties/properties"): 
     433        node_id = property.get("node_id") 
     434        format = property.get("format") 
     435        if "data" in property.attrib: 
     436            data = property.get("data") 
     437        else: 
     438            data = property.text 
     439        properties[node_id] = _data(format, data) 
     440 
     441    # Collect all nodes 
     442    for node in tree.findall("nodes/node"): 
     443        node_id = node.get("id") 
     444        node = _node( 
     445            id=node_id, 
     446            title=node.get("title"), 
     447            name=node.get("name"), 
     448            position=tuple_eval(node.get("position")), 
     449            project_name=node.get("project_name"), 
     450            qualified_name=node.get("qualified_name"), 
     451            version=node.get("version", ""), 
     452            data=properties.get(node_id, None) 
     453        ) 
     454        nodes.append(node) 
     455 
     456    for link in tree.findall("links/link"): 
     457        params = _link( 
     458            id=link.get("id"), 
     459            source_node_id=link.get("source_node_id"), 
     460            sink_node_id=link.get("sink_node_id"), 
     461            source_channel=link.get("source_channel"), 
     462            sink_channel=link.get("sink_channel"), 
     463            enabled=link.get("enabled") == "true", 
     464        ) 
     465        links.append(params) 
     466 
     467    for annot in tree.findall("annotations/*"): 
     468        if annot.tag == "text": 
     469            rect = tuple_eval(annot.get("rect", "(0.0, 0.0, 20.0, 20.0)")) 
     470 
     471            font_family = annot.get("font-family", "").strip() 
     472            font_size = annot.get("font-size", "").strip() 
     473 
     474            font = {} 
     475            if font_family: 
     476                font["family"] = font_family 
     477            if font_size: 
     478                font["size"] = int(font_size) 
     479 
     480            annotation = _annotation( 
     481                id=annot.get("id"), 
     482                type="text", 
     483                params=_text_params(rect, annot.text or "", font), 
     484            ) 
     485        elif annot.tag == "arrow": 
     486            start = tuple_eval(annot.get("start", "(0, 0)")) 
     487            end = tuple_eval(annot.get("end", "(0, 0)")) 
     488            color = annot.get("fill", "red") 
     489            annotation = _annotation( 
     490                id=annot.get("id"), 
     491                type="arrow", 
     492                params=_arrow_params((start, end), color) 
     493            ) 
     494        annotations.append(annotation) 
     495 
     496    return _scheme( 
     497        version=scheme.get("version"), 
     498        title=scheme.get("title", ""), 
     499        description=scheme.get("description"), 
     500        nodes=nodes, 
     501        links=links, 
     502        annotations=annotations 
     503    ) 
     504 
     505 
     506def parse_ows_etree_v_1_0(tree): 
     507    nodes, links = [], [] 
     508    id_gen = count() 
     509 
     510    settings = tree.find("settings") 
     511    properties = {} 
     512    if settings is not None: 
     513        data = settings.get("settingsDictionary", None) 
     514        if data: 
     515            try: 
     516                properties = literal_eval(data) 
     517            except Exception: 
     518                log.error("Could not decode properties data.", 
     519                          exc_info=True) 
     520 
     521    for widget in tree.findall("widgets/widget"): 
     522        title = widget.get("caption") 
     523        data = properties.get(title, None) 
     524        node = _node( 
     525            id=next(id_gen), 
     526            title=widget.get("caption"), 
     527            name=None, 
     528            position=(float(widget.get("xPos")), 
     529                      float(widget.get("yPos"))), 
     530            project_name=None, 
     531            qualified_name=widget.get("widgetName"), 
     532            version="", 
     533            data=_data("pickle", data) 
     534        ) 
     535        nodes.append(node) 
     536 
     537    nodes_by_title = dict((node.title, node) for node in nodes) 
     538 
     539    for channel in tree.findall("channels/channel"): 
     540        in_title = channel.get("inWidgetCaption") 
     541        out_title = channel.get("outWidgetCaption") 
     542 
     543        source = nodes_by_title[out_title] 
     544        sink = nodes_by_title[in_title] 
     545        enabled = channel.get("enabled") == "1" 
     546        # repr list of (source_name, sink_name) tuples. 
     547        signals = literal_eval(channel.get("signals")) 
     548 
     549        for source_channel, sink_channel in signals: 
     550            links.append( 
     551                _link(id=next(id_gen), 
     552                      source_node_id=source.id, 
     553                      sink_node_id=sink.id, 
     554                      source_channel=source_channel, 
     555                      sink_channel=sink_channel, 
     556                      enabled=enabled) 
     557            ) 
     558    return _scheme(title="", description="", version="1.0", 
     559                   nodes=nodes, links=links, annotations=[]) 
     560 
     561 
     562def parse_ows_stream(stream): 
     563    doc = parse(stream) 
     564    scheme_el = doc.getroot() 
     565    version = scheme_el.get("version", None) 
     566    if version is None: 
     567        # Fallback: check for "widgets" tag. 
     568        if scheme_el.find("widgets") is not None: 
     569            version = "1.0" 
     570        else: 
     571            log.warning("<scheme> tag does not have a 'version' attribute") 
     572            version = "2.0" 
     573 
     574    if version == "1.0": 
     575        return parse_ows_etree_v_1_0(doc) 
     576    elif version == "2.0": 
     577        return parse_ows_etree_v_2_0(doc) 
     578    else: 
     579        raise ValueError() 
     580 
     581 
     582def resolve_1_0(scheme_desc, registry): 
     583    widgets = registry.widgets() 
     584    widgets_by_name = dict((d.qualified_name.rsplit(".", 1)[-1], d) 
     585                           for d in widgets) 
     586    nodes = scheme_desc.nodes 
     587    for i, node in list(enumerate(nodes)): 
     588        # 1.0's qualified name is the class name only, need to replace it 
     589        # with the full qualified import name 
     590        qname = node.qualified_name 
     591        if qname in widgets_by_name: 
     592            desc = widgets_by_name[qname] 
     593            nodes[i] = node._replace(qualified_name=desc.qualified_name, 
     594                                     project_name=desc.project_name) 
     595 
     596    return scheme_desc._replace(nodes=nodes) 
     597 
     598 
     599def resolve_replaced(scheme_desc, registry): 
     600    widgets = registry.widgets() 
     601    replacements = {} 
     602    for desc in widgets: 
     603        if desc.replaces: 
     604            for repl_qname in desc.replaces: 
     605                replacements[repl_qname] = desc.qualified_name 
     606 
     607    nodes = scheme_desc.nodes 
     608    for i, node in list(enumerate(nodes)): 
     609        if not registry.has_widget(node.qualified_name) and \ 
     610                node.qualified_name in replacements: 
     611            qname = replacements[node.qualified_name] 
     612            desc = registry.widget(qname) 
     613            nodes[i] = node._replace(qualified_name=desc.qualified_name, 
     614                                     project_name=desc.project_name) 
     615 
     616    return scheme_desc._replace(nodes=nodes) 
     617 
     618 
     619def scheme_load(scheme, stream, registry=None, error_handler=None): 
     620    desc = parse_ows_stream(stream) 
     621 
     622    if registry is None: 
     623        registry = global_registry() 
     624 
     625    if error_handler is None: 
     626        def error_handler(exc): 
     627            raise exc 
     628 
     629    if desc.version == "1.0": 
     630        desc = resolve_1_0(desc, registry, error_handler) 
     631 
     632    desc = resolve_replaced(desc, registry) 
     633    nodes_not_found = [] 
     634    nodes = [] 
     635    nodes_by_id = {} 
     636    links = [] 
     637    annotations = [] 
     638 
     639    scheme.title = desc.title 
     640    scheme.description = desc.description 
     641 
     642    for node_d in desc.nodes: 
     643        try: 
     644            w_desc = registry.widget(node_d.qualified_name) 
     645        except KeyError as ex: 
     646            error_handler(UnknownWidgetDefinition(*ex.args)) 
     647            nodes_not_found.append(node_d.id) 
     648        else: 
     649            node = SchemeNode( 
     650                w_desc, title=node_d.title, position=node_d.position) 
     651            data = node_d.data 
     652 
     653            if data: 
     654                try: 
     655                    properties = loads(data.data, data.format) 
     656                except Exception: 
     657                    log.error("Could not load properties for %r.", node.title, 
     658                              exc_info=True) 
     659                else: 
     660                    node.properties = properties 
     661 
     662            nodes.append(node) 
     663            nodes_by_id[node_d.id] = node 
     664 
     665    for link_d in desc.links: 
     666        source_id = link_d.source_node_id 
     667        sink_id = link_d.sink_node_id 
     668 
     669        if source_id in nodes_not_found or sink_id in nodes_not_found: 
     670            continue 
     671 
     672        source = nodes_by_id[source_id] 
     673        sink = nodes_by_id[sink_id] 
     674        try: 
     675            link = SchemeLink(source, link_d.source_channel, 
     676                              sink, link_d.sink_channel, 
     677                              enabled=link_d.enabled) 
     678        except (ValueError, IncompatibleChannelTypeError) as ex: 
     679            error_handler(ex) 
     680        else: 
     681            links.append(link) 
     682 
     683    for annot_d in desc.annotations: 
     684        params = annot_d.params 
     685        if annot_d.type == "text": 
     686            annot = SchemeTextAnnotation(params.geometry, params.text, 
     687                                         params.font) 
     688        elif annot_d.type == "arrow": 
     689            start, end = params.geometry 
     690            annot = SchemeArrowAnnotation(start, end, params.color) 
     691 
     692        else: 
     693            log.warning("Ignoring unknown annotation type: %r", annot_d.type) 
     694        annotations.append(annot) 
     695 
     696    for node in nodes: 
     697        scheme.add_node(node) 
     698 
     699    for link in links: 
     700        scheme.add_link(link) 
     701 
     702    for annot in annotations: 
     703        scheme.add_annotation(annot) 
     704 
     705    return scheme 
    388706 
    389707 
  • Orange/OrangeCanvas/scheme/scheme.py

    r11657 r11683  
    2828) 
    2929 
    30 from .readwrite import scheme_to_ows_stream, parse_scheme 
     30from . import readwrite 
    3131 
    3232from ..registry import WidgetDescription, InputSignal, OutputSignal 
     
    620620        self.sync_node_properties() 
    621621 
    622         scheme_to_ows_stream(self, stream, pretty, 
    623                              pickle_fallback=pickle_fallback) 
     622        readwrite.scheme_to_ows_stream(self, stream, pretty, 
     623                                       pickle_fallback=pickle_fallback) 
    624624 
    625625    def load_from(self, stream): 
     
    633633        if isinstance(stream, basestring): 
    634634            stream = open(stream, "rb") 
    635  
    636         parse_scheme(self, stream) 
     635        readwrite.scheme_load(self, stream) 
     636#         parse_scheme(self, stream) 
  • Orange/OrangeCanvas/scheme/tests/test_readwrite.py

    r11391 r11683  
    11"""Test read write 
    22""" 
     3from xml.etree import ElementTree as ET 
     4from StringIO import StringIO 
     5 
    36from ...gui import test 
    4 from ...registry import global_registry 
     7from ...registry import global_registry, WidgetRegistry, WidgetDescription 
    58 
    69from .. import Scheme, SchemeNode, SchemeLink, \ 
     
    811 
    912from .. import readwrite 
    10 from ..readwrite import scheme_to_ows_stream, parse_scheme 
     13from ..readwrite import scheme_to_ows_stream, parse_scheme, scheme_load 
    1114 
    1215 
    1316class TestReadWrite(test.QAppTestCase): 
    1417    def test_io(self): 
    15         from StringIO import StringIO 
    1618        reg = global_registry() 
    1719 
     
    7577                self.assertEqual(annot1.end_pos, annot2.end_pos) 
    7678 
     79    def test_io2(self): 
     80        reg = global_registry() 
     81 
     82        base = "Orange.OrangeWidgets" 
     83        file_desc = reg.widget(base + ".Data.OWFile.OWFile") 
     84        discretize_desc = reg.widget(base + ".Data.OWDiscretize.OWDiscretize") 
     85        bayes_desc = reg.widget(base + ".Classify.OWNaiveBayes.OWNaiveBayes") 
     86 
     87        scheme = Scheme() 
     88        file_node = SchemeNode(file_desc) 
     89        discretize_node = SchemeNode(discretize_desc) 
     90        bayes_node = SchemeNode(bayes_desc) 
     91 
     92        scheme.add_node(file_node) 
     93        scheme.add_node(discretize_node) 
     94        scheme.add_node(bayes_node) 
     95 
     96        scheme.add_link(SchemeLink(file_node, "Data", 
     97                                   discretize_node, "Data")) 
     98 
     99        scheme.add_link(SchemeLink(discretize_node, "Data", 
     100                                   bayes_node, "Data")) 
     101 
     102        scheme.add_annotation(SchemeArrowAnnotation((0, 0), (10, 10))) 
     103        scheme.add_annotation(SchemeTextAnnotation((0, 100, 200, 200), "$$")) 
     104 
     105        stream = StringIO() 
     106        scheme_to_ows_stream(scheme, stream) 
     107 
     108        stream.seek(0) 
     109 
     110        scheme_1 = scheme_load(Scheme(), stream) 
     111 
     112        self.assertEqual(len(scheme.nodes), len(scheme_1.nodes)) 
     113        self.assertEqual(len(scheme.links), len(scheme_1.links)) 
     114        self.assertEqual(len(scheme.annotations), len(scheme_1.annotations)) 
     115 
     116        for n1, n2 in zip(scheme.nodes, scheme_1.nodes): 
     117            self.assertEqual(n1.position, n2.position) 
     118            self.assertEqual(n1.title, n2.title) 
     119 
     120        for link1, link2 in zip(scheme.links, scheme_1.links): 
     121            self.assertEqual(link1.source_type(), link2.source_type()) 
     122            self.assertEqual(link1.sink_type(), link2.sink_type()) 
     123 
     124            self.assertEqual(link1.source_channel.name, 
     125                             link2.source_channel.name) 
     126 
     127            self.assertEqual(link1.sink_channel.name, 
     128                             link2.sink_channel.name) 
     129 
     130            self.assertEqual(link1.enabled, link2.enabled) 
     131 
     132        for annot1, annot2 in zip(scheme.annotations, scheme_1.annotations): 
     133            self.assertIs(type(annot1), type(annot2)) 
     134            if isinstance(annot1, SchemeTextAnnotation): 
     135                self.assertEqual(annot1.text, annot2.text) 
     136                self.assertEqual(annot1.rect, annot2.rect) 
     137            else: 
     138                self.assertEqual(annot1.start_pos, annot2.start_pos) 
     139                self.assertEqual(annot1.end_pos, annot2.end_pos) 
     140 
    77141    def test_safe_evals(self): 
    78142        s = readwrite.string_eval(r"'\x00\xff'") 
     
    113177        with self.assertRaises(TypeError): 
    114178            readwrite.literal_dumps(self) 
     179 
     180    def test_1_0_parse(self): 
     181        tree = ET.parse(StringIO(FOOBAR_v10)) 
     182        parsed = readwrite.parse_ows_etree_v_1_0(tree) 
     183        self.assertIsInstance(parsed, readwrite._scheme) 
     184        self.assertEqual(parsed.version, "1.0") 
     185        self.assertTrue(len(parsed.nodes) == 2) 
     186        self.assertTrue(len(parsed.links) == 2) 
     187 
     188        qnames = [node.qualified_name for node in parsed.nodes] 
     189        self.assertSetEqual(set(qnames), set(["foo", "bar"])) 
     190 
     191        reg = foo_registry() 
     192 
     193        parsed = readwrite.resolve_1_0(parsed, reg) 
     194 
     195        qnames = [node.qualified_name for node in parsed.nodes] 
     196        self.assertSetEqual(set(qnames), 
     197                            set(["package.foo", "frob.bar"])) 
     198        projects = [node.project_name for node in parsed.nodes] 
     199        self.assertSetEqual(set(projects), set(["Foo", "Bar"])) 
     200 
     201    def test_resolve_replaced(self): 
     202        tree = ET.parse(StringIO(FOOBAR_v20)) 
     203        parsed = readwrite.parse_ows_etree_v_2_0(tree) 
     204 
     205        self.assertIsInstance(parsed, readwrite._scheme) 
     206        self.assertEqual(parsed.version, "2.0") 
     207        self.assertTrue(len(parsed.nodes) == 2) 
     208        self.assertTrue(len(parsed.links) == 2) 
     209 
     210        qnames = [node.qualified_name for node in parsed.nodes] 
     211        self.assertSetEqual(set(qnames), set(["package.foo", "package.bar"])) 
     212 
     213        reg = foo_registry() 
     214 
     215        parsed = readwrite.resolve_replaced(parsed, reg) 
     216 
     217        qnames = [node.qualified_name for node in parsed.nodes] 
     218        self.assertSetEqual(set(qnames), 
     219                            set(["package.foo", "frob.bar"])) 
     220        projects = [node.project_name for node in parsed.nodes] 
     221        self.assertSetEqual(set(projects), set(["Foo", "Bar"])) 
     222 
     223 
     224def foo_registry(): 
     225    reg = WidgetRegistry() 
     226    reg.register_widget( 
     227        WidgetDescription( 
     228            name="Foo", 
     229            id="foooo", 
     230            qualified_name="package.foo", 
     231            project_name="Foo" 
     232        ) 
     233    ) 
     234    reg.register_widget( 
     235        WidgetDescription( 
     236            name="Bar", 
     237            id="barrr", 
     238            qualified_name="frob.bar", 
     239            project_name="Bar", 
     240            replaces=["package.bar"] 
     241 
     242        ) 
     243    ) 
     244    return reg 
     245 
     246 
     247FOOBAR_v10 = """<?xml version="1.0" ?> 
     248<schema> 
     249    <widgets> 
     250        <widget caption="Foo" widgetName="foo" xPos="1" yPos="2"/> 
     251        <widget caption="Bar" widgetName="bar" xPos="2" yPos="3"/> 
     252    </widgets> 
     253    <channels> 
     254        <channel enabled="1" inWidgetCaption="Foo" outWidgetCaption="Bar" 
     255                 signals="[('foo', 'bar')]"/> 
     256        <channel enabled="0" inWidgetCaption="Foo" outWidgetCaption="Bar" 
     257                 signals="[('foo1', 'bar1')]"/> 
     258    </channels> 
     259    <settings settingsDictionary="{}"/> 
     260</schema> 
     261""" 
     262 
     263FOOBAR_v20 = """<?xml version="1.0" ?> 
     264<scheme title="FooBar" description="Foo to the bar" version="2.0"> 
     265    <nodes> 
     266        <node id="0" title="Foo" position="1, 2" project_name="Foo" 
     267              qualified_name="package.foo" /> 
     268        <node id="1" title="Bar" position="2, 3" project_name="Foo" 
     269              qualified_name="package.bar" /> 
     270    </nodes> 
     271    <links> 
     272        <link enabled="true" id="0" sink_channel="bar" sink_node_id="1" 
     273              source_channel="foo" source_node_id="0" /> 
     274        <link enabled="false" id="1" sink_channel="bar1" sink_node_id="1" 
     275              source_channel="foo1" source_node_id="0" /> 
     276    </links> 
     277</scheme> 
     278""" 
Note: See TracChangeset for help on using the changeset viewer.