Merged changeset r9285:9377 from jblum/grc into trunk, with distcheck fixes
[debian/gnuradio] / grc / src / grc / converter.py
1 """
2 Copyright 2008 Free Software Foundation, Inc.
3 This file is part of GNU Radio
4
5 GNU Radio Companion is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 GNU Radio Companion is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18 """
19 ##@package grc.converter
20 #convert old flow graph file format to new format
21 #@author Josh Blum
22
23 from grc.Constants import FLOW_GRAPH_DTD
24 from grc import ParseXML, Utils
25 from grc.Utils import odict
26 from lxml import etree
27 import difflib
28 import os
29
30 def _make_param(key, value):
31         """!
32         Make a paramater dict from the key/value pair.
33         @param key the key
34         @param value the value
35         @return a dictionary object
36         """
37         param = odict()
38         param['key'] = key
39         param['value'] = value
40         return param
41
42 def _get_blocks(blocks, tag):
43         """!
44         Get a list of blocks with the tag.
45         @param blocks the old block list
46         @param tag the tag name
47         @retun a list of matching blocks
48         """
49         return filter(lambda b: b['tag'] == tag, blocks)
50
51 def _get_params(block):
52         """!
53         Get a list of params.
54         @param block the old block
55         @retun a list of params
56         """
57         params = Utils.exists_or_else(block, 'params', {}) or {}
58         params = Utils.listify(params, 'param')
59         return params
60
61 def _convert_id(id):
62         """!
63         Convert an old id to a new safe id.
64         Replace spaces with underscores.
65         Lower case the odl id.
66         @return the reformatted id
67         """
68         return id.lower().replace(' ', '_')
69
70 def convert(file_path, platform):
71         """!
72         Convert the flow graph to the new format.
73         Make a backup of the old file.
74         Save a reformated flow graph to the file path.
75         If this is a new format flow graph, do nothing.
76         @param file_path the path to the saved flow graph
77         @param platform the grc gnuradio platform
78         """
79         try: #return if file passes validation
80                 ParseXML.validate_dtd(file_path, FLOW_GRAPH_DTD)
81                 try:
82                         changed = False
83                         #convert instances of gui_coordinate and gui_rotation
84                         xml = etree.parse(file_path)
85                         for find, replace in (
86                                 ('gui_coordinate', '_coordinate'),
87                                 ('gui_rotation', '_rotation'),
88                         ):
89                                 keys = xml.xpath('/flow_graph/block/param[key="%s"]/key'%find)
90                                 for key in keys:
91                                         key.text = replace
92                                         changed = True
93                         if not changed: return
94                         #backup after successful conversion
95                         os.rename(file_path, file_path+'.bak')
96                         #save new flow graph to file path
97                         xml.write(file_path, xml_declaration=True, pretty_print=True)
98                 except Exception, e: print e
99                 return
100         except: pass #convert
101         ############################################################
102         # extract window size, variables, blocks, and connections
103         ############################################################
104         old_n = ParseXML.from_file(file_path)['flow_graph']
105         try: window_width = min(3*int(old_n['window_width'])/2, 2048)
106         except: window_width = 2048
107         try: window_height = min(3*int(old_n['window_height'])/2, 2048)
108         except: window_height = 2048
109         window_size = '%d, %d'%(window_width, window_height)
110         variables = Utils.exists_or_else(old_n, 'vars', {}) or {}
111         variables = Utils.listify(variables, 'var')
112         blocks = Utils.exists_or_else(old_n, 'signal_blocks', {}) or {}
113         blocks = Utils.listify(blocks, 'signal_block')
114         connections = Utils.exists_or_else(old_n, 'connections', {}) or {}
115         connections = Utils.listify(connections, 'connection')
116         #initialize new nested data
117         new_n = odict()
118         new_n['block'] = list()
119         new_n['connection'] = list()
120         ############################################################
121         # conversion - options block
122         ############################################################
123         #get name
124         about_blocks = _get_blocks(blocks, 'About')
125         if about_blocks: title = _get_params(about_blocks[0])[0]
126         else: title = 'Untitled'
127         #get author
128         if about_blocks: author = _get_params(about_blocks[0])[1]
129         else: author = ''
130         #get desc
131         note_blocks = _get_blocks(blocks, 'Note')
132         if note_blocks: desc = _get_params(note_blocks[0])[0]
133         else: desc = ''
134         #create options block
135         options_block = odict()
136         options_block['key'] = 'options'
137         options_block['param'] = [
138                 _make_param('id', 'top_block'),
139                 _make_param('title', title),
140                 _make_param('author', author),
141                 _make_param('description', desc),
142                 _make_param('window_size', window_size),
143                 _make_param('_coordinate', '(10, 10)'),
144         ]
145         #append options block
146         new_n['block'].append(options_block)
147         ############################################################
148         # conversion - variables
149         ############################################################
150         x = 100 
151         for variable in variables:
152                 key = variable['key']
153                 value = variable['value']
154                 minimum = Utils.exists_or_else(variable, 'min', '')
155                 maximum = Utils.exists_or_else(variable, 'max', '')
156                 step = Utils.exists_or_else(variable, 'step', '')
157                 x = x + 150
158                 coor = '(%d, %d)'%(x, 10)
159                 var_block = odict()
160                 if minimum and maximum: #slider varible
161                         #determine num steps
162                         try: num_steps = str(int((float(maximum) - float(minimum))/float(step)))
163                         except: num_steps = '100'
164                         var_block['key'] = 'variable_slider'
165                         var_block['param'] = [
166                                 _make_param('id', key),
167                                 _make_param('value', value),
168                                 _make_param('min', minimum),
169                                 _make_param('max', maximum),
170                                 _make_param('num_steps', num_steps),
171                                 _make_param('_coordinate', coor),
172                         ]
173                 else: #regular variable
174                         var_block['key'] = 'variable'
175                         var_block['param'] = [
176                                 _make_param('id', key),
177                                 _make_param('value', value),
178                                 _make_param('_coordinate', coor),
179                         ]
180                 #append variable block
181                 new_n['block'].append(var_block)
182         ############################################################
183         # conversion - blocks
184         ############################################################
185         #create name to key map for all blocks in platform
186         name_to_key = dict((b.get_name(), b.get_key()) for b in platform.get_blocks())
187         for block in blocks:
188                 #extract info
189                 tag = block['tag']
190                 #ignore list
191                 if tag in ('Note', 'About'): continue
192                 id = _convert_id(block['id'])
193                 coor = '(%s, %s + 100)'%(
194                         Utils.exists_or_else(block, 'x_coordinate', '0'),
195                         Utils.exists_or_else(block, 'y_coordinate', '0'),
196                 )
197                 rot = Utils.exists_or_else(block, 'rotation', '0')
198                 params = _get_params(block)
199                 #new block
200                 new_block = odict()
201                 matches = difflib.get_close_matches(tag, name_to_key.keys(), 1)
202                 if not matches: continue
203                 #match found
204                 key = name_to_key[matches[0]]
205                 new_block['key'] = key
206                 new_block['param'] = [
207                         _make_param('id', id),
208                         _make_param('_coordinate', coor),
209                         _make_param('_rotation', rot),
210                 ]
211                 #handle specific blocks
212                 if key == 'wxgui_fftsink2':
213                         params = params[0:3] + ['0'] + params[3:4] + ['8'] + params[4:]
214                 #append params
215                 for i, param in enumerate(params):
216                         platform_block = platform.get_block(key)
217                         try: platform_param = platform_block.get_params()[i+2]
218                         except IndexError: break
219                         if platform_param.is_enum():
220                                 try: param_value = platform_param.get_option_keys()[int(param)]
221                                 except: param_value = platform_param.get_option_keys()[0]
222                         else:
223                                 param_value = param.replace('$', '').replace('^', '**')
224                         new_block['param'].append(_make_param(platform_param.get_key(), param_value))
225                 #append block
226                 new_n['block'].append(new_block)
227         ############################################################
228         # conversion - connections
229         ############################################################
230         for connection in connections:
231                 #extract info
232                 input_signal_block_id = connection['input_signal_block_id']
233                 input_socket_index = connection['input_socket_index']
234                 output_signal_block_id = connection['output_signal_block_id']
235                 output_socket_index = connection['output_socket_index']
236                 #new connection
237                 new_conn = odict()
238                 new_conn['source_block_id'] = _convert_id(output_signal_block_id)
239                 new_conn['sink_block_id'] = _convert_id(input_signal_block_id)
240                 new_conn['source_key'] = output_socket_index
241                 new_conn['sink_key'] = input_socket_index
242                 #append connection
243                 new_n['connection'].append(new_conn)
244         ############################################################
245         # backup and replace
246         ############################################################
247         #backup after successful conversion
248         os.rename(file_path, file_path+'.bak')
249         #save new flow graph to file path
250         ParseXML.to_file({'flow_graph': new_n}, file_path)
251