summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohnathan Corgan <johnathan@corganlabs.com>2014-01-27 10:06:39 -0800
committerJohnathan Corgan <johnathan@corganlabs.com>2014-01-27 10:06:39 -0800
commitfa32b7b02c7c01a8f04590b347040c3937199c7e (patch)
tree31deb425cef10ba225be8e3587dd99fd98b6b7b8
parent3fa56a46fb91dabbc4973f9d3983639a88c12d6c (diff)
parent1c3b5187a61d8bf989d00791997a93a041d18d5b (diff)
Merge remote-tracking branch 'gnuradio-wg-grc/grc_xml_error_dialog'
-rw-r--r--grc/base/ParseXML.py33
-rw-r--r--grc/base/Platform.py113
-rw-r--r--grc/gui/ActionHandler.py18
-rw-r--r--grc/gui/Actions.py7
-rw-r--r--grc/gui/Bars.py1
-rw-r--r--grc/gui/CMakeLists.txt1
-rw-r--r--grc/gui/Messages.py6
-rw-r--r--grc/gui/ParserErrorsDialog.py100
8 files changed, 221 insertions, 58 deletions
diff --git a/grc/base/ParseXML.py b/grc/base/ParseXML.py
index 56097395dd..abb6576ac2 100644
--- a/grc/base/ParseXML.py
+++ b/grc/base/ParseXML.py
@@ -20,12 +20,18 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
from lxml import etree
from . import odict
+xml_failures = {}
+
+
class XMLSyntaxError(Exception):
def __init__(self, error_log):
self._error_log = error_log
+ xml_failures[error_log.last_error.filename] = error_log
+
def __str__(self):
return '\n'.join(map(str, self._error_log.filter_from_errors()))
+
def validate_dtd(xml_file, dtd_file=None):
"""
Validate an xml file against its dtd.
@@ -36,13 +42,23 @@ def validate_dtd(xml_file, dtd_file=None):
@throws Exception validation fails
"""
#perform parsing, use dtd validation if dtd file is not specified
- parser = etree.XMLParser(dtd_validation=not dtd_file)
- xml = etree.parse(xml_file, parser=parser)
- if parser.error_log: raise XMLSyntaxError(parser.error_log)
- #perform dtd validation if the dtd file is specified
- if not dtd_file: return
- dtd = etree.DTD(dtd_file)
- if not dtd.validate(xml.getroot()): raise XMLSyntaxError(dtd.error_log)
+ try:
+ parser = etree.XMLParser(dtd_validation=not dtd_file)
+ xml = etree.parse(xml_file, parser=parser)
+ except etree.LxmlError:
+ pass
+ if parser.error_log:
+ raise XMLSyntaxError(parser.error_log)
+
+ # perform dtd validation if the dtd file is specified
+ if not dtd_file:
+ return
+ try:
+ dtd = etree.DTD(dtd_file)
+ if not dtd.validate(xml.getroot()):
+ raise XMLSyntaxError(dtd.error_log)
+ except etree.LxmlError:
+ raise XMLSyntaxError(dtd.error_log)
def from_file(xml_file):
"""
@@ -57,6 +73,7 @@ def from_file(xml_file):
xml = etree.parse(xml_file).getroot()
return _from_file(xml)
+
def _from_file(xml):
"""
Recursivly parse the xml tree into nested data format.
@@ -81,6 +98,7 @@ def _from_file(xml):
return odict({tag: nested_data})
+
def to_file(nested_data, xml_file):
"""
Write an xml file and use the to xml helper method to load it.
@@ -92,6 +110,7 @@ def to_file(nested_data, xml_file):
xml = _to_file(nested_data)[0]
open(xml_file, 'w').write(etree.tostring(xml, xml_declaration=True, pretty_print=True))
+
def _to_file(nested_data):
"""
Recursivly parse the nested data into xml tree format.
diff --git a/grc/base/Platform.py b/grc/base/Platform.py
index 88cbf32b89..3ff80e8a03 100644
--- a/grc/base/Platform.py
+++ b/grc/base/Platform.py
@@ -28,11 +28,11 @@ from Port import Port as _Port
from Param import Param as _Param
from Constants import BLOCK_TREE_DTD, FLOW_GRAPH_DTD
-class Platform(_Element):
+class Platform(_Element):
def __init__(self, name, version, key,
- block_paths, block_dtd, default_flow_graph, generator,
- license='', website=None, colors=[]):
+ block_paths, block_dtd, default_flow_graph, generator,
+ license='', website=None, colors=None):
"""
Make a platform from the arguments.
@@ -61,48 +61,65 @@ class Platform(_Element):
self._block_dtd = block_dtd
self._default_flow_graph = default_flow_graph
self._generator = generator
- self._colors = colors
+ self._colors = colors or []
#create a dummy flow graph for the blocks
self._flow_graph = _Element(self)
- #search for *.xml files in the given search path
- self.loadblocks();
-
- def loadblocks(self):
- xml_files = list()
+ self._blocks = None
+ self._blocks_n = None
+ self._category_trees_n = None
+ self.load_blocks()
+
+ def load_blocks(self):
+ """load the blocks and block tree from the search paths"""
+ # reset
+ self._blocks = odict()
+ self._blocks_n = odict()
+ self._category_trees_n = list()
+ ParseXML.xml_failures.clear()
+ # try to parse and load blocks
+ for xml_file in self.iter_xml_files():
+ try:
+ if xml_file.endswith("block_tree.xml"):
+ self.load_category_tree_xml(xml_file)
+ else:
+ self.load_block_xml(xml_file)
+ except ParseXML.XMLSyntaxError as e:
+ # print >> sys.stderr, 'Warning: Block validation failed:\n\t%s\n\tIgnoring: %s' % (e, xml_file)
+ pass
+ except Exception as e:
+ print >> sys.stderr, 'Warning: Block loading failed:\n\t%s\n\tIgnoring: %s' % (e, xml_file)
+
+ def iter_xml_files(self):
+ """Iterator for block descriptions and category trees"""
for block_path in self._block_paths:
- if os.path.isfile(block_path): xml_files.append(block_path)
+ if os.path.isfile(block_path):
+ yield block_path
elif os.path.isdir(block_path):
for dirpath, dirnames, filenames in os.walk(block_path):
for filename in sorted(filter(lambda f: f.endswith('.xml'), filenames)):
- xml_files.append(os.path.join(dirpath, filename))
- #load the blocks
- self._blocks = odict()
- self._blocks_n = odict()
- self._block_tree_files = list()
- for xml_file in xml_files:
- try: #try to add the xml file as a block wrapper
- ParseXML.validate_dtd(xml_file, self._block_dtd)
- n = ParseXML.from_file(xml_file).find('block')
- #inject block wrapper path
- n['block_wrapper_path'] = xml_file
- block = self.Block(self._flow_graph, n)
- key = block.get_key()
- #test against repeated keys
- if key in self.get_block_keys():
- print >> sys.stderr, 'Warning: Block with key "%s" already exists.\n\tIgnoring: %s'%(key, xml_file)
- #store the block
- else:
- self._blocks[key] = block
- self._blocks_n[key] = n
- except ParseXML.XMLSyntaxError, e:
- try: #try to add the xml file as a block tree
- ParseXML.validate_dtd(xml_file, BLOCK_TREE_DTD)
- self._block_tree_files.append(xml_file)
- except ParseXML.XMLSyntaxError, e:
- print >> sys.stderr, 'Warning: Block validation failed:\n\t%s\n\tIgnoring: %s'%(e, xml_file)
- except Exception, e:
- print >> sys.stderr, 'Warning: Block loading failed:\n\t%s\n\tIgnoring: %s'%(e, xml_file)
+ yield os.path.join(dirpath, filename)
+
+ def load_block_xml(self, xml_file):
+ """Load block description from xml file"""
+ # validate and import
+ ParseXML.validate_dtd(xml_file, self._block_dtd)
+ n = ParseXML.from_file(xml_file).find('block')
+ n['block_wrapper_path'] = xml_file # inject block wrapper path
+ # get block instance and add it to the list of blocks
+ block = self.Block(self._flow_graph, n)
+ key = block.get_key()
+ if key in self.get_block_keys(): # test against repeated keys
+ print >> sys.stderr, 'Warning: Block with key "%s" already exists.\n\tIgnoring: %s' % (key, xml_file)
+ else: # store the block
+ self._blocks[key] = block
+ self._blocks_n[key] = n
+
+ def load_category_tree_xml(self, xml_file):
+ """Validate and parse category tree file and add it to list"""
+ ParseXML.validate_dtd(xml_file, BLOCK_TREE_DTD)
+ n = ParseXML.from_file(xml_file).find('cat')
+ self._category_trees_n.append(n)
def parse_flow_graph(self, flow_graph_file):
"""
@@ -117,7 +134,7 @@ class Platform(_Element):
@throws exception if the validation fails
"""
flow_graph_file = flow_graph_file or self._default_flow_graph
- open(flow_graph_file, 'r') #test open
+ open(flow_graph_file, 'r') # test open
ParseXML.validate_dtd(flow_graph_file, FLOW_GRAPH_DTD)
return ParseXML.from_file(flow_graph_file)
@@ -131,24 +148,26 @@ class Platform(_Element):
block_tree: the block tree object
"""
#recursive function to load categories and blocks
- def load_category(cat_n, parent=[]):
+ def load_category(cat_n, parent=None):
#add this category
- parent = parent + [cat_n.find('name')]
+ parent = (parent or []) + [cat_n.find('name')]
block_tree.add_block(parent)
#recursive call to load sub categories
map(lambda c: load_category(c, parent), cat_n.findall('cat'))
#add blocks in this category
for block_key in cat_n.findall('block'):
if block_key not in self.get_block_keys():
- print >> sys.stderr, 'Warning: Block key "%s" not found when loading category tree.'%(block_key)
+ print >> sys.stderr, 'Warning: Block key "%s" not found when loading category tree.' % (block_key)
continue
block = self.get_block(block_key)
#if it exists, the block's category shall not be overridden by the xml tree
- if not block.get_category(): block.set_category(parent)
- #load the block tree and update the categories for each block
- for block_tree_file in self._block_tree_files:
- #recursivly put categories in blocks
- load_category(ParseXML.from_file(block_tree_file).find('cat'))
+ if not block.get_category():
+ block.set_category(parent)
+
+ # recursively load the category trees and update the categories for each block
+ for category_tree_n in self._category_trees_n:
+ load_category(category_tree_n)
+
#add blocks to block tree
for block in self.get_blocks():
#blocks with empty categories are hidden
diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index a891298fa4..86f06aad58 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -31,6 +31,7 @@ import Messages
from .. base import ParseXML
from MainWindow import MainWindow
from PropsDialog import PropsDialog
+from ParserErrorsDialog import ParserErrorsDialog
import Dialogs
from FileDialogs import OpenFlowGraphFileDialog, SaveFlowGraphFileDialog, SaveImageFileDialog
@@ -116,6 +117,10 @@ class ActionHandler:
Actions.TYPES_WINDOW_DISPLAY, Actions.TOGGLE_BLOCKS_WINDOW,
Actions.TOGGLE_REPORTS_WINDOW,
): action.set_sensitive(True)
+ if ParseXML.xml_failures:
+ Messages.send_xml_errors_if_any(ParseXML.xml_failures)
+ Actions.XML_PARSER_ERRORS_DISPLAY.set_sensitive(True)
+
if not self.init_file_paths:
self.init_file_paths = Preferences.files_open()
if not self.init_file_paths: self.init_file_paths = ['']
@@ -377,6 +382,11 @@ class ActionHandler:
self.get_flow_graph().import_data(n)
self.get_flow_graph().update()
##################################################
+ # View Parser Errors
+ ##################################################
+ elif action == Actions.XML_PARSER_ERRORS_DISPLAY:
+ ParserErrorsDialog(ParseXML.xml_failures).run()
+ ##################################################
# Undo/Redo
##################################################
elif action == Actions.FLOW_GRAPH_UNDO:
@@ -454,9 +464,11 @@ class ActionHandler:
elif action == Actions.PAGE_CHANGE: #pass and run the global actions
pass
elif action == Actions.RELOAD_BLOCKS:
- self.platform.loadblocks()
- self.main_window.btwin.clear();
- self.platform.load_block_tree(self.main_window.btwin);
+ self.platform.load_blocks()
+ self.main_window.btwin.clear()
+ self.platform.load_block_tree(self.main_window.btwin)
+ Actions.XML_PARSER_ERRORS_DISPLAY.set_sensitive(bool(ParseXML.xml_failures))
+ Messages.send_xml_errors_if_any(ParseXML.xml_failures)
elif action == Actions.FIND_BLOCKS:
self.main_window.btwin.show()
self.main_window.btwin.search_entry.show()
diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py
index e96fb4d276..284c78f8fc 100644
--- a/grc/gui/Actions.py
+++ b/grc/gui/Actions.py
@@ -261,7 +261,7 @@ BLOCK_PASTE = Action(
keypresses=(gtk.keysyms.v, gtk.gdk.CONTROL_MASK),
)
ERRORS_WINDOW_DISPLAY = Action(
- label='_Errors',
+ label='Flowgraph _Errors',
tooltip='View flow graph errors',
stock_id=gtk.STOCK_DIALOG_ERROR,
)
@@ -354,3 +354,8 @@ BUSSIFY_SINKS = Action(
tooltip='Gang sink ports into a single bus port',
stock_id=gtk.STOCK_JUMP_TO,
)
+XML_PARSER_ERRORS_DISPLAY = Action(
+ label='_Parser Errors',
+ tooltip='View errors that occured while parsing XML files',
+ stock_id=gtk.STOCK_DIALOG_ERROR,
+)
diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py
index 0e2b29c4e3..f016209383 100644
--- a/grc/gui/Bars.py
+++ b/grc/gui/Bars.py
@@ -103,6 +103,7 @@ MENU_BAR_LIST = (
(gtk.Action('Help', '_Help', None, None), [
Actions.HELP_WINDOW_DISPLAY,
Actions.TYPES_WINDOW_DISPLAY,
+ Actions.XML_PARSER_ERRORS_DISPLAY,
None,
Actions.ABOUT_WINDOW_DISPLAY,
]),
diff --git a/grc/gui/CMakeLists.txt b/grc/gui/CMakeLists.txt
index c2eb16e9fd..08aaf3e4df 100644
--- a/grc/gui/CMakeLists.txt
+++ b/grc/gui/CMakeLists.txt
@@ -39,6 +39,7 @@ GR_PYTHON_INSTALL(FILES
MainWindow.py
Messages.py
NotebookPage.py
+ ParserErrorsDialog.py
PropsDialog.py
Preferences.py
StateCache.py
diff --git a/grc/gui/Messages.py b/grc/gui/Messages.py
index d903e40a45..a6620aa72f 100644
--- a/grc/gui/Messages.py
+++ b/grc/gui/Messages.py
@@ -53,6 +53,12 @@ def send_init(platform):
def send_page_switch(file_path):
send('\nShowing: "%s"\n'%file_path)
+################# functions for loading blocks ########################################
+def send_xml_errors_if_any(xml_failures):
+ if xml_failures:
+ send('\nXML parser: Found {} erroneous XML file{} while loading the block tree '
+ '(see "Help/Parser errors" for details)\n'.format(len(xml_failures), 's' if len(xml_failures) > 1 else ''))
+
################# functions for loading flow graphs ########################################
def send_start_load(file_path):
send('\nLoading: "%s"'%file_path + '\n')
diff --git a/grc/gui/ParserErrorsDialog.py b/grc/gui/ParserErrorsDialog.py
new file mode 100644
index 0000000000..68ee459414
--- /dev/null
+++ b/grc/gui/ParserErrorsDialog.py
@@ -0,0 +1,100 @@
+"""
+Copyright 2013 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+"""
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+
+from Constants import MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT
+
+
+class ParserErrorsDialog(gtk.Dialog):
+ """
+ A dialog for viewing parser errors
+ """
+
+ def __init__(self, error_logs):
+ """
+ Properties dialog constructor.
+
+ Args:
+ block: a block instance
+ """
+ gtk.Dialog.__init__(self, title='Parser Errors', buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT))
+
+ self._error_logs = None
+ self.tree_store = gtk.TreeStore(str)
+ self.update_tree_store(error_logs)
+
+ column = gtk.TreeViewColumn('XML Parser Errors by Filename')
+ renderer = gtk.CellRendererText()
+ column.pack_start(renderer, True)
+ column.add_attribute(renderer, 'text', 0)
+ column.set_sort_column_id(0)
+
+ self.tree_view = tree_view = gtk.TreeView(self.tree_store)
+ tree_view.set_enable_search(False) # disable pop up search box
+ tree_view.set_search_column(-1) # really disable search
+ tree_view.set_reorderable(False)
+ tree_view.set_headers_visible(False)
+ tree_view.get_selection().set_mode(gtk.SELECTION_NONE)
+ tree_view.append_column(column)
+
+ for row in self.tree_store:
+ tree_view.expand_row(row.path, False)
+
+ scrolled_window = gtk.ScrolledWindow()
+ scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled_window.add_with_viewport(tree_view)
+
+ self.vbox.pack_start(scrolled_window, True)
+ self.set_size_request(2*MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT)
+ self.show_all()
+
+ def update_tree_store(self, error_logs):
+ """set up data model"""
+ self.tree_store.clear()
+ self._error_logs = error_logs
+ for filename, errors in error_logs.iteritems():
+ parent = self.tree_store.append(None, [str(filename)])
+ try:
+ with open(filename, 'r') as fp:
+ code = fp.readlines()
+ except EnvironmentError:
+ code = None
+ for error in errors:
+ # http://lxml.de/api/lxml.etree._LogEntry-class.html
+ em = self.tree_store.append(parent, ["Line {e.line}: {e.message}".format(e=error)])
+ if code:
+ self.tree_store.append(em, ["\n".join(
+ "{} {}{}".format(line, code[line - 1].replace("\t", " ").strip("\n"),
+ " " * 20 + "<!-- ERROR -->" if line == error.line else "")
+ for line in range(error.line - 2, error.line + 3) if 0 < line <= len(code)
+ )])
+
+ def run(self):
+ """
+ Run the dialog and get its response.
+
+ Returns:
+ true if the response was accept
+ """
+ response = gtk.Dialog.run(self)
+ self.destroy()
+ return response == gtk.RESPONSE_ACCEPT