From 2dd94cb2bdda87053f0911427491321cbbcd900d Mon Sep 17 00:00:00 2001 From: Benoit Favre <benoit.favre@lif.univ-mrs.fr> Date: Mon, 2 Mar 2015 18:18:22 +0100 Subject: [PATCH] add editor for xml --- .options.txt | 2 +- command.py | 63 +++++++++++++++++++++++++++ validate.py | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++ xmledit.py | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 command.py create mode 100644 validate.py create mode 100644 xmledit.py diff --git a/.options.txt b/.options.txt index c229903..acb2937 100644 --- a/.options.txt +++ b/.options.txt @@ -2,4 +2,4 @@ slu: 0 xml_filename: data/homeostasis_25nov.xml osc_host: 127.0.0.1 osc_port: 1234 -asr_model: asr/mika-fred-1.cfg +asr_model: asr/custom.cfg diff --git a/command.py b/command.py new file mode 100644 index 0000000..c7f0977 --- /dev/null +++ b/command.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python2 + +import os, threading, locale, sys + +from gi.repository import GObject, Gtk, Gdk + +GObject.threads_init() +Gdk.threads_init() + +encoding = locale.getpreferredencoding() +utf8conv = lambda x : unicode(x, encoding).encode('utf8') + +class Command(Gtk.ScrolledWindow): + def __init__(self): + super(Command, self).__init__() + self.view = Gtk.TextView() + self.view.set_editable(False) + self.view.set_cursor_visible(False) + self.buffer = self.view.get_buffer() + self.add(self.view) + self.listeners = [] + + def clear(self): + self.buffer.set_text('') + + def add_listener(self, listener): + self.listeners.append(listener) + + def run(self, command): + thread = threading.Thread(target=self._read_command_output, args=(command,)) + thread.start() + + def _read_command_output(self, command): + stdin, stdouterr = os.popen4(command) + while 1: + line = stdouterr.readline() + if not line: + break + Gdk.threads_enter() + self.add_text(utf8conv(line)) + Gdk.threads_leave() + for listener in self.listeners: + listener(command, self.buffer.get_text()) + + def add_text(self, line): + if not line.endswith('\n'): + line += '\n' + self.buffer.insert_at_cursor(line) + adj = self.get_vadjustment() + adj.set_value(adj.get_upper() - adj.get_page_size()) + +if __name__ == '__main__': + import signal + signal.signal(signal.SIGINT, signal.SIG_DFL) + + command = Command(' '.join(sys.argv[1:])) + + window = Gtk.Window() + window.add(command) + window.set_default_size(800, 600) + window.show_all() + window.connect("delete-event", Gtk.main_quit) + Gtk.main() diff --git a/validate.py b/validate.py new file mode 100644 index 0000000..d74f938 --- /dev/null +++ b/validate.py @@ -0,0 +1,119 @@ +from xml.etree import ElementTree as ET +import re + +seen_section_ids = {} +seen_actions = {} + +class VerifyException(Exception): + def __init__(self, message, node): + self.message = message + self.node = node + def __str__(self): + return self.message + ': ' + ET.tostring(self.node)[:200].strip() + def __repr__(self): + return str(self) + +def is_int(text): + return re.match(r'^\d+$', text) + +def has_blank(text): + return re.match(r'\s', text) + +def verify_keyword(node): + global seen_actions + for key in node.attrib: + if key not in ['action', 'lang']: + raise VerifyException('attribute "%s" not allowed in <%s>' % (key, node.tag), node) + for key in ['action']: + if key not in node.attrib: + raise VerifyException('node <%s> must contain attribute "%s"' % (node.tag, key), node) + if node.get('action').strip() == '': + print 'WARNING: empty action for %s' % ET.tostring(node).strip() + #raise VerifyException('empty action', node) + if has_blank(node.get('action')): + raise VerifyException('spaces not allowed in action "%s"', node) + seen_actions[node.get('action')] = True + if node.get('lang') not in [None, 'eng', 'esp']: + raise VerifyException('unsupported lang "%s"' % node.get('lang'), node) + for child in node: + raise VerifyException('child <%s> not allowed in <%s>' % (child.tag, node.tag), node) + +def verify_sequence(node): + for key in node.attrib: + if key not in ['ordre', 'repetition', 'action', 'lang']: + raise VerifyException('attribute "%s" not allowed in <%s>' % (key, node.tag), node) + if node.get('lang') not in [None, 'eng', 'esp']: + raise VerifyException('unsupported lang "%s"' % node.get('lang'), node) + for child in node: + if child.tag == 'keyword': + verify_keyword(child) + else: + raise VerifyException('child <%s> not allowed in <%s>' % (child.tag, node.tag), node) + +def verify_section(node): + global seen_section_ids + for key in node.attrib: + if key not in ['id', 'action']: + raise VerifyException('attribute "%s" not allowed in <%s>' % (key, node.tag), node) + for key in ['id']: + if key not in node.attrib: + raise VerifyException('node <%s> must contain attribute "%s"' % (node.tag, key), node) + if not is_int(node.get('id')): + raise VerifyException('only integers allowed for section id "%s"' % node.get('id')) + if node.get('id') in seen_section_ids: + raise VerifyException('repeated section id "%s"' % node.get('id')) + seen_section_ids[node.get('id')] = True + for child in node: + if child.tag == 'sequence': + verify_sequence(child) + else: + raise VerifyException('child <%s> not allowed in <%s>' % (child.tag, node.tag), node) + if node.text != None and node.text.strip() != '': + raise VerifyException('no text allowed directly in <%s>' % (node.tag), node) + if node.tail != None and node.tail.strip() != '': + raise VerifyException('no text allowed directly after <%s>' % (node.tag), node) + +def verify_liste_section(node): + for key in node.attrib: + if key not in ['sequences', 'ordre', 'repetition', 'action']: + raise VerifyException('attribute "%s" not allowed in <%s>' % (key, node.tag), node) + for child in node: + if child.tag == 'section': + verify_section(child) + else: + raise VerifyException('child <%s> not allowed in <%s>' % (child.tag, node.tag), node) + if node.text != None and node.text.strip() != '': + raise VerifyException('no text allowed directly in <%s>' % (node.tag), node) + if node.tail != None and node.tail.strip() != '': + raise VerifyException('no text allowed directly after <%s>' % (node.tag), node) + +def verify_root(node): + if node.tag != 'homeostasis': + raise VerifyException('root tag should be <homeostasis>') + for key in node.attrib: + if key not in ['version']: + raise VerifyException('attribute "%s" not allowed in <%s>' % (key, node.tag), node) + for child in node: + if child.tag == 'liste_section': + verify_liste_section(child) + else: + raise VerifyException('child <%s> not allowed in <%s>' % (child.tag, node.tag), node) + if node.text != None and node.text.strip() != '': + raise VerifyException('no text allowed directly in <%s>' % (node.tag), node) + if node.tail != None and node.tail.strip() != '': + raise VerifyException('no text allowed directly after <%s>' % (node.tag), node) + +def validate_xml(filename): + global seen_section_ids, seen_actions + seen_section_ids = {} + seen_actions = {} + try: + root = ET.parse(filename).getroot() + verify_root(root) + except Exception as e: + return (False, str(e)) + return (True, 'successfuly validated "%s"\nfound %d sections, %d types of action' % (filename, len(seen_section_ids), len(seen_actions))) + +if __name__ == '__main__': + import sys + print validate_xml(sys.argv[1]) diff --git a/xmledit.py b/xmledit.py new file mode 100644 index 0000000..b35b996 --- /dev/null +++ b/xmledit.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python2 + +import signal +signal.signal(signal.SIGINT, signal.SIG_DFL) + +import os, sys +os.environ['GTK_THEME'] = 'light' + +from gi.repository import GObject, Gst, Gtk, Gdk, GtkSource + +GObject.threads_init() +Gdk.threads_init() + +import command, validate + +class SourceView(Gtk.Window): + def __init__(self, filename): + super(SourceView, self).__init__() + + self.filename = filename + + self.connect("destroy", self.quit) + self.set_default_size(800,600) + self.set_border_width(10) + self.set_title('SourceView') + + tabs = Gtk.Notebook() + self.tabs = tabs + + vbox = Gtk.VBox() # contains tabs and buttons + + self.source_buffer = GtkSource.Buffer() + lang_manager = GtkSource.LanguageManager() + self.source_buffer.set_language(lang_manager.get_language('xml')) + self.source_view = GtkSource.View.new_with_buffer(self.source_buffer) + self.source_view.set_show_line_numbers(True) + self.source_view.set_tab_width(4) + self.source_view.set_wrap_mode(Gtk.WrapMode.WORD) + self.scrolled = Gtk.ScrolledWindow() + self.scrolled.add(self.source_view) + #vbox.pack_start(self.scrolled, True, True, 5) + + self.command = command.Command() + #vbox.pack_start(self.command, False, True, 5) + + hbox = Gtk.HBox() + revert_button = Gtk.Button('Revert') + revert_button.connect('clicked', self.load) + hbox.pack_start(revert_button, False, False, 5) + + validate_button = Gtk.Button('Validate') + validate_button.connect('clicked', self.validate) + hbox.pack_start(validate_button, False, False, 5) + + compile_button = Gtk.Button('Compile') + compile_button.connect('clicked', self.compile) + hbox.pack_start(compile_button, False, False, 5) + + save_button = Gtk.Button('Save') + save_button.connect('clicked', self.save) + hbox.pack_start(save_button, False, False, 5) + + vbox.pack_start(tabs, True, True, 5) + vbox.pack_start(hbox, False, True, 5) + + tabs.append_page(self.scrolled, Gtk.Label(filename)) + tabs.append_page(self.command, Gtk.Label('Compilation result')) + tabs.set_show_border(False) + + self.add(vbox) + self.show_all() + + self.load() + + def load(self, *args): + self.source_buffer.set_text(open(self.filename).read()) + + def save(self, *args): + self.command.clear() + try: + with open('data/edited.xml', 'w') as fp: + bounds = self.source_buffer.get_bounds() + fp.write(self.source_buffer.get_text(bounds[0], bounds[1], True)) + except Exception as e: + print e + self.command.add_text('FAILED to save as data/edited.xml') + + def validate(self, *args): + self.command.clear() + self.save() + self.command.add_text('VALIDATE...') + self.tabs.set_current_page(1) + result, message = validate.validate_xml('data/edited.xml') + self.command.add_text(message) + if result: + self.command.add_text('SUCCESS') + else: + self.command.add_text('FAILED') + return result + + def compile(self, *args): + self.command.clear() + self.tabs.set_current_page(1) + self.save() + self.validate() + self.command.add_text('COMPILE...') + self.command.run('./tools/compile.ben.sh data/edited.xml tools/model') + + def quit(self, window): + Gtk.main_quit() + +if __name__ == '__main__': + app = SourceView(sys.argv[1]) + Gtk.main() -- GitLab